From e8bda6ca052c573245291e3c8c3cbd51063552a2 Mon Sep 17 00:00:00 2001 From: Garfie Date: Wed, 8 Apr 2026 19:15:49 -0500 Subject: [PATCH] refactor: replace fully-qualified names with proper imports across codebase --- .../garfiec/librechat/LibreChatApplication.kt | 3 +- .../com/garfiec/librechat/MainActivity.kt | 2 +- .../librechat/navigation/TabletLayout.kt | 3 +- .../librechat/KoinGraphVerificationTest.kt | 17 ++-- .../common/di/CommonModuleVerificationTest.kt | 6 +- .../core/data/db/RoomMigrationTest.kt | 3 +- .../data/di/DataPlatformModule.android.kt | 3 +- .../data/di/DataModuleVerificationTest.kt | 92 ++++++++++++------- .../librechat/core/data/db/dao/MessageDao.kt | 3 +- .../core/data/mapper/ConversationMapper.kt | 4 +- .../core/data/datastore/IosTokenDataStore.kt | 3 +- .../core/data/di/DataPlatformModule.ios.kt | 14 ++- .../di/NetworkModuleVerificationTest.kt | 18 ++-- .../network/client/LibreChatHttpClient.kt | 3 +- .../librechat/core/network/sse/SseClient.kt | 3 +- .../core/network/sse/SseEventMapper.kt | 3 +- .../core/network/sse/SseLineParser.kt | 3 +- .../agents/di/AgentsModuleVerificationTest.kt | 21 +++-- .../components/model/AgentSharingState.kt | 4 +- .../components/model/SupportContactState.kt | 4 +- .../agents/screen/AgentDetailScreen.kt | 3 +- .../agents/screen/AgentMarketplaceScreen.kt | 3 +- .../agents/viewmodel/AgentEditorViewModel.kt | 11 ++- .../auth/di/AuthModuleVerificationTest.kt | 27 ++++-- .../feature/chat/components/AudioContent.kt | 3 +- .../feature/chat/components/ChatInput.kt | 3 +- .../chat/components/ContentPartRenderer.kt | 3 +- .../feature/chat/components/LatexBlock.kt | 5 +- .../feature/chat/screen/ChatScreen.kt | 3 +- .../librechat/feature/chat/util/FileUtils.kt | 9 +- .../delegate/FileAttachmentDelegate.kt | 5 +- .../delegate/TextToSpeechDelegate.kt | 11 ++- .../chat/di/ChatModuleVerificationTest.kt | 57 ++++++++---- .../chat/components/AttachmentChipsRow.kt | 3 +- .../feature/chat/components/LandingContent.kt | 3 +- .../chat/components/PlatformChatComponents.kt | 3 +- .../feature/chat/viewmodel/ChatUiState.kt | 17 ++-- .../feature/chat/viewmodel/ChatViewModel.kt | 13 ++- .../components/PlatformChatComponents.ios.kt | 9 +- .../components/PlatformMediaComponents.ios.kt | 19 ++-- .../artifact/PlatformArtifactPanel.ios.kt | 3 +- .../feature/chat/viewmodel/delegate/IosTts.kt | 5 +- .../di/ConversationsModuleVerificationTest.kt | 33 ++++--- .../components/ConversationActions.kt | 5 +- .../platform/PlatformActions.ios.kt | 20 ++-- .../files/platform/PdfPreview.android.kt | 3 +- .../files/di/FilesModuleVerificationTest.kt | 18 ++-- .../feature/files/viewmodel/FilesViewModel.kt | 3 +- .../delegate/SpeechSettingsDelegate.kt | 6 +- .../di/SettingsModuleVerificationTest.kt | 60 ++++++++---- .../viewmodel/SettingsViewModelTest.kt | 3 +- .../feature/settings/screen/ApiKeysScreen.kt | 6 +- .../settings/screen/GeneralSettingsScreen.kt | 11 ++- .../settings/screen/McpSettingsSection.kt | 2 +- .../settings/screen/SharedLinksScreen.kt | 3 +- .../screen/sections/ChatPreferencesSection.kt | 3 +- .../screen/sections/SecuritySection.kt | 11 ++- .../delegate/IosSpeechSettingsDelegate.kt | 5 +- .../shared/navigation/NavHostViewModel.kt | 3 +- 59 files changed, 409 insertions(+), 216 deletions(-) diff --git a/app/src/main/kotlin/com/garfiec/librechat/LibreChatApplication.kt b/app/src/main/kotlin/com/garfiec/librechat/LibreChatApplication.kt index 989dd99..a980279 100644 --- a/app/src/main/kotlin/com/garfiec/librechat/LibreChatApplication.kt +++ b/app/src/main/kotlin/com/garfiec/librechat/LibreChatApplication.kt @@ -24,6 +24,7 @@ import org.koin.android.ext.android.get import org.koin.android.ext.koin.androidContext import org.koin.android.ext.koin.androidLogger import org.koin.core.context.startKoin +import org.koin.core.logger.Level import co.touchlab.kermit.Logger class LibreChatApplication : Application(), SingletonImageLoader.Factory { @@ -33,7 +34,7 @@ class LibreChatApplication : Application(), SingletonImageLoader.Factory { try { startKoin { if (BuildConfig.DEBUG) { - androidLogger(org.koin.core.logger.Level.DEBUG) + androidLogger(Level.DEBUG) } androidContext(this@LibreChatApplication) allowOverride(false) diff --git a/app/src/main/kotlin/com/garfiec/librechat/MainActivity.kt b/app/src/main/kotlin/com/garfiec/librechat/MainActivity.kt index 9a010d3..7aec9a1 100644 --- a/app/src/main/kotlin/com/garfiec/librechat/MainActivity.kt +++ b/app/src/main/kotlin/com/garfiec/librechat/MainActivity.kt @@ -47,7 +47,7 @@ class MainActivity : ComponentActivity() { private val connectivityObserver: ConnectivityObserver by inject() private val themeDataStore: ThemeDataStore by inject() - private var deepLinkUri by mutableStateOf(null) + private var deepLinkUri by mutableStateOf(null) /** * Incremented each time a share intent arrives. diff --git a/app/src/main/kotlin/com/garfiec/librechat/navigation/TabletLayout.kt b/app/src/main/kotlin/com/garfiec/librechat/navigation/TabletLayout.kt index 148de6b..e63b9f7 100644 --- a/app/src/main/kotlin/com/garfiec/librechat/navigation/TabletLayout.kt +++ b/app/src/main/kotlin/com/garfiec/librechat/navigation/TabletLayout.kt @@ -26,6 +26,7 @@ import androidx.compose.ui.layout.Layout import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.garfiec.librechat.core.model.Banner import com.garfiec.librechat.core.ui.components.BannerDisplay import com.garfiec.librechat.shared.navigation.NavHostViewModel import com.garfiec.librechat.shared.navigation.Navigator @@ -226,7 +227,7 @@ private fun MainContent( navigator: Navigator, navHostViewModel: NavHostViewModel, isInAuthFlow: Boolean, - banners: List, + banners: List, dismissedBannerIds: Set, onToggleDrawer: () -> Unit, modifier: Modifier = Modifier, diff --git a/app/src/test/kotlin/com/garfiec/librechat/KoinGraphVerificationTest.kt b/app/src/test/kotlin/com/garfiec/librechat/KoinGraphVerificationTest.kt index bd86864..c86097c 100644 --- a/app/src/test/kotlin/com/garfiec/librechat/KoinGraphVerificationTest.kt +++ b/app/src/test/kotlin/com/garfiec/librechat/KoinGraphVerificationTest.kt @@ -1,5 +1,8 @@ package com.garfiec.librechat +import android.app.Application +import android.content.Context +import androidx.lifecycle.SavedStateHandle import com.garfiec.librechat.core.common.di.commonModule import com.garfiec.librechat.core.common.network.ConnectivityObserver import com.garfiec.librechat.core.data.datastore.ConfigCacheDataStore @@ -65,7 +68,9 @@ import com.garfiec.librechat.feature.settings.di.settingsModule import com.garfiec.librechat.shared.navigation.sharedAppModule import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope +import java.io.File import org.junit.Test +import org.koin.core.annotation.KoinExperimentalAPI import org.koin.core.module.Module import org.koin.test.verify.verify import kotlin.reflect.KClass @@ -93,14 +98,14 @@ class KoinGraphVerificationTest { * framework types so every module is verified in a single test class. * If a type is renamed or removed, this test will catch it. */ - @OptIn(org.koin.core.annotation.KoinExperimentalAPI::class) + @OptIn(KoinExperimentalAPI::class) @Test fun verifyFullKoinGraph() { val extraTypes = mutableListOf>( // Android framework - android.content.Context::class, - android.app.Application::class, - androidx.lifecycle.SavedStateHandle::class, + Context::class, + Application::class, + SavedStateHandle::class, // core:common provides CoroutineDispatcher::class, CoroutineScope::class, @@ -162,8 +167,8 @@ class KoinGraphVerificationTest { // feature:files platform provides FileReader::class, // Wrappers/DSL types that verify can't resolve via constructor - kotlin.Lazy::class, - java.io.File::class, + Lazy::class, + File::class, ) // Types whose libraries aren't on the app test classpath (transitive diff --git a/core/common/src/androidUnitTest/kotlin/com/garfiec/librechat/core/common/di/CommonModuleVerificationTest.kt b/core/common/src/androidUnitTest/kotlin/com/garfiec/librechat/core/common/di/CommonModuleVerificationTest.kt index 6a322a3..86e2a93 100644 --- a/core/common/src/androidUnitTest/kotlin/com/garfiec/librechat/core/common/di/CommonModuleVerificationTest.kt +++ b/core/common/src/androidUnitTest/kotlin/com/garfiec/librechat/core/common/di/CommonModuleVerificationTest.kt @@ -1,5 +1,7 @@ package com.garfiec.librechat.core.common.di +import android.app.Application +import android.content.Context import org.junit.Test import org.koin.test.verify.verify @@ -8,8 +10,8 @@ class CommonModuleVerificationTest { fun verifyCommonModule() { commonModule.verify( extraTypes = listOf( - android.content.Context::class, - android.app.Application::class, + Context::class, + Application::class, ), ) } diff --git a/core/data/src/androidInstrumentedTest/kotlin/com/garfiec/librechat/core/data/db/RoomMigrationTest.kt b/core/data/src/androidInstrumentedTest/kotlin/com/garfiec/librechat/core/data/db/RoomMigrationTest.kt index a01d0c7..a319c4b 100644 --- a/core/data/src/androidInstrumentedTest/kotlin/com/garfiec/librechat/core/data/db/RoomMigrationTest.kt +++ b/core/data/src/androidInstrumentedTest/kotlin/com/garfiec/librechat/core/data/db/RoomMigrationTest.kt @@ -7,6 +7,7 @@ import androidx.room.testing.MigrationTestHelper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.runBlocking import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -250,7 +251,7 @@ class RoomMigrationTest { ).build() // Verify we can read the migrated data via DAO - val conversation = kotlinx.coroutines.runBlocking { + val conversation = runBlocking { roomDb.conversationDao().getById("conv-api-test") } assertThat(conversation).isNotNull() diff --git a/core/data/src/androidMain/kotlin/com/garfiec/librechat/core/data/di/DataPlatformModule.android.kt b/core/data/src/androidMain/kotlin/com/garfiec/librechat/core/data/di/DataPlatformModule.android.kt index 860a6ba..3341d6a 100644 --- a/core/data/src/androidMain/kotlin/com/garfiec/librechat/core/data/di/DataPlatformModule.android.kt +++ b/core/data/src/androidMain/kotlin/com/garfiec/librechat/core/data/di/DataPlatformModule.android.kt @@ -1,5 +1,6 @@ package com.garfiec.librechat.core.data.di +import android.content.Context import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.preferencesDataStore @@ -17,7 +18,7 @@ import org.koin.core.module.Module import org.koin.dsl.binds import org.koin.dsl.module -private val android.content.Context.settingsDataStore: DataStore by preferencesDataStore( +private val Context.settingsDataStore: DataStore by preferencesDataStore( name = DATASTORE_FILE_NAME, ) diff --git a/core/data/src/androidUnitTest/kotlin/com/garfiec/librechat/core/data/di/DataModuleVerificationTest.kt b/core/data/src/androidUnitTest/kotlin/com/garfiec/librechat/core/data/di/DataModuleVerificationTest.kt index ed510c5..ff3684e 100644 --- a/core/data/src/androidUnitTest/kotlin/com/garfiec/librechat/core/data/di/DataModuleVerificationTest.kt +++ b/core/data/src/androidUnitTest/kotlin/com/garfiec/librechat/core/data/di/DataModuleVerificationTest.kt @@ -1,5 +1,35 @@ package com.garfiec.librechat.core.data.di +import android.app.Application +import android.content.Context +import androidx.datastore.core.DataStore +import androidx.lifecycle.SavedStateHandle +import com.garfiec.librechat.core.common.network.ConnectivityObserver +import com.garfiec.librechat.core.network.api.AgentsApi +import com.garfiec.librechat.core.network.api.ApiKeysApi +import com.garfiec.librechat.core.network.api.AuthApi +import com.garfiec.librechat.core.network.api.BalanceApi +import com.garfiec.librechat.core.network.api.BannerApi +import com.garfiec.librechat.core.network.api.ChatApi +import com.garfiec.librechat.core.network.api.ConfigApi +import com.garfiec.librechat.core.network.api.ConversationsApi +import com.garfiec.librechat.core.network.api.FilesApi +import com.garfiec.librechat.core.network.api.FilesExtApi +import com.garfiec.librechat.core.network.api.KeysApi +import com.garfiec.librechat.core.network.api.McpApi +import com.garfiec.librechat.core.network.api.MemoriesApi +import com.garfiec.librechat.core.network.api.MessagesApi +import com.garfiec.librechat.core.network.api.PresetsApi +import com.garfiec.librechat.core.network.api.PromptsApi +import com.garfiec.librechat.core.network.api.SearchApi +import com.garfiec.librechat.core.network.api.ShareApi +import com.garfiec.librechat.core.network.api.SpeechApi +import com.garfiec.librechat.core.network.api.TagsApi +import com.garfiec.librechat.core.network.api.UserApi +import com.garfiec.librechat.core.network.sse.SseClient +import io.ktor.client.HttpClient +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.serialization.json.Json import org.junit.Test import org.koin.test.verify.verify @@ -8,37 +38,37 @@ class DataModuleVerificationTest { fun verifyDataModule() { dataModule.verify( extraTypes = listOf( - android.content.Context::class, - android.app.Application::class, - androidx.lifecycle.SavedStateHandle::class, - kotlin.Lazy::class, - io.ktor.client.HttpClient::class, - kotlinx.serialization.json.Json::class, - kotlinx.coroutines.CoroutineDispatcher::class, - com.garfiec.librechat.core.common.network.ConnectivityObserver::class, - com.garfiec.librechat.core.network.sse.SseClient::class, - com.garfiec.librechat.core.network.api.AuthApi::class, - com.garfiec.librechat.core.network.api.UserApi::class, - com.garfiec.librechat.core.network.api.ChatApi::class, - com.garfiec.librechat.core.network.api.ConversationsApi::class, - com.garfiec.librechat.core.network.api.MessagesApi::class, - com.garfiec.librechat.core.network.api.FilesApi::class, - com.garfiec.librechat.core.network.api.FilesExtApi::class, - com.garfiec.librechat.core.network.api.AgentsApi::class, - com.garfiec.librechat.core.network.api.PresetsApi::class, - com.garfiec.librechat.core.network.api.PromptsApi::class, - com.garfiec.librechat.core.network.api.TagsApi::class, - com.garfiec.librechat.core.network.api.ShareApi::class, - com.garfiec.librechat.core.network.api.ConfigApi::class, - com.garfiec.librechat.core.network.api.BalanceApi::class, - com.garfiec.librechat.core.network.api.SearchApi::class, - com.garfiec.librechat.core.network.api.KeysApi::class, - com.garfiec.librechat.core.network.api.ApiKeysApi::class, - com.garfiec.librechat.core.network.api.McpApi::class, - com.garfiec.librechat.core.network.api.MemoriesApi::class, - com.garfiec.librechat.core.network.api.SpeechApi::class, - com.garfiec.librechat.core.network.api.BannerApi::class, - androidx.datastore.core.DataStore::class, + Context::class, + Application::class, + SavedStateHandle::class, + Lazy::class, + HttpClient::class, + Json::class, + CoroutineDispatcher::class, + ConnectivityObserver::class, + SseClient::class, + AuthApi::class, + UserApi::class, + ChatApi::class, + ConversationsApi::class, + MessagesApi::class, + FilesApi::class, + FilesExtApi::class, + AgentsApi::class, + PresetsApi::class, + PromptsApi::class, + TagsApi::class, + ShareApi::class, + ConfigApi::class, + BalanceApi::class, + SearchApi::class, + KeysApi::class, + ApiKeysApi::class, + McpApi::class, + MemoriesApi::class, + SpeechApi::class, + BannerApi::class, + DataStore::class, ), ) } diff --git a/core/data/src/commonMain/kotlin/com/garfiec/librechat/core/data/db/dao/MessageDao.kt b/core/data/src/commonMain/kotlin/com/garfiec/librechat/core/data/db/dao/MessageDao.kt index 8cc9d18..e259ef9 100644 --- a/core/data/src/commonMain/kotlin/com/garfiec/librechat/core/data/db/dao/MessageDao.kt +++ b/core/data/src/commonMain/kotlin/com/garfiec/librechat/core/data/db/dao/MessageDao.kt @@ -2,6 +2,7 @@ package com.garfiec.librechat.core.data.db.dao import androidx.room.Dao import androidx.room.Query +import androidx.room.Transaction import androidx.room.Upsert import com.garfiec.librechat.core.data.db.entity.MessageEntity import kotlinx.coroutines.flow.Flow @@ -29,7 +30,7 @@ interface MessageDao { @Query("DELETE FROM messages WHERE conversationId = :conversationId") suspend fun deleteAllForConversation(conversationId: String) - @androidx.room.Transaction + @Transaction suspend fun replaceAllForConversation(conversationId: String, messages: List) { deleteAllForConversation(conversationId) upsertAll(messages) diff --git a/core/data/src/commonMain/kotlin/com/garfiec/librechat/core/data/mapper/ConversationMapper.kt b/core/data/src/commonMain/kotlin/com/garfiec/librechat/core/data/mapper/ConversationMapper.kt index 7d25789..7e2f693 100644 --- a/core/data/src/commonMain/kotlin/com/garfiec/librechat/core/data/mapper/ConversationMapper.kt +++ b/core/data/src/commonMain/kotlin/com/garfiec/librechat/core/data/mapper/ConversationMapper.kt @@ -5,7 +5,9 @@ import com.garfiec.librechat.core.model.Conversation import com.garfiec.librechat.core.model.EModelEndpoint import kotlin.time.Clock import kotlin.time.Instant +import kotlinx.serialization.builtins.ListSerializer import kotlinx.serialization.json.Json +import kotlinx.serialization.serializer private val json = Json { ignoreUnknownKeys = true } @@ -18,7 +20,7 @@ fun Conversation.toEntity(): ConversationEntity = ConversationEntity( model = model, agentId = agentId, isArchived = isArchived, - tags = json.encodeToString(kotlinx.serialization.builtins.ListSerializer(kotlinx.serialization.serializer()), tags), + tags = json.encodeToString(ListSerializer(serializer()), tags), iconURL = iconURL, greeting = greeting, modelParams = null, diff --git a/core/data/src/iosMain/kotlin/com/garfiec/librechat/core/data/datastore/IosTokenDataStore.kt b/core/data/src/iosMain/kotlin/com/garfiec/librechat/core/data/datastore/IosTokenDataStore.kt index be40c51..dc60e68 100644 --- a/core/data/src/iosMain/kotlin/com/garfiec/librechat/core/data/datastore/IosTokenDataStore.kt +++ b/core/data/src/iosMain/kotlin/com/garfiec/librechat/core/data/datastore/IosTokenDataStore.kt @@ -1,6 +1,7 @@ package com.garfiec.librechat.core.data.datastore import io.ktor.client.HttpClient +import kotlinx.cinterop.BetaInteropApi import kotlinx.cinterop.ExperimentalForeignApi import kotlinx.cinterop.alloc import kotlinx.cinterop.memScoped @@ -36,7 +37,7 @@ import platform.Security.kSecReturnData import platform.Security.kSecValueData import co.touchlab.kermit.Logger -@OptIn(ExperimentalForeignApi::class, kotlinx.cinterop.BetaInteropApi::class) +@OptIn(ExperimentalForeignApi::class, BetaInteropApi::class) class IosTokenDataStore( refreshClient: Lazy, ) : CommonTokenDataStore(refreshClient), ServerUrlKeychainFallback { diff --git a/core/data/src/iosMain/kotlin/com/garfiec/librechat/core/data/di/DataPlatformModule.ios.kt b/core/data/src/iosMain/kotlin/com/garfiec/librechat/core/data/di/DataPlatformModule.ios.kt index ae865aa..261f0cb 100644 --- a/core/data/src/iosMain/kotlin/com/garfiec/librechat/core/data/di/DataPlatformModule.ios.kt +++ b/core/data/src/iosMain/kotlin/com/garfiec/librechat/core/data/di/DataPlatformModule.ios.kt @@ -14,16 +14,20 @@ import com.garfiec.librechat.core.data.repository.SessionCacheCleaner import com.garfiec.librechat.core.network.client.SecureTokenStorage import com.garfiec.librechat.core.network.client.TokenManager import io.ktor.client.HttpClient +import kotlinx.cinterop.ExperimentalForeignApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.IO import okio.Path.Companion.toPath import org.koin.core.module.Module import org.koin.dsl.binds import org.koin.dsl.module +import platform.Foundation.NSCachesDirectory import platform.Foundation.NSFileManager import platform.Foundation.NSHomeDirectory +import platform.Foundation.NSSearchPathForDirectoriesInDomains +import platform.Foundation.NSUserDomainMask -@OptIn(kotlinx.cinterop.ExperimentalForeignApi::class) +@OptIn(ExperimentalForeignApi::class) private fun ensureDirectoryExists(path: String) { NSFileManager.defaultManager.createDirectoryAtPath(path, withIntermediateDirectories = true, attributes = null, error = null) } @@ -59,10 +63,10 @@ actual val dataPlatformModule: Module = module { // --- Session Cache Cleaner --- single { - @OptIn(kotlinx.cinterop.ExperimentalForeignApi::class) - val cachePath = platform.Foundation.NSSearchPathForDirectoriesInDomains( - platform.Foundation.NSCachesDirectory, - platform.Foundation.NSUserDomainMask, + @OptIn(ExperimentalForeignApi::class) + val cachePath = NSSearchPathForDirectoriesInDomains( + NSCachesDirectory, + NSUserDomainMask, true, ).firstOrNull() as? String ?: error("Unable to resolve NSCachesDirectory") CommonSessionCacheCleaner(cachePath) diff --git a/core/network/src/androidUnitTest/kotlin/com/garfiec/librechat/core/network/di/NetworkModuleVerificationTest.kt b/core/network/src/androidUnitTest/kotlin/com/garfiec/librechat/core/network/di/NetworkModuleVerificationTest.kt index 38c2f53..d0b6541 100644 --- a/core/network/src/androidUnitTest/kotlin/com/garfiec/librechat/core/network/di/NetworkModuleVerificationTest.kt +++ b/core/network/src/androidUnitTest/kotlin/com/garfiec/librechat/core/network/di/NetworkModuleVerificationTest.kt @@ -1,5 +1,11 @@ package com.garfiec.librechat.core.network.di +import android.app.Application +import android.content.Context +import com.garfiec.librechat.core.network.client.ServerUrlProvider +import com.garfiec.librechat.core.network.client.TokenManager +import io.ktor.client.engine.HttpClientEngine +import io.ktor.client.engine.HttpClientEngineFactory import org.junit.Test import org.koin.test.verify.verify @@ -8,12 +14,12 @@ class NetworkModuleVerificationTest { fun verifyNetworkModule() { networkModule.verify( extraTypes = listOf( - android.content.Context::class, - android.app.Application::class, - io.ktor.client.engine.HttpClientEngine::class, - io.ktor.client.engine.HttpClientEngineFactory::class, - com.garfiec.librechat.core.network.client.TokenManager::class, - com.garfiec.librechat.core.network.client.ServerUrlProvider::class, + Context::class, + Application::class, + HttpClientEngine::class, + HttpClientEngineFactory::class, + TokenManager::class, + ServerUrlProvider::class, ), ) } diff --git a/core/network/src/commonMain/kotlin/com/garfiec/librechat/core/network/client/LibreChatHttpClient.kt b/core/network/src/commonMain/kotlin/com/garfiec/librechat/core/network/client/LibreChatHttpClient.kt index 5ba74be..c487ce1 100644 --- a/core/network/src/commonMain/kotlin/com/garfiec/librechat/core/network/client/LibreChatHttpClient.kt +++ b/core/network/src/commonMain/kotlin/com/garfiec/librechat/core/network/client/LibreChatHttpClient.kt @@ -9,6 +9,7 @@ import io.ktor.client.plugins.HttpTimeout import io.ktor.client.plugins.contentnegotiation.ContentNegotiation import io.ktor.client.plugins.defaultRequest import io.ktor.client.plugins.logging.LogLevel +import io.ktor.client.plugins.logging.Logger as KtorLogger import io.ktor.client.plugins.logging.Logging import io.ktor.client.statement.bodyAsText import io.ktor.http.ContentType @@ -36,7 +37,7 @@ object LibreChatHttpClient { } install(Logging) { - logger = object : io.ktor.client.plugins.logging.Logger { + logger = object : KtorLogger { override fun log(message: String) { val sanitized = message .replace(Regex("Authorization: Bearer [^\\s]+"), "Authorization: Bearer [REDACTED]") diff --git a/core/network/src/commonMain/kotlin/com/garfiec/librechat/core/network/sse/SseClient.kt b/core/network/src/commonMain/kotlin/com/garfiec/librechat/core/network/sse/SseClient.kt index 7ec1a6c..720a8aa 100644 --- a/core/network/src/commonMain/kotlin/com/garfiec/librechat/core/network/sse/SseClient.kt +++ b/core/network/src/commonMain/kotlin/com/garfiec/librechat/core/network/sse/SseClient.kt @@ -11,6 +11,7 @@ import io.ktor.http.ContentType import io.ktor.http.HttpStatusCode import io.ktor.http.isSuccess import io.ktor.http.path +import kotlinx.coroutines.CancellationException import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.first @@ -127,7 +128,7 @@ class SseClient( shouldResume = true } } - } catch (e: kotlinx.coroutines.CancellationException) { + } catch (e: CancellationException) { throw e // Re-throw cancellation — SKIE handles this gracefully } catch (e: Exception) { Logger.e("SSE", e) { "SSE: unhandled exception escaped flow" } diff --git a/core/network/src/commonMain/kotlin/com/garfiec/librechat/core/network/sse/SseEventMapper.kt b/core/network/src/commonMain/kotlin/com/garfiec/librechat/core/network/sse/SseEventMapper.kt index 2ce269c..ad77ab1 100644 --- a/core/network/src/commonMain/kotlin/com/garfiec/librechat/core/network/sse/SseEventMapper.kt +++ b/core/network/src/commonMain/kotlin/com/garfiec/librechat/core/network/sse/SseEventMapper.kt @@ -3,6 +3,7 @@ package com.garfiec.librechat.core.network.sse import com.garfiec.librechat.core.model.Conversation import com.garfiec.librechat.core.model.Message import com.garfiec.librechat.core.model.StreamEvent +import com.garfiec.librechat.core.model.content.MessageContentPart import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonObject @@ -210,7 +211,7 @@ class SseEventMapper(private val json: Json) { aggregatedContent.mapNotNull { element -> try { json.decodeFromJsonElement( - com.garfiec.librechat.core.model.content.MessageContentPart.serializer(), + MessageContentPart.serializer(), element, ) } catch (e: Exception) { diff --git a/core/network/src/commonMain/kotlin/com/garfiec/librechat/core/network/sse/SseLineParser.kt b/core/network/src/commonMain/kotlin/com/garfiec/librechat/core/network/sse/SseLineParser.kt index 39f50cc..29b4a59 100644 --- a/core/network/src/commonMain/kotlin/com/garfiec/librechat/core/network/sse/SseLineParser.kt +++ b/core/network/src/commonMain/kotlin/com/garfiec/librechat/core/network/sse/SseLineParser.kt @@ -3,6 +3,7 @@ package com.garfiec.librechat.core.network.sse import co.touchlab.kermit.Logger import io.ktor.utils.io.ByteReadChannel import io.ktor.utils.io.readUTF8Line +import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import kotlinx.coroutines.withTimeout @@ -21,7 +22,7 @@ class SseLineParser( withTimeout(lineReadTimeoutMs) { channel.readUTF8Line() } - } catch (e: kotlinx.coroutines.TimeoutCancellationException) { + } catch (e: TimeoutCancellationException) { Logger.w("SSE") { "SSE stream stalled: no data for ${lineReadTimeoutMs / 1000}s" } throw SseStreamException("Stream stalled: no data received for ${lineReadTimeoutMs / 1000}s", e) } ?: break diff --git a/feature/agents/src/androidUnitTest/kotlin/com/garfiec/librechat/feature/agents/di/AgentsModuleVerificationTest.kt b/feature/agents/src/androidUnitTest/kotlin/com/garfiec/librechat/feature/agents/di/AgentsModuleVerificationTest.kt index 8ed3021..fb8879b 100644 --- a/feature/agents/src/androidUnitTest/kotlin/com/garfiec/librechat/feature/agents/di/AgentsModuleVerificationTest.kt +++ b/feature/agents/src/androidUnitTest/kotlin/com/garfiec/librechat/feature/agents/di/AgentsModuleVerificationTest.kt @@ -1,5 +1,12 @@ package com.garfiec.librechat.feature.agents.di +import android.app.Application +import android.content.Context +import androidx.lifecycle.SavedStateHandle +import com.garfiec.librechat.core.data.datastore.ServerDataStore +import com.garfiec.librechat.core.data.repository.AgentRepository +import com.garfiec.librechat.core.data.repository.ConfigRepository +import com.garfiec.librechat.core.data.repository.McpRepository import org.junit.Test import org.koin.test.verify.verify @@ -8,13 +15,13 @@ class AgentsModuleVerificationTest { fun verifyAgentsModule() { agentsModule.verify( extraTypes = listOf( - android.content.Context::class, - android.app.Application::class, - androidx.lifecycle.SavedStateHandle::class, - com.garfiec.librechat.core.data.repository.AgentRepository::class, - com.garfiec.librechat.core.data.repository.ConfigRepository::class, - com.garfiec.librechat.core.data.repository.McpRepository::class, - com.garfiec.librechat.core.data.datastore.ServerDataStore::class, + Context::class, + Application::class, + SavedStateHandle::class, + AgentRepository::class, + ConfigRepository::class, + McpRepository::class, + ServerDataStore::class, ), ) } diff --git a/feature/agents/src/commonMain/kotlin/com/garfiec/librechat/feature/agents/components/model/AgentSharingState.kt b/feature/agents/src/commonMain/kotlin/com/garfiec/librechat/feature/agents/components/model/AgentSharingState.kt index 7601b73..133e7ab 100644 --- a/feature/agents/src/commonMain/kotlin/com/garfiec/librechat/feature/agents/components/model/AgentSharingState.kt +++ b/feature/agents/src/commonMain/kotlin/com/garfiec/librechat/feature/agents/components/model/AgentSharingState.kt @@ -1,6 +1,8 @@ package com.garfiec.librechat.feature.agents.components.model -@androidx.compose.runtime.Immutable +import androidx.compose.runtime.Immutable + +@Immutable data class AgentSharingState( val visibility: AgentVisibility = AgentVisibility.PRIVATE, val isCollaborative: Boolean = false, diff --git a/feature/agents/src/commonMain/kotlin/com/garfiec/librechat/feature/agents/components/model/SupportContactState.kt b/feature/agents/src/commonMain/kotlin/com/garfiec/librechat/feature/agents/components/model/SupportContactState.kt index 28b3cd9..8e4d271 100644 --- a/feature/agents/src/commonMain/kotlin/com/garfiec/librechat/feature/agents/components/model/SupportContactState.kt +++ b/feature/agents/src/commonMain/kotlin/com/garfiec/librechat/feature/agents/components/model/SupportContactState.kt @@ -1,6 +1,8 @@ package com.garfiec.librechat.feature.agents.components.model -@androidx.compose.runtime.Immutable +import androidx.compose.runtime.Immutable + +@Immutable data class SupportContactState( val name: String = "", val email: String = "", diff --git a/feature/agents/src/commonMain/kotlin/com/garfiec/librechat/feature/agents/screen/AgentDetailScreen.kt b/feature/agents/src/commonMain/kotlin/com/garfiec/librechat/feature/agents/screen/AgentDetailScreen.kt index 3e4ab1a..6e4528f 100644 --- a/feature/agents/src/commonMain/kotlin/com/garfiec/librechat/feature/agents/screen/AgentDetailScreen.kt +++ b/feature/agents/src/commonMain/kotlin/com/garfiec/librechat/feature/agents/screen/AgentDetailScreen.kt @@ -47,6 +47,7 @@ import androidx.compose.ui.semantics.heading import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.garfiec.librechat.core.model.EModelEndpoint import com.garfiec.librechat.core.ui.components.AvatarImage import com.garfiec.librechat.core.ui.components.endpointIconPainter import com.garfiec.librechat.core.ui.components.ErrorBanner @@ -216,7 +217,7 @@ fun AgentDetailScreen( imageUrl = agent.avatarUrl, size = 80.dp, fallbackText = agent.name, - fallbackIconPainter = endpointIconPainter(com.garfiec.librechat.core.model.EModelEndpoint.AGENTS), + fallbackIconPainter = endpointIconPainter(EModelEndpoint.AGENTS), tintIcon = true, ) diff --git a/feature/agents/src/commonMain/kotlin/com/garfiec/librechat/feature/agents/screen/AgentMarketplaceScreen.kt b/feature/agents/src/commonMain/kotlin/com/garfiec/librechat/feature/agents/screen/AgentMarketplaceScreen.kt index 6895645..c7ebb6b 100644 --- a/feature/agents/src/commonMain/kotlin/com/garfiec/librechat/feature/agents/screen/AgentMarketplaceScreen.kt +++ b/feature/agents/src/commonMain/kotlin/com/garfiec/librechat/feature/agents/screen/AgentMarketplaceScreen.kt @@ -45,6 +45,7 @@ import org.jetbrains.compose.resources.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.garfiec.librechat.core.model.EModelEndpoint import com.garfiec.librechat.core.ui.components.AvatarImage import com.garfiec.librechat.core.ui.components.endpointIconPainter import com.garfiec.librechat.core.ui.components.EmptyState @@ -254,7 +255,7 @@ private fun AgentCard( imageUrl = agent.avatarUrl, size = 40.dp, fallbackText = agent.name, - fallbackIconPainter = endpointIconPainter(com.garfiec.librechat.core.model.EModelEndpoint.AGENTS), + fallbackIconPainter = endpointIconPainter(EModelEndpoint.AGENTS), tintIcon = true, ) } diff --git a/feature/agents/src/commonMain/kotlin/com/garfiec/librechat/feature/agents/viewmodel/AgentEditorViewModel.kt b/feature/agents/src/commonMain/kotlin/com/garfiec/librechat/feature/agents/viewmodel/AgentEditorViewModel.kt index c5f0f63..1f03c28 100644 --- a/feature/agents/src/commonMain/kotlin/com/garfiec/librechat/feature/agents/viewmodel/AgentEditorViewModel.kt +++ b/feature/agents/src/commonMain/kotlin/com/garfiec/librechat/feature/agents/viewmodel/AgentEditorViewModel.kt @@ -39,6 +39,7 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch +import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.floatOrNull @@ -820,7 +821,7 @@ class AgentEditorViewModel( * Parse model_parameters JsonElement into AgentAdvancedSettings. * The backend stores these as: { "temperature": 0.7, "top_p": 0.9, "max_tokens": 4096 } */ - private fun parseModelParameters(params: kotlinx.serialization.json.JsonElement?): AgentAdvancedSettings { + private fun parseModelParameters(params: JsonElement?): AgentAdvancedSettings { if (params == null) return AgentAdvancedSettings() return try { val obj = params.jsonObject @@ -839,7 +840,7 @@ class AgentEditorViewModel( * Returns null if no parameters are set (to avoid sending empty objects). */ private fun buildModelParameters(settings: AgentAdvancedSettings): JsonObject? { - val map = mutableMapOf() + val map = mutableMapOf() settings.temperature?.let { map["temperature"] = JsonPrimitive(it) } settings.topP?.let { map["top_p"] = JsonPrimitive(it) } settings.maxTokens?.let { map["max_tokens"] = JsonPrimitive(it) } @@ -921,13 +922,13 @@ class AgentEditorViewModel( private fun Agent.parseSupportContact(): SupportContactState { val json = supportContact ?: return SupportContactState() return try { - val obj = json as? kotlinx.serialization.json.JsonObject + val obj = json as? JsonObject ?: return SupportContactState() val name = obj["name"] - ?.let { (it as? kotlinx.serialization.json.JsonPrimitive)?.content } + ?.let { (it as? JsonPrimitive)?.content } ?: "" val email = obj["email"] - ?.let { (it as? kotlinx.serialization.json.JsonPrimitive)?.content } + ?.let { (it as? JsonPrimitive)?.content } ?: "" SupportContactState(name = name, email = email) } catch (_: Exception) { diff --git a/feature/auth/src/androidUnitTest/kotlin/com/garfiec/librechat/feature/auth/di/AuthModuleVerificationTest.kt b/feature/auth/src/androidUnitTest/kotlin/com/garfiec/librechat/feature/auth/di/AuthModuleVerificationTest.kt index 0412357..297ea6b 100644 --- a/feature/auth/src/androidUnitTest/kotlin/com/garfiec/librechat/feature/auth/di/AuthModuleVerificationTest.kt +++ b/feature/auth/src/androidUnitTest/kotlin/com/garfiec/librechat/feature/auth/di/AuthModuleVerificationTest.kt @@ -1,5 +1,14 @@ package com.garfiec.librechat.feature.auth.di +import android.app.Application +import android.content.Context +import androidx.lifecycle.SavedStateHandle +import com.garfiec.librechat.core.data.datastore.ServerDataStore +import com.garfiec.librechat.core.data.repository.AuthRepository +import com.garfiec.librechat.core.data.repository.ConfigRepository +import com.garfiec.librechat.core.data.repository.UserRepository +import com.garfiec.librechat.core.network.client.SecureTokenStorage +import com.garfiec.librechat.feature.auth.oauth.OAuthLauncher import org.junit.Test import org.koin.test.verify.verify @@ -8,15 +17,15 @@ class AuthModuleVerificationTest { fun verifyAuthModule() { authModule.verify( extraTypes = listOf( - android.content.Context::class, - android.app.Application::class, - androidx.lifecycle.SavedStateHandle::class, - com.garfiec.librechat.core.data.datastore.ServerDataStore::class, - com.garfiec.librechat.core.data.repository.AuthRepository::class, - com.garfiec.librechat.core.data.repository.ConfigRepository::class, - com.garfiec.librechat.core.data.repository.UserRepository::class, - com.garfiec.librechat.core.network.client.SecureTokenStorage::class, - com.garfiec.librechat.feature.auth.oauth.OAuthLauncher::class, + Context::class, + Application::class, + SavedStateHandle::class, + ServerDataStore::class, + AuthRepository::class, + ConfigRepository::class, + UserRepository::class, + SecureTokenStorage::class, + OAuthLauncher::class, ), ) } diff --git a/feature/chat/src/androidMain/kotlin/com/garfiec/librechat/feature/chat/components/AudioContent.kt b/feature/chat/src/androidMain/kotlin/com/garfiec/librechat/feature/chat/components/AudioContent.kt index 0549f90..5ed9dbb 100644 --- a/feature/chat/src/androidMain/kotlin/com/garfiec/librechat/feature/chat/components/AudioContent.kt +++ b/feature/chat/src/androidMain/kotlin/com/garfiec/librechat/feature/chat/components/AudioContent.kt @@ -2,6 +2,7 @@ package com.garfiec.librechat.feature.chat.components import android.util.Base64 import android.view.ViewGroup +import androidx.annotation.OptIn import androidx.compose.foundation.background import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -22,7 +23,7 @@ import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.source.ProgressiveMediaSource import androidx.media3.ui.PlayerView -@androidx.annotation.OptIn(UnstableApi::class) +@OptIn(UnstableApi::class) @Composable actual fun AudioContent( data: String?, diff --git a/feature/chat/src/androidMain/kotlin/com/garfiec/librechat/feature/chat/components/ChatInput.kt b/feature/chat/src/androidMain/kotlin/com/garfiec/librechat/feature/chat/components/ChatInput.kt index 24cb775..c6ebb07 100644 --- a/feature/chat/src/androidMain/kotlin/com/garfiec/librechat/feature/chat/components/ChatInput.kt +++ b/feature/chat/src/androidMain/kotlin/com/garfiec/librechat/feature/chat/components/ChatInput.kt @@ -1,6 +1,7 @@ package com.garfiec.librechat.feature.chat.components import android.Manifest +import android.content.Context import android.content.pm.PackageManager import android.net.Uri import androidx.activity.compose.rememberLauncherForActivityResult @@ -406,7 +407,7 @@ fun ChatInput( * Stored in the app's cache directory under `camera_photos/` which is * registered in the FileProvider paths XML. */ -private fun createCameraPhotoFile(context: android.content.Context): File { +private fun createCameraPhotoFile(context: Context): File { val cameraDir = File(context.cacheDir, "camera_photos") if (!cameraDir.exists()) { cameraDir.mkdirs() diff --git a/feature/chat/src/androidMain/kotlin/com/garfiec/librechat/feature/chat/components/ContentPartRenderer.kt b/feature/chat/src/androidMain/kotlin/com/garfiec/librechat/feature/chat/components/ContentPartRenderer.kt index 1f22845..3616c56 100644 --- a/feature/chat/src/androidMain/kotlin/com/garfiec/librechat/feature/chat/components/ContentPartRenderer.kt +++ b/feature/chat/src/androidMain/kotlin/com/garfiec/librechat/feature/chat/components/ContentPartRenderer.kt @@ -3,6 +3,7 @@ package com.garfiec.librechat.feature.chat.components import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.layout.LayoutCoordinates +import com.garfiec.librechat.core.model.Attachment import com.garfiec.librechat.core.model.content.MessageContentPart @Composable @@ -12,7 +13,7 @@ actual fun ContentPartRenderer( baseUrl: String, fontSizeMultiplier: Float, useKatex: Boolean, - attachments: List, + attachments: List, showImageDescriptions: Boolean, searchQuery: String?, searchFocusedOccurrence: Int, diff --git a/feature/chat/src/androidMain/kotlin/com/garfiec/librechat/feature/chat/components/LatexBlock.kt b/feature/chat/src/androidMain/kotlin/com/garfiec/librechat/feature/chat/components/LatexBlock.kt index fc59b0c..6652fc5 100644 --- a/feature/chat/src/androidMain/kotlin/com/garfiec/librechat/feature/chat/components/LatexBlock.kt +++ b/feature/chat/src/androidMain/kotlin/com/garfiec/librechat/feature/chat/components/LatexBlock.kt @@ -1,6 +1,7 @@ package com.garfiec.librechat.feature.chat.components import android.annotation.SuppressLint +import android.graphics.Color import android.net.http.SslError import android.view.ViewGroup import android.webkit.SslErrorHandler @@ -178,7 +179,7 @@ private fun KatexLatexBlock( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, ) - setBackgroundColor(android.graphics.Color.TRANSPARENT) + setBackgroundColor(Color.TRANSPARENT) settings.javaScriptEnabled = true settings.allowFileAccess = false settings.allowContentAccess = false @@ -251,7 +252,7 @@ private fun KatexLatexInline( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, ) - setBackgroundColor(android.graphics.Color.TRANSPARENT) + setBackgroundColor(Color.TRANSPARENT) settings.javaScriptEnabled = true settings.allowFileAccess = false settings.allowContentAccess = false diff --git a/feature/chat/src/androidMain/kotlin/com/garfiec/librechat/feature/chat/screen/ChatScreen.kt b/feature/chat/src/androidMain/kotlin/com/garfiec/librechat/feature/chat/screen/ChatScreen.kt index 371cbe1..412ae49 100644 --- a/feature/chat/src/androidMain/kotlin/com/garfiec/librechat/feature/chat/screen/ChatScreen.kt +++ b/feature/chat/src/androidMain/kotlin/com/garfiec/librechat/feature/chat/screen/ChatScreen.kt @@ -5,6 +5,7 @@ import android.app.Activity import android.content.ClipData import android.content.ClipboardManager import android.content.Context +import android.content.Intent import android.content.pm.PackageManager import android.speech.RecognizerIntent import androidx.activity.compose.rememberLauncherForActivityResult @@ -201,7 +202,7 @@ actual fun ChatScreen( } } else { // Device speech recognizer path (Device, Google, or Default without server) - val intent = android.content.Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH).apply { + val intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH).apply { putExtra( RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM, diff --git a/feature/chat/src/androidMain/kotlin/com/garfiec/librechat/feature/chat/util/FileUtils.kt b/feature/chat/src/androidMain/kotlin/com/garfiec/librechat/feature/chat/util/FileUtils.kt index 4827684..ca3dd49 100644 --- a/feature/chat/src/androidMain/kotlin/com/garfiec/librechat/feature/chat/util/FileUtils.kt +++ b/feature/chat/src/androidMain/kotlin/com/garfiec/librechat/feature/chat/util/FileUtils.kt @@ -1,10 +1,13 @@ package com.garfiec.librechat.feature.chat.util import android.content.Context +import android.graphics.Bitmap +import android.graphics.BitmapFactory import android.net.Uri import android.provider.OpenableColumns import android.webkit.MimeTypeMap import co.touchlab.kermit.Logger +import java.io.ByteArrayOutputStream /** * Resolves a human-readable filename from a content URI using the ContentResolver. @@ -224,7 +227,7 @@ internal fun reEncodeImageIfNeeded(bytes: ByteArray, mimeType: String): ReEncode // converts non-matching extensions (e.g. .jpg when imageOutputType=png) // while storing the original MIME type, causing the Anthropic 400 error. return try { - val bitmap = android.graphics.BitmapFactory.decodeByteArray(bytes, 0, bytes.size) + val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size) ?: return null // BitmapFactory can't decode this format // If already PNG with correct magic bytes, skip re-encoding to save CPU/memory @@ -233,8 +236,8 @@ internal fun reEncodeImageIfNeeded(bytes: ByteArray, mimeType: String): ReEncode return null } - val outputStream = java.io.ByteArrayOutputStream() - bitmap.compress(android.graphics.Bitmap.CompressFormat.PNG, 100, outputStream) + val outputStream = ByteArrayOutputStream() + bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream) bitmap.recycle() val encodedBytes = outputStream.toByteArray() diff --git a/feature/chat/src/androidMain/kotlin/com/garfiec/librechat/feature/chat/viewmodel/delegate/FileAttachmentDelegate.kt b/feature/chat/src/androidMain/kotlin/com/garfiec/librechat/feature/chat/viewmodel/delegate/FileAttachmentDelegate.kt index 07a7226..5871d5b 100644 --- a/feature/chat/src/androidMain/kotlin/com/garfiec/librechat/feature/chat/viewmodel/delegate/FileAttachmentDelegate.kt +++ b/feature/chat/src/androidMain/kotlin/com/garfiec/librechat/feature/chat/viewmodel/delegate/FileAttachmentDelegate.kt @@ -1,6 +1,7 @@ package com.garfiec.librechat.feature.chat.viewmodel.delegate import android.content.Context +import android.graphics.BitmapFactory import android.net.Uri import com.garfiec.librechat.core.common.EndpointConstants import com.garfiec.librechat.core.common.result.Result @@ -134,10 +135,10 @@ class FileAttachmentDelegate( var imageHeight: Int? = null if (actualIsImage) { try { - val options = android.graphics.BitmapFactory.Options().apply { + val options = BitmapFactory.Options().apply { inJustDecodeBounds = true } - android.graphics.BitmapFactory.decodeByteArray(uploadBytes, 0, uploadBytes.size, options) + BitmapFactory.decodeByteArray(uploadBytes, 0, uploadBytes.size, options) if (options.outWidth > 0 && options.outHeight > 0) { imageWidth = options.outWidth imageHeight = options.outHeight diff --git a/feature/chat/src/androidMain/kotlin/com/garfiec/librechat/feature/chat/viewmodel/delegate/TextToSpeechDelegate.kt b/feature/chat/src/androidMain/kotlin/com/garfiec/librechat/feature/chat/viewmodel/delegate/TextToSpeechDelegate.kt index 8aa029f..737133f 100644 --- a/feature/chat/src/androidMain/kotlin/com/garfiec/librechat/feature/chat/viewmodel/delegate/TextToSpeechDelegate.kt +++ b/feature/chat/src/androidMain/kotlin/com/garfiec/librechat/feature/chat/viewmodel/delegate/TextToSpeechDelegate.kt @@ -1,6 +1,8 @@ package com.garfiec.librechat.feature.chat.viewmodel.delegate import android.content.Context +import android.media.MediaPlayer +import android.os.Bundle import android.speech.tts.TextToSpeech import android.speech.tts.UtteranceProgressListener import com.garfiec.librechat.core.common.result.Result @@ -9,6 +11,7 @@ import com.garfiec.librechat.core.data.repository.SpeechRepository import com.garfiec.librechat.feature.chat.viewmodel.ChatStateHandle import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch +import java.io.File class TextToSpeechDelegate( private val stateHandle: ChatStateHandle, @@ -20,7 +23,7 @@ class TextToSpeechDelegate( private var ttsEngine: TextToSpeech? = null private var ttsReady = false - private var serverTtsPlayer: android.media.MediaPlayer? = null + private var serverTtsPlayer: MediaPlayer? = null private fun getOrInitTts(onReady: () -> Unit) { if (ttsReady && ttsEngine != null) { @@ -103,7 +106,7 @@ class TextToSpeechDelegate( engine.setVoice(voice) } } - val params = android.os.Bundle() + val params = Bundle() engine.speak(text, TextToSpeech.QUEUE_FLUSH, params, messageId) } } @@ -113,12 +116,12 @@ class TextToSpeechDelegate( is Result.Success -> { try { val audioBytes = result.data - val tempFile = java.io.File.createTempFile("tts_", ".mp3", appContext.cacheDir) + val tempFile = File.createTempFile("tts_", ".mp3", appContext.cacheDir) tempFile.deleteOnExit() tempFile.writeBytes(audioBytes) serverTtsPlayer?.release() - serverTtsPlayer = android.media.MediaPlayer().apply { + serverTtsPlayer = MediaPlayer().apply { setDataSource(tempFile.absolutePath) setOnCompletionListener { it.release() diff --git a/feature/chat/src/androidUnitTest/kotlin/com/garfiec/librechat/feature/chat/di/ChatModuleVerificationTest.kt b/feature/chat/src/androidUnitTest/kotlin/com/garfiec/librechat/feature/chat/di/ChatModuleVerificationTest.kt index 69fae1a..804b381 100644 --- a/feature/chat/src/androidUnitTest/kotlin/com/garfiec/librechat/feature/chat/di/ChatModuleVerificationTest.kt +++ b/feature/chat/src/androidUnitTest/kotlin/com/garfiec/librechat/feature/chat/di/ChatModuleVerificationTest.kt @@ -1,5 +1,24 @@ package com.garfiec.librechat.feature.chat.di +import android.app.Application +import android.content.Context +import androidx.lifecycle.SavedStateHandle +import com.garfiec.librechat.core.common.network.ConnectivityObserver +import com.garfiec.librechat.core.data.datastore.ServerDataStore +import com.garfiec.librechat.core.data.datastore.SettingsDataStore +import com.garfiec.librechat.core.data.repository.AgentRepository +import com.garfiec.librechat.core.data.repository.ChatRepository +import com.garfiec.librechat.core.data.repository.ConfigRepository +import com.garfiec.librechat.core.data.repository.ConversationRepository +import com.garfiec.librechat.core.data.repository.DraftRepository +import com.garfiec.librechat.core.data.repository.FileRepository +import com.garfiec.librechat.core.data.repository.McpRepository +import com.garfiec.librechat.core.data.repository.MessageRepository +import com.garfiec.librechat.core.data.repository.PresetRepository +import com.garfiec.librechat.core.data.repository.PromptRepository +import com.garfiec.librechat.core.data.repository.ShareRepository +import com.garfiec.librechat.core.data.repository.SpeechRepository +import com.garfiec.librechat.core.data.repository.UserRepository import org.junit.Test import org.koin.test.verify.verify @@ -8,25 +27,25 @@ class ChatModuleVerificationTest { fun verifyChatModule() { chatModule.verify( extraTypes = listOf( - android.content.Context::class, - android.app.Application::class, - androidx.lifecycle.SavedStateHandle::class, - com.garfiec.librechat.core.data.repository.AgentRepository::class, - com.garfiec.librechat.core.data.repository.ChatRepository::class, - com.garfiec.librechat.core.data.repository.MessageRepository::class, - com.garfiec.librechat.core.data.repository.ConfigRepository::class, - com.garfiec.librechat.core.data.repository.ConversationRepository::class, - com.garfiec.librechat.core.data.repository.DraftRepository::class, - com.garfiec.librechat.core.data.repository.FileRepository::class, - com.garfiec.librechat.core.data.repository.PresetRepository::class, - com.garfiec.librechat.core.data.repository.PromptRepository::class, - com.garfiec.librechat.core.data.repository.ShareRepository::class, - com.garfiec.librechat.core.data.repository.SpeechRepository::class, - com.garfiec.librechat.core.data.repository.McpRepository::class, - com.garfiec.librechat.core.data.repository.UserRepository::class, - com.garfiec.librechat.core.common.network.ConnectivityObserver::class, - com.garfiec.librechat.core.data.datastore.ServerDataStore::class, - com.garfiec.librechat.core.data.datastore.SettingsDataStore::class, + Context::class, + Application::class, + SavedStateHandle::class, + AgentRepository::class, + ChatRepository::class, + MessageRepository::class, + ConfigRepository::class, + ConversationRepository::class, + DraftRepository::class, + FileRepository::class, + PresetRepository::class, + PromptRepository::class, + ShareRepository::class, + SpeechRepository::class, + McpRepository::class, + UserRepository::class, + ConnectivityObserver::class, + ServerDataStore::class, + SettingsDataStore::class, ), ) } diff --git a/feature/chat/src/commonMain/kotlin/com/garfiec/librechat/feature/chat/components/AttachmentChipsRow.kt b/feature/chat/src/commonMain/kotlin/com/garfiec/librechat/feature/chat/components/AttachmentChipsRow.kt index 09b62ff..0167dca 100644 --- a/feature/chat/src/commonMain/kotlin/com/garfiec/librechat/feature/chat/components/AttachmentChipsRow.kt +++ b/feature/chat/src/commonMain/kotlin/com/garfiec/librechat/feature/chat/components/AttachmentChipsRow.kt @@ -48,6 +48,7 @@ import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.role import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import coil3.compose.AsyncImage @@ -203,7 +204,7 @@ private fun FilesChip( @Composable private fun ToolIndicatorChip( label: String, - icon: androidx.compose.ui.graphics.vector.ImageVector, + icon: ImageVector, semanticDescription: String, ) { AssistChip( diff --git a/feature/chat/src/commonMain/kotlin/com/garfiec/librechat/feature/chat/components/LandingContent.kt b/feature/chat/src/commonMain/kotlin/com/garfiec/librechat/feature/chat/components/LandingContent.kt index 4a92abb..c5aea36 100644 --- a/feature/chat/src/commonMain/kotlin/com/garfiec/librechat/feature/chat/components/LandingContent.kt +++ b/feature/chat/src/commonMain/kotlin/com/garfiec/librechat/feature/chat/components/LandingContent.kt @@ -29,6 +29,7 @@ import androidx.compose.ui.unit.dp import librechat_mobile.feature.chat.generated.resources.Res import librechat_mobile.feature.chat.generated.resources.* import kotlin.time.Clock +import kotlinx.datetime.DayOfWeek import kotlinx.datetime.TimeZone import kotlinx.datetime.toLocalDateTime @@ -114,7 +115,7 @@ private fun getTimeBasedGreeting(): String { val now = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()) val hour = now.hour val dayOfWeek = now.dayOfWeek - val isWeekend = dayOfWeek == kotlinx.datetime.DayOfWeek.SATURDAY || dayOfWeek == kotlinx.datetime.DayOfWeek.SUNDAY + val isWeekend = dayOfWeek == DayOfWeek.SATURDAY || dayOfWeek == DayOfWeek.SUNDAY if (isWeekend) { return when { diff --git a/feature/chat/src/commonMain/kotlin/com/garfiec/librechat/feature/chat/components/PlatformChatComponents.kt b/feature/chat/src/commonMain/kotlin/com/garfiec/librechat/feature/chat/components/PlatformChatComponents.kt index 5b4d1eb..defbf9f 100644 --- a/feature/chat/src/commonMain/kotlin/com/garfiec/librechat/feature/chat/components/PlatformChatComponents.kt +++ b/feature/chat/src/commonMain/kotlin/com/garfiec/librechat/feature/chat/components/PlatformChatComponents.kt @@ -4,6 +4,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.layout.LayoutCoordinates import com.garfiec.librechat.core.common.ChatLayoutConstants +import com.garfiec.librechat.core.model.Attachment import com.garfiec.librechat.core.model.Message import com.garfiec.librechat.core.model.content.MessageContentPart @@ -56,7 +57,7 @@ expect fun ContentPartRenderer( baseUrl: String = "", fontSizeMultiplier: Float = 1.0f, useKatex: Boolean = false, - attachments: List = emptyList(), + attachments: List = emptyList(), showImageDescriptions: Boolean = true, searchQuery: String? = null, searchFocusedOccurrence: Int = -1, diff --git a/feature/chat/src/commonMain/kotlin/com/garfiec/librechat/feature/chat/viewmodel/ChatUiState.kt b/feature/chat/src/commonMain/kotlin/com/garfiec/librechat/feature/chat/viewmodel/ChatUiState.kt index 82eabc6..82dbd41 100644 --- a/feature/chat/src/commonMain/kotlin/com/garfiec/librechat/feature/chat/viewmodel/ChatUiState.kt +++ b/feature/chat/src/commonMain/kotlin/com/garfiec/librechat/feature/chat/viewmodel/ChatUiState.kt @@ -1,12 +1,15 @@ package com.garfiec.librechat.feature.chat.viewmodel +import androidx.compose.runtime.Immutable import com.garfiec.librechat.core.common.ChatLayoutConstants import com.garfiec.librechat.core.common.EndpointConstants import com.garfiec.librechat.core.common.ToolConstants import com.garfiec.librechat.core.data.datastore.ChatFontSize import com.garfiec.librechat.core.data.datastore.LatexRenderer import com.garfiec.librechat.core.model.Agent +import com.garfiec.librechat.core.model.Attachment import com.garfiec.librechat.core.model.EndpointConfig +import com.garfiec.librechat.core.model.Message import com.garfiec.librechat.core.ui.components.ModelParameters import com.garfiec.librechat.feature.chat.model.McpServerDisplayData import com.garfiec.librechat.feature.chat.model.PresetDisplayData @@ -20,7 +23,7 @@ enum class ChatScreenState { LANDING, LOADING, ACTIVE } * Exposed as a single [StateFlow] to reduce the number of individual subscriptions * in the UI layer. */ -@androidx.compose.runtime.Immutable +@Immutable data class ChatPreferences( val showImageDescriptions: Boolean = false, val dismissKeyboardOnSend: Boolean = false, @@ -38,19 +41,19 @@ data class ChatPreferences( * @param messageIndex Index into [ChatUiState.displayMessages] containing this occurrence. * @param occurrenceInMessage 0-based index of this occurrence within the message text. */ -@androidx.compose.runtime.Immutable +@Immutable data class SearchMatch( val messageIndex: Int, val occurrenceInMessage: Int, ) -@androidx.compose.runtime.Immutable +@Immutable data class RetryInfo( val attempt: Int, val maxAttempts: Int, ) -@androidx.compose.runtime.Immutable +@Immutable data class ActiveToolCall( val id: String, val name: String, @@ -58,10 +61,10 @@ data class ActiveToolCall( val output: String? = null, ) -@androidx.compose.runtime.Immutable +@Immutable data class ChatUiState( val screenState: ChatScreenState = ChatScreenState.LANDING, - val messages: List = emptyList(), + val messages: List = emptyList(), val displayMessages: List = emptyList(), val activeBranches: Map = emptyMap(), val inputText: String = "", @@ -71,7 +74,7 @@ data class ChatUiState( /** Attachments received during SSE streaming (e.g., tool-generated images). * Cleared when streaming ends. Used to provide attachment context while the * final message (with full attachments) has not yet been persisted to Room. */ - val streamingAttachments: List = emptyList(), + val streamingAttachments: List = emptyList(), val selectedModel: String? = null, val selectedEndpoint: String = EndpointConstants.AGENTS, val endpointConfigs: Map = emptyMap(), diff --git a/feature/chat/src/commonMain/kotlin/com/garfiec/librechat/feature/chat/viewmodel/ChatViewModel.kt b/feature/chat/src/commonMain/kotlin/com/garfiec/librechat/feature/chat/viewmodel/ChatViewModel.kt index 6310000..a45d27e 100644 --- a/feature/chat/src/commonMain/kotlin/com/garfiec/librechat/feature/chat/viewmodel/ChatViewModel.kt +++ b/feature/chat/src/commonMain/kotlin/com/garfiec/librechat/feature/chat/viewmodel/ChatViewModel.kt @@ -22,7 +22,9 @@ import com.garfiec.librechat.core.data.repository.PresetRepository import com.garfiec.librechat.core.data.repository.PromptRepository import com.garfiec.librechat.core.data.repository.ShareRepository import com.garfiec.librechat.core.data.repository.UserRepository +import com.garfiec.librechat.core.model.Attachment import com.garfiec.librechat.core.model.EModelEndpoint +import com.garfiec.librechat.core.model.Message import com.garfiec.librechat.core.model.Preset import com.garfiec.librechat.core.model.StreamEvent import com.garfiec.librechat.core.model.request.EphemeralAgent @@ -38,6 +40,7 @@ import com.garfiec.librechat.feature.chat.viewmodel.delegate.ModelSelectionDeleg import com.garfiec.librechat.feature.chat.viewmodel.delegate.PlatformDelegateFactory import com.garfiec.librechat.feature.chat.viewmodel.delegate.PresetPromptDelegate import com.garfiec.librechat.feature.chat.viewmodel.delegate.toSerialName +import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow @@ -467,7 +470,7 @@ class ChatViewModel( val messageText = text.ifBlank { if (hasFiles) "" else return } - val optimisticMessage = com.garfiec.librechat.core.model.Message( + val optimisticMessage = Message( messageId = Uuid.random().toString(), conversationId = conversationId ?: "", parentMessageId = lastMessageId, @@ -637,7 +640,7 @@ class ChatViewModel( } } - private fun editUserMessage(originalMessage: com.garfiec.librechat.core.model.Message, newText: String) { + private fun editUserMessage(originalMessage: Message, newText: String) { val parentMessageId = originalMessage.parentMessageId isEditOrRegenerate = true @@ -665,7 +668,7 @@ class ChatViewModel( } } - private fun editAiMessage(aiMessage: com.garfiec.librechat.core.model.Message, newText: String) { + private fun editAiMessage(aiMessage: Message, newText: String) { val parentUserMessage = _uiState.value.messages.find { it.messageId == aiMessage.parentMessageId } ?: return @@ -750,7 +753,7 @@ class ChatViewModel( private suspend fun collectStreamSafely(stream: Flow) { try { stream.collect { event -> handleStreamEvent(event) } - } catch (e: kotlinx.coroutines.CancellationException) { + } catch (e: CancellationException) { throw e // Never swallow cancellation } catch (e: Exception) { Logger.e(e) { "Stream collection failed" } @@ -1026,7 +1029,7 @@ class ChatViewModel( } } is StreamEvent.AttachmentCreated -> { - val attachment = com.garfiec.librechat.core.model.Attachment( + val attachment = Attachment( fileId = event.fileId, filename = event.filename, filepath = event.filepath, diff --git a/feature/chat/src/iosMain/kotlin/com/garfiec/librechat/feature/chat/components/PlatformChatComponents.ios.kt b/feature/chat/src/iosMain/kotlin/com/garfiec/librechat/feature/chat/components/PlatformChatComponents.ios.kt index 93e0a80..321eeec 100644 --- a/feature/chat/src/iosMain/kotlin/com/garfiec/librechat/feature/chat/components/PlatformChatComponents.ios.kt +++ b/feature/chat/src/iosMain/kotlin/com/garfiec/librechat/feature/chat/components/PlatformChatComponents.ios.kt @@ -42,9 +42,11 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.layout.LayoutCoordinates import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight @@ -58,6 +60,7 @@ import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties import com.garfiec.librechat.core.common.ChatLayoutConstants +import com.garfiec.librechat.core.model.Attachment import com.garfiec.librechat.core.model.Message import com.garfiec.librechat.core.model.content.MessageContentPart import com.garfiec.librechat.core.ui.components.AvatarImage @@ -334,7 +337,7 @@ private fun TwoSidedBubble( @Composable actual fun ContentPartRenderer( part: MessageContentPart, modifier: Modifier, baseUrl: String, fontSizeMultiplier: Float, - useKatex: Boolean, attachments: List, + useKatex: Boolean, attachments: List, showImageDescriptions: Boolean, searchQuery: String?, searchFocusedOccurrence: Int, onFocusedOccurrencePositioned: ((LayoutCoordinates) -> Unit)?, ) { @@ -624,7 +627,7 @@ private fun IosMarkdownTable( @Composable private fun TableCell( - text: String, style: androidx.compose.ui.text.TextStyle, color: androidx.compose.ui.graphics.Color, + text: String, style: TextStyle, color: Color, alignment: TableCellAlignment, cellWidth: Dp, paddingH: Dp, paddingV: Dp, modifier: Modifier = Modifier, ) { Box( @@ -646,7 +649,7 @@ private fun TableCell( } } -private fun androidx.compose.ui.text.TextStyle.scale(m: Float): androidx.compose.ui.text.TextStyle { +private fun TextStyle.scale(m: Float): TextStyle { if (m == 1.0f) return this return copy( fontSize = if (fontSize.isSpecified) (fontSize.value * m).sp else fontSize, diff --git a/feature/chat/src/iosMain/kotlin/com/garfiec/librechat/feature/chat/components/PlatformMediaComponents.ios.kt b/feature/chat/src/iosMain/kotlin/com/garfiec/librechat/feature/chat/components/PlatformMediaComponents.ios.kt index c75ee9f..42efc8a 100644 --- a/feature/chat/src/iosMain/kotlin/com/garfiec/librechat/feature/chat/components/PlatformMediaComponents.ios.kt +++ b/feature/chat/src/iosMain/kotlin/com/garfiec/librechat/feature/chat/components/PlatformMediaComponents.ios.kt @@ -52,8 +52,10 @@ import co.touchlab.kermit.Logger import coil3.compose.AsyncImage import kotlinx.cinterop.BetaInteropApi import kotlinx.cinterop.ExperimentalForeignApi +import kotlinx.cinterop.cValue import kotlinx.coroutines.delay import platform.AVFAudio.AVAudioPlayer +import platform.AVFoundation.AVLayerVideoGravityResizeAspect import platform.AVFoundation.AVPlayer import platform.AVFoundation.AVPlayerItem import platform.AVFoundation.AVPlayerLayer @@ -76,7 +78,10 @@ import platform.Foundation.writeToFile import kotlinx.cinterop.addressOf import kotlinx.cinterop.usePinned import platform.QuartzCore.CALayer +import platform.UIKit.UIColor import platform.UIKit.UIView +import platform.UIKit.UIViewController +import platform.UIKit.UIWindowScene import platform.WebKit.WKNavigation import platform.WebKit.WKNavigationDelegateProtocol import platform.WebKit.WKWebView @@ -385,7 +390,7 @@ actual fun VideoContent( factory = { val view = UIView() val playerLayer = AVPlayerLayer.playerLayerWithPlayer(player) - playerLayer.videoGravity = platform.AVFoundation.AVLayerVideoGravityResizeAspect + playerLayer.videoGravity = AVLayerVideoGravityResizeAspect view.layer.addSublayer(playerLayer) view }, @@ -473,8 +478,8 @@ private fun KatexWebView( .height(contentHeight), factory = { val config = WKWebViewConfiguration() - val webView = WKWebView(frame = kotlinx.cinterop.cValue { }, configuration = config) - val nativeBg = platform.UIKit.UIColor( + val webView = WKWebView(frame = cValue { }, configuration = config) + val nativeBg = UIColor( red = bgComposeColor.red.toDouble(), green = bgComposeColor.green.toDouble(), blue = bgComposeColor.blue.toDouble(), @@ -502,7 +507,7 @@ private fun KatexWebView( ) } -private fun colorToCssHex(color: androidx.compose.ui.graphics.Color): String { +private fun colorToCssHex(color: Color): String { val r = (color.red * 255).toInt() val g = (color.green * 255).toInt() val b = (color.blue * 255).toInt() @@ -690,7 +695,7 @@ private fun MermaidWKWebView( modifier = modifier, factory = { val config = WKWebViewConfiguration() - val webView = WKWebView(frame = kotlinx.cinterop.cValue { }, configuration = config) + val webView = WKWebView(frame = cValue { }, configuration = config) webView.setOpaque(false) webView.loadHTMLString(html, baseURL = NSURL.URLWithString("https://cdn.jsdelivr.net")) webView @@ -823,8 +828,8 @@ actual fun FullscreenImageViewer( } } -private fun getRootViewController(): platform.UIKit.UIViewController? { - val scene = UIApplication.sharedApplication.connectedScenes.firstOrNull() as? platform.UIKit.UIWindowScene +private fun getRootViewController(): UIViewController? { + val scene = UIApplication.sharedApplication.connectedScenes.firstOrNull() as? UIWindowScene return scene?.keyWindow?.rootViewController } diff --git a/feature/chat/src/iosMain/kotlin/com/garfiec/librechat/feature/chat/components/artifact/PlatformArtifactPanel.ios.kt b/feature/chat/src/iosMain/kotlin/com/garfiec/librechat/feature/chat/components/artifact/PlatformArtifactPanel.ios.kt index cba9319..601dec6 100644 --- a/feature/chat/src/iosMain/kotlin/com/garfiec/librechat/feature/chat/components/artifact/PlatformArtifactPanel.ios.kt +++ b/feature/chat/src/iosMain/kotlin/com/garfiec/librechat/feature/chat/components/artifact/PlatformArtifactPanel.ios.kt @@ -33,6 +33,7 @@ import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties import com.garfiec.librechat.feature.chat.components.shareArtifact import kotlinx.cinterop.ExperimentalForeignApi +import kotlinx.cinterop.cValue import platform.Foundation.NSURL import platform.WebKit.WKWebView import platform.WebKit.WKWebViewConfiguration @@ -131,7 +132,7 @@ actual fun ArtifactPanel( defaultWebpagePreferences.allowsContentJavaScript = true } val webView = WKWebView( - frame = kotlinx.cinterop.cValue { }, + frame = cValue { }, configuration = config, ) webView.setOpaque(false) diff --git a/feature/chat/src/iosMain/kotlin/com/garfiec/librechat/feature/chat/viewmodel/delegate/IosTts.kt b/feature/chat/src/iosMain/kotlin/com/garfiec/librechat/feature/chat/viewmodel/delegate/IosTts.kt index 2dbf3fb..6d213d4 100644 --- a/feature/chat/src/iosMain/kotlin/com/garfiec/librechat/feature/chat/viewmodel/delegate/IosTts.kt +++ b/feature/chat/src/iosMain/kotlin/com/garfiec/librechat/feature/chat/viewmodel/delegate/IosTts.kt @@ -4,6 +4,7 @@ import com.garfiec.librechat.core.common.result.Result import com.garfiec.librechat.core.data.datastore.SettingsDataStore import com.garfiec.librechat.core.data.repository.SpeechRepository import com.garfiec.librechat.feature.chat.viewmodel.ChatStateHandle +import kotlinx.cinterop.ObjCSignatureOverride import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import platform.AVFAudio.AVSpeechBoundary @@ -29,7 +30,7 @@ class IosTts( private val synthesizer = AVSpeechSynthesizer() private val delegate = object : NSObject(), AVSpeechSynthesizerDelegateProtocol { - @kotlinx.cinterop.ObjCSignatureOverride + @ObjCSignatureOverride override fun speechSynthesizer( synthesizer: AVSpeechSynthesizer, didFinishSpeechUtterance: AVSpeechUtterance, @@ -37,7 +38,7 @@ class IosTts( stateHandle.update { copy(currentlyReadingMessageId = null) } } - @kotlinx.cinterop.ObjCSignatureOverride + @ObjCSignatureOverride override fun speechSynthesizer( synthesizer: AVSpeechSynthesizer, didCancelSpeechUtterance: AVSpeechUtterance, diff --git a/feature/conversations/src/androidUnitTest/kotlin/com/garfiec/librechat/feature/conversations/di/ConversationsModuleVerificationTest.kt b/feature/conversations/src/androidUnitTest/kotlin/com/garfiec/librechat/feature/conversations/di/ConversationsModuleVerificationTest.kt index a47859e..007f513 100644 --- a/feature/conversations/src/androidUnitTest/kotlin/com/garfiec/librechat/feature/conversations/di/ConversationsModuleVerificationTest.kt +++ b/feature/conversations/src/androidUnitTest/kotlin/com/garfiec/librechat/feature/conversations/di/ConversationsModuleVerificationTest.kt @@ -1,5 +1,16 @@ package com.garfiec.librechat.feature.conversations.di +import android.app.Application +import android.content.Context +import androidx.lifecycle.SavedStateHandle +import com.garfiec.librechat.core.data.datastore.ServerDataStore +import com.garfiec.librechat.core.data.datastore.SettingsDataStore +import com.garfiec.librechat.core.data.repository.ConfigRepository +import com.garfiec.librechat.core.data.repository.ConversationRepository +import com.garfiec.librechat.core.data.repository.MessageRepository +import com.garfiec.librechat.core.data.repository.SearchRepository +import com.garfiec.librechat.core.data.repository.ShareRepository +import com.garfiec.librechat.core.data.repository.TagRepository import org.junit.Test import org.koin.test.verify.verify @@ -8,17 +19,17 @@ class ConversationsModuleVerificationTest { fun verifyConversationsModule() { conversationsModule.verify( extraTypes = listOf( - android.content.Context::class, - android.app.Application::class, - androidx.lifecycle.SavedStateHandle::class, - com.garfiec.librechat.core.data.repository.ConversationRepository::class, - com.garfiec.librechat.core.data.repository.MessageRepository::class, - com.garfiec.librechat.core.data.repository.TagRepository::class, - com.garfiec.librechat.core.data.repository.ShareRepository::class, - com.garfiec.librechat.core.data.repository.SearchRepository::class, - com.garfiec.librechat.core.data.repository.ConfigRepository::class, - com.garfiec.librechat.core.data.datastore.ServerDataStore::class, - com.garfiec.librechat.core.data.datastore.SettingsDataStore::class, + Context::class, + Application::class, + SavedStateHandle::class, + ConversationRepository::class, + MessageRepository::class, + TagRepository::class, + ShareRepository::class, + SearchRepository::class, + ConfigRepository::class, + ServerDataStore::class, + SettingsDataStore::class, ), ) } diff --git a/feature/conversations/src/commonMain/kotlin/com/garfiec/librechat/feature/conversations/components/ConversationActions.kt b/feature/conversations/src/commonMain/kotlin/com/garfiec/librechat/feature/conversations/components/ConversationActions.kt index c470844..6e041ca 100644 --- a/feature/conversations/src/commonMain/kotlin/com/garfiec/librechat/feature/conversations/components/ConversationActions.kt +++ b/feature/conversations/src/commonMain/kotlin/com/garfiec/librechat/feature/conversations/components/ConversationActions.kt @@ -33,6 +33,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import org.jetbrains.compose.resources.stringResource import androidx.compose.ui.unit.dp @@ -185,8 +186,8 @@ private fun ActionRow( icon: ImageVector, label: String, onClick: () -> Unit, - iconTint: androidx.compose.ui.graphics.Color = MaterialTheme.colorScheme.onSurface, - labelColor: androidx.compose.ui.graphics.Color = MaterialTheme.colorScheme.onSurface, + iconTint: Color = MaterialTheme.colorScheme.onSurface, + labelColor: Color = MaterialTheme.colorScheme.onSurface, ) { Row( modifier = Modifier diff --git a/feature/conversations/src/iosMain/kotlin/com/garfiec/librechat/feature/conversations/platform/PlatformActions.ios.kt b/feature/conversations/src/iosMain/kotlin/com/garfiec/librechat/feature/conversations/platform/PlatformActions.ios.kt index 3155660..526229f 100644 --- a/feature/conversations/src/iosMain/kotlin/com/garfiec/librechat/feature/conversations/platform/PlatformActions.ios.kt +++ b/feature/conversations/src/iosMain/kotlin/com/garfiec/librechat/feature/conversations/platform/PlatformActions.ios.kt @@ -10,7 +10,13 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import co.touchlab.kermit.Logger +import platform.Foundation.NSTimer +import platform.UIKit.UIAlertController +import platform.UIKit.UIAlertControllerStyleAlert +import platform.UIKit.UIApplication import platform.UIKit.UIPasteboard +import platform.UIKit.UIWindow +import platform.UIKit.UIWindowScene actual fun copyToClipboard(text: String, label: String) { UIPasteboard.generalPasteboard.string = text @@ -20,19 +26,19 @@ actual fun showToast(message: String) { // iOS doesn't have native Toast — show a brief alert-style overlay. Logger.i("Toast") { message } // Use UIAlertController as a lightweight toast replacement - val scene = platform.UIKit.UIApplication.sharedApplication.connectedScenes - .firstOrNull() as? platform.UIKit.UIWindowScene + val scene = UIApplication.sharedApplication.connectedScenes + .firstOrNull() as? UIWindowScene val rootVc = scene?.windows?.firstOrNull { - (it as? platform.UIKit.UIWindow)?.isKeyWindow() == true - }?.let { (it as platform.UIKit.UIWindow).rootViewController } ?: return - val alert = platform.UIKit.UIAlertController.alertControllerWithTitle( + (it as? UIWindow)?.isKeyWindow() == true + }?.let { (it as UIWindow).rootViewController } ?: return + val alert = UIAlertController.alertControllerWithTitle( title = null, message = message, - preferredStyle = platform.UIKit.UIAlertControllerStyleAlert, + preferredStyle = UIAlertControllerStyleAlert, ) rootVc.presentViewController(alert, animated = true, completion = null) // Auto-dismiss after 1.5 seconds - platform.Foundation.NSTimer.scheduledTimerWithTimeInterval( + NSTimer.scheduledTimerWithTimeInterval( interval = 1.5, repeats = false, ) { diff --git a/feature/files/src/androidMain/kotlin/com/garfiec/librechat/feature/files/platform/PdfPreview.android.kt b/feature/files/src/androidMain/kotlin/com/garfiec/librechat/feature/files/platform/PdfPreview.android.kt index 859a3a9..315d07e 100644 --- a/feature/files/src/androidMain/kotlin/com/garfiec/librechat/feature/files/platform/PdfPreview.android.kt +++ b/feature/files/src/androidMain/kotlin/com/garfiec/librechat/feature/files/platform/PdfPreview.android.kt @@ -1,6 +1,7 @@ package com.garfiec.librechat.feature.files.platform import android.graphics.Bitmap +import android.graphics.Color import android.graphics.pdf.PdfRenderer import android.os.ParcelFileDescriptor import androidx.compose.foundation.Image @@ -115,7 +116,7 @@ actual fun PdfPreview( bitmapHeight, Bitmap.Config.ARGB_8888, ) - bitmap.eraseColor(android.graphics.Color.WHITE) + bitmap.eraseColor(Color.WHITE) page.render( bitmap, null, diff --git a/feature/files/src/androidUnitTest/kotlin/com/garfiec/librechat/feature/files/di/FilesModuleVerificationTest.kt b/feature/files/src/androidUnitTest/kotlin/com/garfiec/librechat/feature/files/di/FilesModuleVerificationTest.kt index 6ee4176..5223e4e 100644 --- a/feature/files/src/androidUnitTest/kotlin/com/garfiec/librechat/feature/files/di/FilesModuleVerificationTest.kt +++ b/feature/files/src/androidUnitTest/kotlin/com/garfiec/librechat/feature/files/di/FilesModuleVerificationTest.kt @@ -1,5 +1,11 @@ package com.garfiec.librechat.feature.files.di +import android.app.Application +import android.content.Context +import androidx.lifecycle.SavedStateHandle +import com.garfiec.librechat.core.data.datastore.ServerDataStore +import com.garfiec.librechat.core.data.repository.FileRepository +import com.garfiec.librechat.feature.files.platform.FileReader import org.junit.Test import org.koin.test.verify.verify @@ -8,12 +14,12 @@ class FilesModuleVerificationTest { fun verifyFilesModule() { filesModule.verify( extraTypes = listOf( - android.content.Context::class, - android.app.Application::class, - androidx.lifecycle.SavedStateHandle::class, - com.garfiec.librechat.core.data.repository.FileRepository::class, - com.garfiec.librechat.core.data.datastore.ServerDataStore::class, - com.garfiec.librechat.feature.files.platform.FileReader::class, + Context::class, + Application::class, + SavedStateHandle::class, + FileRepository::class, + ServerDataStore::class, + FileReader::class, ), ) } diff --git a/feature/files/src/commonMain/kotlin/com/garfiec/librechat/feature/files/viewmodel/FilesViewModel.kt b/feature/files/src/commonMain/kotlin/com/garfiec/librechat/feature/files/viewmodel/FilesViewModel.kt index 802f4aa..6a66e7e 100644 --- a/feature/files/src/commonMain/kotlin/com/garfiec/librechat/feature/files/viewmodel/FilesViewModel.kt +++ b/feature/files/src/commonMain/kotlin/com/garfiec/librechat/feature/files/viewmodel/FilesViewModel.kt @@ -12,6 +12,7 @@ import com.garfiec.librechat.feature.files.FileDisplayData import com.garfiec.librechat.feature.files.FilePreviewDisplayData import com.garfiec.librechat.feature.files.platform.FileReader import com.garfiec.librechat.feature.files.platform.formatFileSize +import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -166,7 +167,7 @@ class FilesViewModel( } } - private var uploadJob: kotlinx.coroutines.Job? = null + private var uploadJob: Job? = null /** * Upload a file from a platform-specific file reference. diff --git a/feature/settings/src/androidMain/kotlin/com/garfiec/librechat/feature/settings/viewmodel/delegate/SpeechSettingsDelegate.kt b/feature/settings/src/androidMain/kotlin/com/garfiec/librechat/feature/settings/viewmodel/delegate/SpeechSettingsDelegate.kt index 9c852b1..c272bb6 100644 --- a/feature/settings/src/androidMain/kotlin/com/garfiec/librechat/feature/settings/viewmodel/delegate/SpeechSettingsDelegate.kt +++ b/feature/settings/src/androidMain/kotlin/com/garfiec/librechat/feature/settings/viewmodel/delegate/SpeechSettingsDelegate.kt @@ -3,6 +3,7 @@ package com.garfiec.librechat.feature.settings.viewmodel.delegate import android.content.Context import android.media.MediaPlayer import android.speech.tts.TextToSpeech +import android.speech.tts.UtteranceProgressListener import com.garfiec.librechat.core.common.result.Result import com.garfiec.librechat.core.data.datastore.SettingsDataStore import com.garfiec.librechat.core.data.repository.SpeechRepository @@ -11,6 +12,7 @@ import com.garfiec.librechat.feature.settings.screen.DeviceVoiceInfo import com.garfiec.librechat.feature.settings.viewmodel.SettingsStateHandle import kotlinx.coroutines.launch import co.touchlab.kermit.Logger +import java.io.File /** * Handles TTS voice selection, test playback, device voice loading, and MediaPlayer lifecycle. @@ -102,7 +104,7 @@ class SpeechSettingsDelegate( } } stateHandle.update { copy(isTtsPreviewPlaying = true) } - tts.setOnUtteranceProgressListener(object : android.speech.tts.UtteranceProgressListener() { + tts.setOnUtteranceProgressListener(object : UtteranceProgressListener() { override fun onStart(utteranceId: String?) { /* no-op */ } override fun onDone(utteranceId: String?) { stateHandle.update { copy(isTtsPreviewPlaying = false) } @@ -217,7 +219,7 @@ class SpeechSettingsDelegate( currentMediaPlayer = null // Write bytes to a temporary file - val tempFile = java.io.File.createTempFile("voice_test", ".mp3", context.cacheDir) + val tempFile = File.createTempFile("voice_test", ".mp3", context.cacheDir) tempFile.deleteOnExit() tempFile.writeBytes(audioBytes) diff --git a/feature/settings/src/androidUnitTest/kotlin/com/garfiec/librechat/feature/settings/di/SettingsModuleVerificationTest.kt b/feature/settings/src/androidUnitTest/kotlin/com/garfiec/librechat/feature/settings/di/SettingsModuleVerificationTest.kt index 369a601..8de0844 100644 --- a/feature/settings/src/androidUnitTest/kotlin/com/garfiec/librechat/feature/settings/di/SettingsModuleVerificationTest.kt +++ b/feature/settings/src/androidUnitTest/kotlin/com/garfiec/librechat/feature/settings/di/SettingsModuleVerificationTest.kt @@ -1,5 +1,25 @@ package com.garfiec.librechat.feature.settings.di +import android.app.Application +import android.content.Context +import androidx.lifecycle.SavedStateHandle +import com.garfiec.librechat.core.data.datastore.ServerDataStore +import com.garfiec.librechat.core.data.datastore.SettingsDataStore +import com.garfiec.librechat.core.data.datastore.ThemeDataStore +import com.garfiec.librechat.core.data.repository.ApiKeyRepository +import com.garfiec.librechat.core.data.repository.AuthRepository +import com.garfiec.librechat.core.data.repository.BalanceRepository +import com.garfiec.librechat.core.data.repository.ConversationRepository +import com.garfiec.librechat.core.data.repository.KeyRepository +import com.garfiec.librechat.core.data.repository.McpRepository +import com.garfiec.librechat.core.data.repository.MemoryRepository +import com.garfiec.librechat.core.data.repository.PresetRepository +import com.garfiec.librechat.core.data.repository.ShareRepository +import com.garfiec.librechat.core.data.repository.SpeechRepository +import com.garfiec.librechat.core.data.repository.UserRepository +import com.garfiec.librechat.feature.settings.util.ContentReader +import com.garfiec.librechat.feature.settings.util.PlatformCacheCleaner +import com.garfiec.librechat.feature.settings.viewmodel.delegate.SpeechSettingsFactory import org.junit.Test import org.koin.test.verify.verify @@ -8,26 +28,26 @@ class SettingsModuleVerificationTest { fun verifySettingsModule() { settingsModule.verify( extraTypes = listOf( - android.content.Context::class, - android.app.Application::class, - androidx.lifecycle.SavedStateHandle::class, - com.garfiec.librechat.core.data.repository.UserRepository::class, - com.garfiec.librechat.core.data.repository.AuthRepository::class, - com.garfiec.librechat.core.data.repository.ConversationRepository::class, - com.garfiec.librechat.core.data.repository.McpRepository::class, - com.garfiec.librechat.core.data.repository.MemoryRepository::class, - com.garfiec.librechat.core.data.repository.SpeechRepository::class, - com.garfiec.librechat.core.data.repository.BalanceRepository::class, - com.garfiec.librechat.core.data.repository.ShareRepository::class, - com.garfiec.librechat.core.data.repository.KeyRepository::class, - com.garfiec.librechat.core.data.repository.ApiKeyRepository::class, - com.garfiec.librechat.core.data.repository.PresetRepository::class, - com.garfiec.librechat.core.data.datastore.ThemeDataStore::class, - com.garfiec.librechat.core.data.datastore.ServerDataStore::class, - com.garfiec.librechat.core.data.datastore.SettingsDataStore::class, - com.garfiec.librechat.feature.settings.util.ContentReader::class, - com.garfiec.librechat.feature.settings.util.PlatformCacheCleaner::class, - com.garfiec.librechat.feature.settings.viewmodel.delegate.SpeechSettingsFactory::class, + Context::class, + Application::class, + SavedStateHandle::class, + UserRepository::class, + AuthRepository::class, + ConversationRepository::class, + McpRepository::class, + MemoryRepository::class, + SpeechRepository::class, + BalanceRepository::class, + ShareRepository::class, + KeyRepository::class, + ApiKeyRepository::class, + PresetRepository::class, + ThemeDataStore::class, + ServerDataStore::class, + SettingsDataStore::class, + ContentReader::class, + PlatformCacheCleaner::class, + SpeechSettingsFactory::class, ), ) } diff --git a/feature/settings/src/androidUnitTest/kotlin/com/garfiec/librechat/feature/settings/viewmodel/SettingsViewModelTest.kt b/feature/settings/src/androidUnitTest/kotlin/com/garfiec/librechat/feature/settings/viewmodel/SettingsViewModelTest.kt index dbb76dd..f1aa865 100644 --- a/feature/settings/src/androidUnitTest/kotlin/com/garfiec/librechat/feature/settings/viewmodel/SettingsViewModelTest.kt +++ b/feature/settings/src/androidUnitTest/kotlin/com/garfiec/librechat/feature/settings/viewmodel/SettingsViewModelTest.kt @@ -23,6 +23,7 @@ import com.garfiec.librechat.core.data.repository.ShareRepository import com.garfiec.librechat.core.data.repository.SpeechRepository import com.garfiec.librechat.core.data.repository.UserRepository import com.garfiec.librechat.core.model.User +import io.mockk.Ordering import io.mockk.coEvery import io.mockk.coVerify import io.mockk.every @@ -182,7 +183,7 @@ class SettingsViewModelTest { viewModel.deleteAccount() advanceUntilIdle() - coVerify(ordering = io.mockk.Ordering.ORDERED) { + coVerify(ordering = Ordering.ORDERED) { userRepository.deleteUser() authRepository.logout() } diff --git a/feature/settings/src/commonMain/kotlin/com/garfiec/librechat/feature/settings/screen/ApiKeysScreen.kt b/feature/settings/src/commonMain/kotlin/com/garfiec/librechat/feature/settings/screen/ApiKeysScreen.kt index 5f69d94..0cc6db9 100644 --- a/feature/settings/src/commonMain/kotlin/com/garfiec/librechat/feature/settings/screen/ApiKeysScreen.kt +++ b/feature/settings/src/commonMain/kotlin/com/garfiec/librechat/feature/settings/screen/ApiKeysScreen.kt @@ -3,6 +3,7 @@ package com.garfiec.librechat.feature.settings.screen import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -30,6 +31,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.material3.TopAppBar @@ -173,7 +175,7 @@ fun ApiKeysScreen( else -> { LazyColumn( modifier = Modifier.fillMaxSize(), - contentPadding = androidx.compose.foundation.layout.PaddingValues(16.dp), + contentPadding = PaddingValues(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp), ) { items( @@ -210,7 +212,7 @@ fun ApiKeysScreen( viewModel.deleteKey(id) } }, - colors = androidx.compose.material3.ButtonDefaults.textButtonColors( + colors = ButtonDefaults.textButtonColors( contentColor = MaterialTheme.colorScheme.error, ), ) { diff --git a/feature/settings/src/commonMain/kotlin/com/garfiec/librechat/feature/settings/screen/GeneralSettingsScreen.kt b/feature/settings/src/commonMain/kotlin/com/garfiec/librechat/feature/settings/screen/GeneralSettingsScreen.kt index cb6c3de..1e4a171 100644 --- a/feature/settings/src/commonMain/kotlin/com/garfiec/librechat/feature/settings/screen/GeneralSettingsScreen.kt +++ b/feature/settings/src/commonMain/kotlin/com/garfiec/librechat/feature/settings/screen/GeneralSettingsScreen.kt @@ -1,5 +1,6 @@ package com.garfiec.librechat.feature.settings.screen +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -25,6 +26,7 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.RadioButton import androidx.compose.material3.Scaffold +import androidx.compose.material3.Surface import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar @@ -33,6 +35,7 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.vector.ImageVector import org.jetbrains.compose.resources.stringResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.heading @@ -286,7 +289,7 @@ private fun AboutInfo(serverUrl: String) { ) { Row( modifier = Modifier.fillMaxWidth(), - horizontalArrangement = androidx.compose.foundation.layout.Arrangement.SpaceBetween, + horizontalArrangement = Arrangement.SpaceBetween, ) { Text( text = stringResource(Res.string.app_version_label), @@ -301,7 +304,7 @@ private fun AboutInfo(serverUrl: String) { Spacer(modifier = Modifier.height(8.dp)) Row( modifier = Modifier.fillMaxWidth(), - horizontalArrangement = androidx.compose.foundation.layout.Arrangement.SpaceBetween, + horizontalArrangement = Arrangement.SpaceBetween, ) { Text( text = stringResource(Res.string.server_label), @@ -323,14 +326,14 @@ private fun AboutInfo(serverUrl: String) { @Composable private fun GeneralSettingsRow( - icon: androidx.compose.ui.graphics.vector.ImageVector, + icon: ImageVector, title: String, subtitle: String, onClick: () -> Unit, modifier: Modifier = Modifier, ) { Column(modifier = modifier) { - androidx.compose.material3.Surface( + Surface( modifier = Modifier.fillMaxWidth(), onClick = onClick, ) { diff --git a/feature/settings/src/commonMain/kotlin/com/garfiec/librechat/feature/settings/screen/McpSettingsSection.kt b/feature/settings/src/commonMain/kotlin/com/garfiec/librechat/feature/settings/screen/McpSettingsSection.kt index 50070ad..d62a26c 100644 --- a/feature/settings/src/commonMain/kotlin/com/garfiec/librechat/feature/settings/screen/McpSettingsSection.kt +++ b/feature/settings/src/commonMain/kotlin/com/garfiec/librechat/feature/settings/screen/McpSettingsSection.kt @@ -103,7 +103,7 @@ internal fun McpSettingsSection( @Composable private fun McpServerItem( server: McpServer, - serverStatus: com.garfiec.librechat.core.model.mcp.McpServerStatus?, + serverStatus: McpServerStatus?, isReinitializing: Boolean, onEdit: () -> Unit, onDelete: () -> Unit, diff --git a/feature/settings/src/commonMain/kotlin/com/garfiec/librechat/feature/settings/screen/SharedLinksScreen.kt b/feature/settings/src/commonMain/kotlin/com/garfiec/librechat/feature/settings/screen/SharedLinksScreen.kt index 54d6142..d60c156 100644 --- a/feature/settings/src/commonMain/kotlin/com/garfiec/librechat/feature/settings/screen/SharedLinksScreen.kt +++ b/feature/settings/src/commonMain/kotlin/com/garfiec/librechat/feature/settings/screen/SharedLinksScreen.kt @@ -4,6 +4,7 @@ import com.garfiec.librechat.feature.settings.util.copyToClipboard import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -131,7 +132,7 @@ fun SharedLinksScreen( .fillMaxSize() .padding(padding), verticalArrangement = Arrangement.spacedBy(8.dp), - contentPadding = androidx.compose.foundation.layout.PaddingValues(16.dp), + contentPadding = PaddingValues(16.dp), ) { items( items = links, diff --git a/feature/settings/src/commonMain/kotlin/com/garfiec/librechat/feature/settings/screen/sections/ChatPreferencesSection.kt b/feature/settings/src/commonMain/kotlin/com/garfiec/librechat/feature/settings/screen/sections/ChatPreferencesSection.kt index fbd103b..e4b6418 100644 --- a/feature/settings/src/commonMain/kotlin/com/garfiec/librechat/feature/settings/screen/sections/ChatPreferencesSection.kt +++ b/feature/settings/src/commonMain/kotlin/com/garfiec/librechat/feature/settings/screen/sections/ChatPreferencesSection.kt @@ -22,6 +22,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector import org.jetbrains.compose.resources.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp @@ -108,7 +109,7 @@ internal fun DangerZone( @Composable internal fun SettingsRow( - icon: androidx.compose.ui.graphics.vector.ImageVector, + icon: ImageVector, title: String, subtitle: String, onClick: () -> Unit, diff --git a/feature/settings/src/commonMain/kotlin/com/garfiec/librechat/feature/settings/screen/sections/SecuritySection.kt b/feature/settings/src/commonMain/kotlin/com/garfiec/librechat/feature/settings/screen/sections/SecuritySection.kt index f06b09a..7f563bd 100644 --- a/feature/settings/src/commonMain/kotlin/com/garfiec/librechat/feature/settings/screen/sections/SecuritySection.kt +++ b/feature/settings/src/commonMain/kotlin/com/garfiec/librechat/feature/settings/screen/sections/SecuritySection.kt @@ -6,8 +6,10 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.material3.AlertDialog import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TextButton @@ -18,6 +20,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import org.jetbrains.compose.resources.stringResource +import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.unit.dp import librechat_mobile.feature.settings.generated.resources.Res import librechat_mobile.feature.settings.generated.resources.* @@ -50,7 +53,7 @@ internal fun TwoFactorSetupDialog( color = MaterialTheme.colorScheme.surfaceVariant, shape = MaterialTheme.shapes.small, ) { - androidx.compose.foundation.text.selection.SelectionContainer { + SelectionContainer { Text( text = otpauthUrl, style = MaterialTheme.typography.bodySmall, @@ -59,7 +62,7 @@ internal fun TwoFactorSetupDialog( } } } - androidx.compose.material3.OutlinedTextField( + OutlinedTextField( value = code, onValueChange = { code = it.filter { ch -> ch.isDigit() }.take(6) }, label = { Text(stringResource(Res.string.hint_verification_code)) }, @@ -105,7 +108,7 @@ internal fun TwoFactorCodeDialog( text = description, style = MaterialTheme.typography.bodyMedium, ) - androidx.compose.material3.OutlinedTextField( + OutlinedTextField( value = code, onValueChange = { code = it.filter { ch -> ch.isDigit() }.take(6) }, label = { Text(stringResource(Res.string.hint_verification_code)) }, @@ -160,7 +163,7 @@ internal fun BackupCodesDialog( Text( text = code, style = MaterialTheme.typography.bodyMedium, - fontFamily = androidx.compose.ui.text.font.FontFamily.Monospace, + fontFamily = FontFamily.Monospace, ) } } diff --git a/feature/settings/src/iosMain/kotlin/com/garfiec/librechat/feature/settings/viewmodel/delegate/IosSpeechSettingsDelegate.kt b/feature/settings/src/iosMain/kotlin/com/garfiec/librechat/feature/settings/viewmodel/delegate/IosSpeechSettingsDelegate.kt index 0f22446..6dc4ee4 100644 --- a/feature/settings/src/iosMain/kotlin/com/garfiec/librechat/feature/settings/viewmodel/delegate/IosSpeechSettingsDelegate.kt +++ b/feature/settings/src/iosMain/kotlin/com/garfiec/librechat/feature/settings/viewmodel/delegate/IosSpeechSettingsDelegate.kt @@ -7,6 +7,7 @@ import com.garfiec.librechat.core.data.repository.SpeechRepository import com.garfiec.librechat.core.model.speech.TtsVoice import com.garfiec.librechat.feature.settings.screen.DeviceVoiceInfo import com.garfiec.librechat.feature.settings.viewmodel.SettingsStateHandle +import kotlinx.cinterop.ObjCSignatureOverride import kotlinx.coroutines.launch import platform.AVFAudio.AVSpeechBoundary import platform.AVFAudio.AVSpeechSynthesisVoice @@ -28,7 +29,7 @@ class IosSpeechSettingsDelegate( private val synthesizer = AVSpeechSynthesizer() private val synthesizerDelegate = object : NSObject(), AVSpeechSynthesizerDelegateProtocol { - @kotlinx.cinterop.ObjCSignatureOverride + @ObjCSignatureOverride override fun speechSynthesizer( synthesizer: AVSpeechSynthesizer, didFinishSpeechUtterance: AVSpeechUtterance, @@ -36,7 +37,7 @@ class IosSpeechSettingsDelegate( stateHandle.update { copy(isTtsPreviewPlaying = false) } } - @kotlinx.cinterop.ObjCSignatureOverride + @ObjCSignatureOverride override fun speechSynthesizer( synthesizer: AVSpeechSynthesizer, didCancelSpeechUtterance: AVSpeechUtterance, diff --git a/shared/src/commonMain/kotlin/com/garfiec/librechat/shared/navigation/NavHostViewModel.kt b/shared/src/commonMain/kotlin/com/garfiec/librechat/shared/navigation/NavHostViewModel.kt index 823c0f9..7c13c04 100644 --- a/shared/src/commonMain/kotlin/com/garfiec/librechat/shared/navigation/NavHostViewModel.kt +++ b/shared/src/commonMain/kotlin/com/garfiec/librechat/shared/navigation/NavHostViewModel.kt @@ -4,6 +4,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.garfiec.librechat.core.data.repository.AuthRepository import com.garfiec.librechat.core.data.repository.BannerRepository +import com.garfiec.librechat.core.data.datastore.SettingsDataStore import com.garfiec.librechat.core.data.repository.ConfigRepository import com.garfiec.librechat.core.data.repository.ConversationRepository import com.garfiec.librechat.core.model.Banner @@ -25,7 +26,7 @@ class NavHostViewModel( configRepository: ConfigRepository, conversationRepository: ConversationRepository, private val tokenManager: TokenManager, - private val settingsDataStore: com.garfiec.librechat.core.data.datastore.SettingsDataStore, + private val settingsDataStore: SettingsDataStore, ) : ViewModel() { private val bannerStateHolder = BannerStateHolder(bannerRepository, viewModelScope)