archive-97f2350/app/src/main/java/uk/orllewin/sianel/ui/UploadScreen.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
package uk.orllewin.sianel.ui
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Checkbox
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import uk.orllewin.sianel.R
import uk.orllewin.sianel.data.Quality
import uk.orllewin.sianel.ui.components.upload.QualityPicker
import uk.orllewin.sianel.ui.components.upload.ThumbnailStrip
import uk.orllewin.sianel.ui.components.upload.VideoPreview
@Composable
fun UploadScreen(
viewModel: MainViewModel,
onUpload: () -> Unit,
onCancel: () -> Unit,
paddingValues: PaddingValues = PaddingValues(0.dp),
) {
val pending = viewModel.pending
if (pending == null) {
// Defensive: a stale UploadRoute with nothing staged — bounce back to the list.
LaunchedEffect(Unit) { onCancel() }
return
}
Column(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp),
) {
VideoPreview(uri = pending.uri)
ThumbnailStrip(
thumbnails = viewModel.thumbnails,
selected = viewModel.selectedThumbnailIndex,
onSelect = viewModel::selectThumbnail,
)
Text(pending.filename, style = MaterialTheme.typography.titleMedium)
Text(
stringResource(R.string.source, fmtSize(pending.sizeBytes)),
style = MaterialTheme.typography.bodyMedium,
)
QualityPicker(
selected = viewModel.quality,
onChange = viewModel::selectQuality,
estimateFor = { estimateSize(it, pending) },
)
Row(
modifier = Modifier
.fillMaxWidth()
.clickable { viewModel.updateUnlisted(!viewModel.unlisted) }
.padding(vertical = 4.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Checkbox(checked = viewModel.unlisted, onCheckedChange = viewModel::updateUnlisted)
Column(modifier = Modifier.padding(start = 8.dp)) {
Text(stringResource(R.string.unlisted), style = MaterialTheme.typography.bodyMedium)
Text(
stringResource(R.string.hidden_from_the_public_page_still_reachable_by_direct_link),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
}
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
Button(onClick = onUpload) { Text(stringResource(R.string.upload)) }
OutlinedButton(onClick = onCancel) { Text(stringResource(R.string.cancel)) }
}
}
}
private fun estimateSize(
quality: Quality,
pending: MainViewModel.PendingVideo,
): Long? {
if (!quality.transcodes) return pending.sizeBytes.takeIf { it > 0 }
val targetHeight = quality.targetHeight ?: return null
val bitrate = quality.bitrate ?: return null
val srcHeight = pending.heightPx
// Mirror UploadWorker.shouldTranscode: if source ≤ target the worker skips
// transcoding and uploads the source file as-is.
if (srcHeight != null && srcHeight <= targetHeight) {
return pending.sizeBytes.takeIf { it > 0 }
}
val durationMs = pending.durationMs ?: return null
val audioBps = 128_000L
val totalBits = (bitrate.toLong() + audioBps) * durationMs / 1000L
return totalBits / 8L
}