diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 22c0199..798b270 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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) } \ No newline at end of file diff --git a/app/src/main/java/com/fabisahne/cloudsync/data/AppDatabase.kt b/app/src/main/java/com/fabisahne/cloudsync/data/AppDatabase.kt new file mode 100644 index 0000000..ebc4994 --- /dev/null +++ b/app/src/main/java/com/fabisahne/cloudsync/data/AppDatabase.kt @@ -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 + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fabisahne/cloudsync/data/FolderPair.kt b/app/src/main/java/com/fabisahne/cloudsync/data/FolderPair.kt new file mode 100644 index 0000000..b941b0c --- /dev/null +++ b/app/src/main/java/com/fabisahne/cloudsync/data/FolderPair.kt @@ -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() +) diff --git a/app/src/main/java/com/fabisahne/cloudsync/data/FolderPairDao.kt b/app/src/main/java/com/fabisahne/cloudsync/data/FolderPairDao.kt new file mode 100644 index 0000000..c9a8d51 --- /dev/null +++ b/app/src/main/java/com/fabisahne/cloudsync/data/FolderPairDao.kt @@ -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> + + @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) +} \ No newline at end of file diff --git a/app/src/main/java/com/fabisahne/cloudsync/ui/tabs/FolderPairTab.kt b/app/src/main/java/com/fabisahne/cloudsync/ui/tabs/FolderPairTab.kt index 34bf629..1e7c97b 100644 --- a/app/src/main/java/com/fabisahne/cloudsync/ui/tabs/FolderPairTab.kt +++ b/app/src/main/java/com/fabisahne/cloudsync/ui/tabs/FolderPairTab.kt @@ -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 + ) + } + ) + } + } } } } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 511b5f0..0fd8fd3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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"] \ No newline at end of file