Skip to content

Commit

Permalink
Make the list-detail adaptive
Browse files Browse the repository at this point in the history
  • Loading branch information
jsixface committed Jan 7, 2025
1 parent 8474c09 commit 57d21c0
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 95 deletions.
4 changes: 2 additions & 2 deletions composeApp/src/commonMain/kotlin/ui/MainScreen.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package ui

import Backend
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Icon
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffold
import androidx.compose.runtime.Composable
Expand All @@ -20,7 +20,7 @@ import ui.model.AppPages
@Composable
fun MainScreen() {

Surface(modifier = Modifier.fillMaxSize()) {
Box(modifier = Modifier.fillMaxSize()) {
var showCloudDialog by remember { mutableStateOf(false) }
var currentPage by rememberSaveable { mutableStateOf(AppPages.HOME) }

Expand Down
171 changes: 82 additions & 89 deletions composeApp/src/commonMain/kotlin/ui/home/HomeScreen.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package ui.home

import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.hoverable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
Expand All @@ -14,13 +14,20 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Close
import androidx.compose.material.icons.rounded.Refresh
import androidx.compose.material.icons.rounded.Search
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
import androidx.compose.material3.adaptive.layout.AnimatedPane
import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold
import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole
import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
Expand All @@ -40,15 +47,14 @@ import ui.utils.ComboBox
import viewmodels.VideoListViewModel


private val bottomPad = Modifier.padding(0.dp, 0.dp, 0.dp, 8.dp)
private val sidePad = Modifier.padding(8.dp, 0.dp, 0.dp, 0.dp)


@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun HomeScreen() {

var selectedVideo by mutableStateOf<VideoFile?>(null)
var showFileDetails by mutableStateOf(false)
val navigator = rememberListDetailPaneScaffoldNavigator<VideoFile>()

var loadingJob: Job? by remember { mutableStateOf(null) }
var loading by remember { mutableStateOf(true) }
Expand Down Expand Up @@ -83,52 +89,49 @@ fun HomeScreen() {
}
LaunchedEffect(Unit) { load() }

Column(modifier = Modifier.padding(16.dp).fillMaxWidth()) {
// Heading & status
Row(modifier = bottomPad) {
Text(
"Video files", style = MaterialTheme.typography.headlineLarge, modifier = sidePad
)
if (loading) {
CircularProgressIndicator(modifier = sidePad)
}
if (errorLoading) {
Text(
"Error loading list",
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.error
)
}
}

if (showFileDetails) {
selectedVideo?.let { v ->
FileDetailsDialog(v) { conv ->
showFileDetails = false
conv?.let { scope.launch { viewModel.submitJob(v, conv) } }
ListDetailPaneScaffold(
directive = navigator.scaffoldDirective,
value = navigator.scaffoldValue,
listPane = {
AnimatedPane {
if (loading) CircularProgressIndicator(modifier = sidePad)
if (errorLoading) {
Text(
"Error loading list",
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.error
)
}
PageContent(videoList, videoSelected = {
navigator.navigateTo(ListDetailPaneScaffoldRole.Detail, it)
}) {
scope.launch {
loading = true
viewModel.refresh()
load()
}
}
}
}
PageContent(videoList, videoSelected = {
selectedVideo = it
showFileDetails = true
}) {
scope.launch {
loading = true
viewModel.refresh()
load()
},
detailPane = {
AnimatedPane {
navigator.currentDestination?.content?.let { v ->
FileDetails(v) { conv ->
conv?.let { scope.launch { viewModel.submitJob(v, conv) } }
navigator.navigateBack()
}
}
}
}
}
)
}


@OptIn(ExperimentalFoundationApi::class)
@Composable
fun PageContent(list: List<VideoFile>, videoSelected: (VideoFile) -> Unit, onRefresh: () -> Unit) {
var filteredAudioCodec by mutableStateOf<String?>(null)
var filteredVideoCodec by mutableStateOf<String?>(null)
var filteredName by mutableStateOf("")
var filteredAudioCodec by remember { mutableStateOf<String?>(null) }
var filteredVideoCodec by remember { mutableStateOf<String?>(null) }
var filteredName by remember { mutableStateOf("") }

val filteredVideos = list.filter {
it.fileName.contains(
Expand All @@ -139,52 +142,37 @@ fun PageContent(list: List<VideoFile>, videoSelected: (VideoFile) -> Unit, onRef
} && it.audios.any { a -> filteredAudioCodec?.let { fa -> a.codec == fa } ?: true }
}
Column {
Row(modifier = bottomPad.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
val filterMod = Modifier.padding(8.dp, 0.dp).fillMaxWidth()
Column(modifier = filterMod, horizontalAlignment = Alignment.CenterHorizontally) {
val videoOptions = list.asSequence().flatMap { it.videos }.map { it.codec }.toSet().toList().sorted()
val audioOptions = list.asSequence().flatMap { it.audios }.map { it.codec }.toSet().toList().sorted()

OutlinedTextField(
value = filteredName,
modifier = sidePad,
modifier = filterMod,
onValueChange = { filteredName = it },
label = { Text("File name") },
leadingIcon = { Icon(Icons.Rounded.Search, contentDescription = "Search") })
val videoOptions = list.asSequence().flatMap { it.videos }.map { it.codec }.toSet().toList().sorted()
ComboBox("Video Codecs", videoOptions, filteredVideoCodec) { filteredVideoCodec = it }
val audioOptions = list.asSequence().flatMap { it.audios }.map { it.codec }.toSet().toList().sorted()
ComboBox("Audio Codecs", audioOptions, filteredAudioCodec) { filteredAudioCodec = it }
IconButton(modifier = sidePad, onClick = onRefresh) {
Icon(Icons.Rounded.Refresh, contentDescription = "Refresh")
}
// Clear filters
if (filteredName.isNotEmpty() || filteredAudioCodec != null || filteredVideoCodec != null) {
IconButton(onClick = {
filteredName = ""
filteredAudioCodec = null
filteredVideoCodec = null
}) {
Icon(Icons.Rounded.Close, contentDescription = "Clear filters")
ComboBox("Video Codecs", videoOptions, filteredVideoCodec, modifier = filterMod) { filteredVideoCodec = it }
ComboBox("Audio Codecs", audioOptions, filteredAudioCodec, modifier = filterMod) { filteredAudioCodec = it }
Row(horizontalArrangement = Arrangement.SpaceEvenly) {
IconButton(modifier = sidePad, onClick = onRefresh) {
Icon(Icons.Rounded.Refresh, contentDescription = "Refresh")
}
// Clear filters
if (filteredName.isNotEmpty() || filteredAudioCodec != null || filteredVideoCodec != null) {
IconButton(onClick = {
filteredName = ""
filteredAudioCodec = null
filteredVideoCodec = null
}) {
Icon(Icons.Rounded.Close, contentDescription = "Clear filters")
}
}
}
}
Row(modifier = bottomPad.fillMaxSize()) {
Row(modifier = Modifier.fillMaxSize().padding(8.dp)) {
LazyColumn {
stickyHeader {
Row(modifier = Modifier.padding(8.dp)) {
Text(
"File name",
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.weight(4f)
)
Text(
"Video Codecs",
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.weight(1f)
)
Text(
"Audio Codecs",
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.weight(1f)
)
}
}
items(filteredVideos) { file ->
VideoRow(file) { videoSelected(it) }
}
Expand All @@ -195,16 +183,21 @@ fun PageContent(list: List<VideoFile>, videoSelected: (VideoFile) -> Unit, onRef

@Composable
private fun VideoRow(file: VideoFile, onClick: (VideoFile) -> Unit) {
Column {
Surface(
onClick = { onClick(file) },
tonalElevation = 3.dp,
modifier = Modifier.hoverable(MutableInteractionSource())
) {
Row(modifier = Modifier.padding(8.dp)) {
Text(file.fileName, style = MaterialTheme.typography.bodyMedium, modifier = Modifier.weight(4f))
Text(file.videoInfo, style = MaterialTheme.typography.bodyMedium, modifier = Modifier.weight(1f))
Text(file.audioInfo, style = MaterialTheme.typography.bodyMedium, modifier = Modifier.weight(1f))
Card(
onClick = { onClick(file) },
modifier = Modifier.fillMaxWidth().padding(4.dp).hoverable(MutableInteractionSource()),
elevation = CardDefaults.cardElevation(3.dp)
) {
Column(modifier = Modifier.fillMaxWidth()) {
Text(
text = file.fileName,
style = MaterialTheme.typography.bodyLarge,
modifier = Modifier.fillMaxWidth().padding(8.dp)
)
HorizontalDivider(color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f))
Row(modifier = Modifier.fillMaxWidth().padding(8.dp), horizontalArrangement = Arrangement.SpaceEvenly) {
Text(text = file.videoInfo, style = MaterialTheme.typography.bodyMedium)
Text(text = file.audioInfo, style = MaterialTheme.typography.bodyMedium)
}
}
}
Expand Down
9 changes: 5 additions & 4 deletions composeApp/src/commonMain/kotlin/ui/utils/ComboBox.kt
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package ui.utils

import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.ArrowDropDown
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.ExposedDropdownMenuDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.MenuAnchorType
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
Expand All @@ -16,7 +17,6 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp


@OptIn(ExperimentalMaterial3Api::class)
Expand All @@ -25,17 +25,18 @@ fun <T> ComboBox(
title: String,
options: List<T>,
selected: T?,
modifier: Modifier = Modifier,
optionName: T.() -> String = { toString() },
onSelect: (T?) -> Unit
) {
var expanded by remember { mutableStateOf(false) }
ExposedDropdownMenuBox(
expanded,
onExpandedChange = { expanded = it },
modifier = Modifier.padding(8.dp, 0.dp, 0.dp, 0.dp)
modifier = modifier
) {
OutlinedTextField(
modifier = Modifier.menuAnchor(),
modifier = Modifier.fillMaxWidth().menuAnchor(MenuAnchorType.PrimaryNotEditable, true),
value = selected?.optionName() ?: "",
onValueChange = { },
readOnly = true,
Expand Down

0 comments on commit 57d21c0

Please sign in to comment.