feat: Added UI for the FolderPairTab

This commit adds a FolderPairCard composable.
Also fixed the warning: "Serializable object must implement 'readResolve'".
This commit is contained in:
Fabian Wolter
2026-02-23 18:29:23 +01:00
parent 31bd2b1ceb
commit c215ae3ec4
5 changed files with 142 additions and 31 deletions

View File

@@ -23,6 +23,8 @@ import com.fabisahne.cloudsync.ui.tabs.SettingsTab
object HomeScreen : Screen {
private fun readResolve(): Any = HomeScreen
private val TABS = listOf(
HistoryTab,
FolderPairTab,

View File

@@ -11,6 +11,8 @@ import cafe.adriel.voyager.navigator.tab.TabOptions
object AccountTab : Tab {
private fun readResolve(): Any = AccountTab
override val options: TabOptions
@Composable
get() {

View File

@@ -4,14 +4,23 @@ import android.content.Intent
import android.net.Uri
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
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.Delete
import androidx.compose.material.icons.filled.FolderCopy
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -19,17 +28,30 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.core.net.toUri
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
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 com.fabisahne.cloudsync.ui.screens.FolderPairScreen
import com.fabisahne.cloudsync.ui.theme.CloudSyncTheme
import kotlinx.coroutines.launch
import androidx.core.net.toUri
import java.time.Instant
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.format.DateTimeFormatter
object FolderPairTab : Tab {
private fun readResolve(): Any = FolderPairTab
override val options: TabOptions
@Composable
get() {
@@ -47,22 +69,15 @@ object FolderPairTab : Tab {
@Composable
override fun Content() {
// Text("Folder Pairs")
// FloatingActionButton(
//
// modifier = Modifier.absolute(),
// onClick = {}
// ) {
// Icon(Icons.Default.Add, "Add Pair")
// }
val navigator = LocalNavigator.currentOrThrow.parent!!
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? ->
val directoryPickerLauncher =
rememberLauncherForActivityResult(ActivityResultContracts.OpenDocumentTree()) { uri: Uri? ->
uri?.let {
val takeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
@@ -81,6 +96,12 @@ object FolderPairTab : Tab {
}
}
val deletePair = { pair: FolderPair ->
scope.launch {
dao.delete(pair)
}
}
Scaffold(
floatingActionButton = {
FloatingActionButton(
@@ -95,12 +116,13 @@ object FolderPairTab : Tab {
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
)
FolderPairCard(
pair,
onDelete = {
deletePair(pair)
},
onClick = {
navigator.push(FolderPairScreen(pair.id))
}
)
}
@@ -108,3 +130,84 @@ object FolderPairTab : Tab {
}
}
}
@Composable
fun FolderPairCard(folderPair: FolderPair, onDelete: () -> Unit = {}, onClick: () -> Unit = {}) {
val instant = Instant.ofEpochMilli(folderPair.createdAt)
val dateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault())
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")
Card(
onClick = onClick,
modifier = Modifier
.padding(8.dp, 8.dp)
.height(100.dp)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight()
.padding(10.dp, 14.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Column(
modifier = Modifier.fillMaxHeight(),
verticalArrangement = Arrangement.SpaceBetween
) {
Text(dateTime.format(formatter), style = MaterialTheme.typography.labelMedium)
Text(folderPair.name, style = MaterialTheme.typography.titleMedium)
Text(
folderPair.localDir.toUri().lastPathSegment ?: folderPair.localDir,
style = MaterialTheme.typography.labelMedium
)
}
Button(
onClick = onDelete
) {
Icon(
Icons.Default.Delete,
null,
)
}
}
}
}
@Preview(backgroundColor = 0xFFFFFFFF, showBackground = true)
@Composable
fun FolderPairsCardPreview() {
val folderPair = FolderPair(
name = "Sample Folder Pair",
localDir = "sample/local/folder",
cloudPath = "sample/cloud/path"
)
CloudSyncTheme {
Column {
FolderPairCard(folderPair)
FolderPairCard(folderPair)
}
}
}
@Preview(backgroundColor = 0xFF000000, showBackground = true)
@Composable
fun FolderPairsCardPreviewDark() {
val folderPair = FolderPair(
name = "Sample Folder Pair",
localDir = "sample/local/folder",
cloudPath = "sample/cloud/path"
)
CloudSyncTheme(
darkTheme = true
) {
Column {
FolderPairCard(folderPair)
FolderPairCard(folderPair)
}
}
}

View File

@@ -32,6 +32,8 @@ import com.fabisahne.cloudsync.ui.theme.CloudSyncTheme
import java.time.LocalDate
object HistoryTab : Tab {
private fun readResolve(): Any = HistoryTab
override val options: TabOptions
@Composable
get() {

View File

@@ -11,6 +11,8 @@ import cafe.adriel.voyager.navigator.tab.Tab
import cafe.adriel.voyager.navigator.tab.TabOptions
object SettingsTab : Tab {
private fun readResolve(): Any = SettingsTab
override val options: TabOptions
@Composable
get() {