Prepare for database

This commit is contained in:
Minecon724 2025-06-25 12:20:26 +02:00
commit feec030726
Signed by: Minecon724
GPG key ID: A02E6E67AB961189
17 changed files with 265 additions and 12 deletions

View file

@ -68,6 +68,11 @@ dependencies {
implementation(libs.androidx.material3.window.size.class1)
implementation(libs.okhttp.sse)
implementation(libs.androidx.datastore)
implementation(libs.hilt.navigation.compose)
implementation(libs.androidx.room.runtime)
implementation(libs.androidx.room.compiler)
implementation(libs.androidx.room.paging)
implementation(libs.androidx.room.ktx)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)

View file

@ -4,5 +4,4 @@ import android.app.Application
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
class ChatApplication : Application() {
}
class ChatApplication : Application()

View file

@ -27,8 +27,8 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.min
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import eu.m724.chatapp.R
import eu.m724.chatapp.activity.chat.quick_settings.composable.ModelCard
import eu.m724.chatapp.api.data.response.models.LanguageModel
@ -38,7 +38,7 @@ fun ChatQuickSettings(
modifier: Modifier = Modifier,
onModelSelected: (LanguageModel) -> Unit,
onDismiss: () -> Unit,
viewModel: ChatQuickSettingsViewModel = viewModel(),
viewModel: ChatQuickSettingsViewModel = hiltViewModel(),
) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()

View file

