package com.syncflow.ui.browser import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.* import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties import androidx.hilt.navigation.compose.hiltViewModel import com.syncflow.domain.model.RemoteFile @OptIn(ExperimentalMaterial3Api::class) @Composable fun RemoteBrowserDialog( accountId: Long, initialPath: String = "/", onSelect: (path: String) -> Unit, onDismiss: () -> Unit, vm: RemoteBrowserViewModel = hiltViewModel(), ) { LaunchedEffect(accountId, initialPath) { vm.init(accountId, initialPath) } val state by vm.state.collectAsState() Dialog( onDismissRequest = onDismiss, properties = DialogProperties(usePlatformDefaultWidth = false), ) { Surface( modifier = Modifier .fillMaxWidth(0.95f) .fillMaxHeight(0.85f), shape = MaterialTheme.shapes.extraLarge, tonalElevation = 6.dp, ) { Column { // Title bar TopAppBar( title = { Column { Text("Choose remote folder", style = MaterialTheme.typography.titleMedium) Text( state.currentPath, style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant, maxLines = 1, overflow = TextOverflow.Ellipsis, ) } }, navigationIcon = { IconButton(onClick = { if (!vm.navigateUp()) onDismiss() }) { Icon(Icons.Default.ArrowBack, null) } }, actions = { // Select current folder TextButton(onClick = { onSelect(state.currentPath) }) { Text("Select here") } }, colors = TopAppBarDefaults.topAppBarColors(containerColor = MaterialTheme.colorScheme.surfaceContainerHigh), ) HorizontalDivider() when { state.isLoading -> Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { CircularProgressIndicator() } state.error != null -> Box(Modifier.fillMaxSize().padding(24.dp), contentAlignment = Alignment.Center) { Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(8.dp)) { Icon(Icons.Default.ErrorOutline, null, Modifier.size(48.dp), tint = MaterialTheme.colorScheme.error) Text(state.error!!, color = MaterialTheme.colorScheme.error) FilledTonalButton(onClick = { vm.retry() }) { Text("Retry") } } } state.entries.isEmpty() -> Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(8.dp)) { Icon(Icons.Default.FolderOff, null, Modifier.size(48.dp), tint = MaterialTheme.colorScheme.onSurfaceVariant) Text("Empty folder", color = MaterialTheme.colorScheme.onSurfaceVariant) TextButton(onClick = { onSelect(state.currentPath) }) { Text("Select this folder anyway") } } } else -> LazyColumn(modifier = Modifier.fillMaxSize()) { items(state.entries, key = { it.path }) { entry -> BrowserEntry( file = entry, onClick = { if (entry.isDirectory) vm.navigateTo(entry.path) else onSelect(entry.path.substringBeforeLast('/').ifBlank { "/" }) }, onSelectFolder = if (entry.isDirectory) ({ onSelect(entry.path) }) else null, ) HorizontalDivider(modifier = Modifier.padding(start = 56.dp)) } } } } } } } @Composable private fun BrowserEntry( file: RemoteFile, onClick: () -> Unit, onSelectFolder: (() -> Unit)?, ) { Row( modifier = Modifier .fillMaxWidth() .clickable(onClick = onClick) .padding(horizontal = 16.dp, vertical = 10.dp), verticalAlignment = Alignment.CenterVertically, ) { Icon( imageVector = if (file.isDirectory) Icons.Default.Folder else Icons.Default.InsertDriveFile, contentDescription = null, modifier = Modifier.size(24.dp), tint = if (file.isDirectory) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant, ) Spacer(Modifier.width(14.dp)) Column(modifier = Modifier.weight(1f)) { Text(file.name, style = MaterialTheme.typography.bodyMedium, maxLines = 1, overflow = TextOverflow.Ellipsis) if (!file.isDirectory) { Text(file.sizeBytes.formatBytes(), style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant) } } if (onSelectFolder != null) { IconButton(onClick = onSelectFolder, modifier = Modifier.size(36.dp)) { Icon(Icons.Default.CheckCircleOutline, "Select this folder", Modifier.size(20.dp), tint = MaterialTheme.colorScheme.primary) } } else { Icon(Icons.Default.ChevronRight, null, Modifier.size(20.dp), tint = MaterialTheme.colorScheme.onSurfaceVariant) } } } private fun Long.formatBytes(): String = when { this < 1024 -> "${this}B" this < 1_048_576 -> "${"%.1f".format(this / 1024.0)}KB" this < 1_073_741_824 -> "${"%.1f".format(this / 1_048_576.0)}MB" else -> "${"%.1f".format(this / 1_073_741_824.0)}GB" }