1 Commits

Author SHA1 Message Date
Fabian Wolter
31bd2b1ceb feat: Initial Database stuff for FolderPairs
This commit adds the initial Database design to save and load `FolderPair`s locally
2026-02-23 17:05:47 +01:00
6 changed files with 139 additions and 6 deletions

View File

@@ -2,6 +2,7 @@ plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.compose)
alias(libs.plugins.jetbrains.kotlin.serialization)
alias(libs.plugins.ksp)
}
android {
@@ -63,4 +64,9 @@ dependencies {
implementation(libs.kotlinx.serialization.core)
implementation(libs.bundles.voyager)
// Room
implementation(libs.androidx.room.runtime)
implementation(libs.androidx.room.ktx)
ksp(libs.androidx.room.compiler)
}

View File

@@ -0,0 +1,27 @@
package com.fabisahne.cloudsync.data
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
@Database(entities = [FolderPair::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
abstract fun folderPairDao(): FolderPairDao
companion object {
private var INSTANCE: AppDatabase? = null
fun getDatabase(context: Context): AppDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"cloud_sync_database",
).build()
INSTANCE = instance
instance
}
}
}
}

View File

@@ -0,0 +1,14 @@
package com.fabisahne.cloudsync.data
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "folder_pairs")
data class FolderPair(
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
val name: String,
val localDir: String,
val cloudPath: String,
val createdAt: Long = System.currentTimeMillis()
)

View File

@@ -0,0 +1,26 @@
package com.fabisahne.cloudsync.data
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
import kotlinx.coroutines.flow.Flow
@Dao
interface FolderPairDao {
@Query("SELECT * FROM folder_pairs ORDER BY createdAt DESC")
fun getAll(): Flow<List<FolderPair>>
@Query("SELECT * FROM folder_pairs WHERE id = :id")
suspend fun getById(id: Long): FolderPair?
@Insert
suspend fun insert(folderPair: FolderPair): Long
@Update
suspend fun update(folderPair: FolderPair)
@Delete
suspend fun delete(folderPair: FolderPair)
}

View File

@@ -1,21 +1,33 @@
package com.fabisahne.cloudsync.ui.tabs
import androidx.compose.foundation.layout.absoluteOffset
import androidx.compose.foundation.layout.absolutePadding
import android.content.Intent
import android.net.Uri
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.FolderCopy
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.platform.LocalContext
import cafe.adriel.voyager.navigator.tab.Tab
import cafe.adriel.voyager.navigator.tab.TabOptions
import com.fabisahne.cloudsync.data.AppDatabase
import com.fabisahne.cloudsync.data.FolderPair
import kotlinx.coroutines.launch
import androidx.core.net.toUri
object FolderPairTab : Tab {
override val options: TabOptions
@@ -43,17 +55,56 @@ object FolderPairTab : Tab {
// ) {
// Icon(Icons.Default.Add, "Add Pair")
// }
val context = LocalContext.current
val scope = rememberCoroutineScope()
val dao = remember { AppDatabase.getDatabase(context).folderPairDao() }
val folderPairs by dao.getAll().collectAsState(initial = emptyList())
val directoryPickerLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.OpenDocumentTree()
) { uri: Uri? ->
uri?.let {
val takeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
context.contentResolver.takePersistableUriPermission(it, takeFlags)
// Save to Room
scope.launch {
dao.insert(
FolderPair(
name = "New Folder Pair",
localDir = it.toString(),
cloudPath = "/backup"
)
)
}
}
}
Scaffold(
floatingActionButton = {
FloatingActionButton(
onClick = {}
onClick = {
directoryPickerLauncher.launch(null)
}
) {
Icon(Icons.Default.Add, "Add Pair")
}
}
) { paddingValues ->
Text("Pairs here", Modifier.padding(paddingValues))
LazyColumn(modifier = Modifier.padding(paddingValues)) {
items(folderPairs.size) { pairIdx ->
val pair = folderPairs[pairIdx]
ListItem(
headlineContent = { Text(pair.name) },
supportingContent = {
Text(
pair.localDir.toUri().lastPathSegment ?: pair.localDir
)
}
)
}
}
}
}
}

View File

@@ -1,14 +1,16 @@
[versions]
agp = "9.0.0"
agp = "9.0.1"
coreKtx = "1.17.0"
junit = "4.13.2"
junitVersion = "1.3.0"
espressoCore = "3.7.0"
lifecycleRuntimeKtx = "2.10.0"
activityCompose = "1.12.2"
kotlin = "2.0.21"
kotlin = "2.2.10"
composeBom = "2024.09.00"
voyager = "1.1.0-beta03"
room = "2.7.1"
ksp = "2.3.6"
nav3Core = "1.0.0"
lifeCycleViewmodelNav3 = "2.10.0"
@@ -40,15 +42,22 @@ androidx-lifecycle-viewmodel-navigation3 = { module = "androidx.lifecycle:lifecy
kotlinx-serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "kotlinxSerializationCore" }
androidx-material3-adaptive-navigation3 = { group = "androidx.compose.material3.adaptive", name = "adaptive-navigation3", version.ref = "material3AdaptiveNav3" }
# Voyager
voyager-navigator = { module = "cafe.adriel.voyager:voyager-navigator", version.ref = "voyager" }
voyager-screenmodel = { module = "cafe.adriel.voyager:voyager-screenmodel", version.ref = "voyager" }
voyager-tab-navigator = { module = "cafe.adriel.voyager:voyager-tab-navigator", version.ref = "voyager" }
voyager-transitions = { module = "cafe.adriel.voyager:voyager-transitions", version.ref = "voyager" }
# Room
androidx-room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" }
androidx-room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" }
androidx-room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
jetbrains-kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlinSerialization" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
[bundles]
voyager = ["voyager-navigator", "voyager-screenmodel", "voyager-tab-navigator", "voyager-transitions"]