@ -22,7 +22,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.hilt.navigation.compose.hiltViewModel
import dagger.hilt.android.AndroidEntryPoint
import eu.m724.chatapp.R
import eu.m724.chatapp.activity.chat.ChatActivity
@ -52,7 +52,7 @@ class MainActivity : ComponentActivity() {
@Composable
fun Content(
modifier: Modifier = Modifier,
viewModel: MainActivityViewModel = viewModel()
viewModel: MainActivityViewModel = hiltViewModel()
) {
val uiState by viewModel.uiState.collectAsState()
val context = LocalContext.current

View file

@ -5,6 +5,7 @@ import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import eu.m724.chatapp.api.AiApiService
import eu.m724.chatapp.store.data.Chat
import eu.m724.chatapp.store.room.ChatDao
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@ -16,7 +17,8 @@ import javax.inject.Inject
@HiltViewModel
class MainActivityViewModel @Inject constructor(
val aiApiService: AiApiService
val aiApiService: AiApiService,
val chatDao: ChatDao
) : ViewModel() {
private val _uiState = MutableStateFlow(MainActivityUiState())
val uiState: StateFlow<MainActivityUiState> = _uiState.asStateFlow()
@ -43,6 +45,8 @@ class MainActivityViewModel @Inject constructor(
messages = emptyList()
)
chatDao.insertChat()
_uiEvents.send(MainActivityUiEvent.StartChat(chat))
}
}

View file

@ -7,6 +7,11 @@ import kotlinx.parcelize.Parcelize
@Parcelize
data class Chat(
/**
* The unique identifier of this chat.
*/
val id: Int,
/**
* The title of this chat.
*/

View file

@ -0,0 +1,7 @@
package eu.m724.chatapp.store.proto
import eu.m724.chatapp.proto.Chat
class DataStoreModule {
val a: Chat
}

View file

@ -0,0 +1,20 @@
package eu.m724.chatapp.store.proto
import androidx.datastore.core.Serializer
import eu.m724.chatapp.proto.ProtoChat
object ProtoChatSerializer : Serializer<ProtoChat> {
override val defaultValue: ProtoChat = Settings.getDefaultInstance()
override suspend fun readFrom(input: InputStream): Settings {
try {
return Settings.parseFrom(input)
} catch (exception: InvalidProtocolBufferException) {
throw CorruptionException("Cannot read proto.", exception)
}
}
override suspend fun writeTo(
t: Settings,
output: OutputStream) = t.writeTo(output)
}

View file

@ -0,0 +1,30 @@
package eu.m724.chatapp.store.room
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
import eu.m724.chatapp.store.room.entity.ChatEntity
import kotlinx.coroutines.flow.Flow
@Dao
interface ChatDao {
@Query("SELECT * FROM chats")
fun getAllChats(): List<ChatEntity>
@Query("SELECT * FROM chats WHERE id = :id")
fun getChatById(id: Int): ChatEntity?
@Query("""
SELECT * FROM chats
JOIN chats_fts ON chats.id = chats_fts.rowid
WHERE chats_fts MATCH :query
""")
fun searchChats(query: String): Flow<List<ChatEntity>>
@Insert
fun insertChat(chat: ChatEntity)
@Update
fun updateChat(chat: ChatEntity)
}

View file

@ -0,0 +1,21 @@
package eu.m724.chatapp.store.room
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
import eu.m724.chatapp.store.room.entity.MessageEntity
import kotlinx.coroutines.flow.Flow
@Dao
interface MessageDao {
@Insert
suspend fun insertMessage(message: MessageEntity)
@Update
suspend fun updateMessage(message: MessageEntity)
@Query("SELECT * FROM messages WHERE chatId = :chatId ORDER BY index ASC")
fun getMessagesForChat(chatId: Int): Flow<List<MessageEntity>>
}

View file

@ -0,0 +1,19 @@
package eu.m724.chatapp.store.room.database
import androidx.room.Database
import androidx.room.RoomDatabase
import eu.m724.chatapp.store.room.ChatDao
import eu.m724.chatapp.store.room.MessageDao
import eu.m724.chatapp.store.room.entity.ChatEntity
import eu.m724.chatapp.store.room.entity.ChatEntityFts
import eu.m724.chatapp.store.room.entity.MessageEntity
@Database(entities = [
ChatEntity::class,
ChatEntityFts::class,
MessageEntity::class
], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun chatDao(): ChatDao
abstract fun messageDao(): MessageDao
}

View file

@ -0,0 +1,36 @@
package eu.m724.chatapp.store.room.database
import android.content.Context
import androidx.room.Room
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import eu.m724.chatapp.store.room.ChatDao
import eu.m724.chatapp.store.room.MessageDao
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
object DatabaseModule {
@Provides
@Singleton
fun provideAppDatabase(@ApplicationContext context: Context): AppDatabase {
return Room.databaseBuilder(
context,
AppDatabase::class.java,
"chatapp-database"
).build()
}
@Provides
fun provideChatDao(appDatabase: AppDatabase): ChatDao {
return appDatabase.chatDao()
}
@Provides
fun provideMessageDao(appDatabase: AppDatabase): MessageDao {
return appDatabase.messageDao()
}
}

View file

@ -0,0 +1,23 @@
package eu.m724.chatapp.store.room.entity
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "chats")
data class ChatEntity(
/**
* The unique identifier of this chat.
*/
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
/**
* The title of this chat, null if not set.
*/
val title: String?,
/**
* The model ID used in this chat.
*/
val model: String,
)

View file

@ -0,0 +1,13 @@
package eu.m724.chatapp.store.room.entity
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Fts4
@Entity(tableName = "chats_fts")
@Fts4
data class ChatEntityFts(
@ColumnInfo(name = "title")
val title: String
)

View file

@ -0,0 +1,34 @@
package eu.m724.chatapp.store.room.entity
import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
@Entity(
tableName = "messages",
indices = [
Index(value = ["chatId"])
]
)
data class MessageEntity(
/**
* The unique identifier of this message. TODO make random perhaps
*/
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
/**
* The index of this message in the chat.
*/
val index: Int,
/**
* The ID of the chat this message belongs to.
*/
val chatId: Int,
/**
* The content of this message.
*/
val content: String
)

View file

@ -0,0 +1,22 @@
syntax = "proto3";
option java_package = "eu.m724.chatapp.proto";
option java_multiple_files = true;
enum ProtoChatMessageRole {
User = 0;
Assistant = 1;
}
message ProtoChatMessage {
int64 id = 1;
string content = 2;
ProtoChatMessageRole role = 3;
}
message ProtoChat {
int64 id = 1;
string title = 2;
string model_id = 3;
repeated ProtoChatMessage messages = 4;
}

View file

@ -10,7 +10,8 @@ material = "1.12.0"
lifecycleRuntimeKtx = "2.9.1"
activityCompose = "1.10.1"
composeBom = "2025.06.01"
hilt = "2.56.2"
hiltAndroid = "2.56.2"
hiltCompiler = "2.56.2"
ksp = "2.1.21-2.0.2"
retrofit = "3.0.0"
secrets = "2.0.1"
@ -19,6 +20,13 @@ material3WindowSizeClass = "1.3.2"
okhttpSse = "4.12.0"
parcelize = "2.1.21"
datastore = "1.1.7"
hiltNavigationCompose = "1.2.0"
roomRuntime = "2.7.2"
roomCompiler = "2.7.2"
roomPaging = "2.7.2"
roomKtx = "2.7.2"
pagingRuntime = "3.3.6"
pagingCompose = "3.3.6"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@ -37,20 +45,27 @@ androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-toolin
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" }
hilt-compiler = { group = "com.google.dagger", name = "hilt-compiler", version.ref = "hilt" }
hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hiltAndroid" }
hilt-compiler = { group = "com.google.dagger", name = "hilt-compiler", version.ref = "hiltCompiler" }
retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }
retrofit-converter-gson = { group = "com.squareup.retrofit2", name = "converter-gson", version.ref = "retrofit"}
logging-interceptor = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "loggingInterceptor" }
androidx-material3-window-size-class1 = { group = "androidx.compose.material3", name = "material3-window-size-class", version.ref = "material3WindowSizeClass" }
okhttp-sse = { group = "com.squareup.okhttp3", name = "okhttp-sse", version.ref = "okhttpSse" }
androidx-datastore = { group = "androidx.datastore", name = "datastore", version.ref = "datastore" }
hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "hiltNavigationCompose" }
androidx-room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "roomRuntime" }
androidx-room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "roomCompiler" }
androidx-room-paging = { group = "androidx.room", name = "room-paging", version.ref = "roomPaging" }
androidx-room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "roomKtx" }
androidx-paging-runtime = { group = "androidx.paging", name = "paging-runtime", version.ref = "pagingRuntime" }
androidx-paging-compose = { group = "androidx.paging", name = "paging-compose", version.ref = "pagingCompose" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
hilt-android = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
hilt-android = { id = "com.google.dagger.hilt.android", version.ref = "hiltAndroid" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
secrets = { id = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin", version.ref = "secrets" }
parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "parcelize" }
parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "parcelize" }