Fix Android manage thumbnails and multi-select picker

Thumbnails were blank on the manage screen because the app fetched them from
the static /thumbs/ path, which a front-door botwall on `location /` blocks for
a non-browser client even though /api/ (proxied to the app) works. Add an authed
GET /api/thumb/{slug} that serves the thumbnail JPEG from public/thumbs/, and
have the app fetch from there. Also show the thumbnail in the edit dialog, not
just the list rows.

Construct the photo picker with an explicit maxItems so it reliably opens in
multi-select mode; the no-arg PickMultipleVisualMedia default has been seen to
present single-select on some devices.
This commit is contained in:
puttaalu committed 2026-10-19 15:33:25 +02:00
1 parent 125b6b5241
commit 2143b95657
6 files changed
+50 -3

No files matched your search

@@ -40,8 +40,11 @@ class MainActivity : ComponentActivity() {
var showSettings by rememberSaveable { mutableStateOf(!viewModel.isConfigured) }
var showManage by rememberSaveable { mutableStateOf(false) }
// Explicit maxItems so the system photo picker reliably opens in
// multi-select mode (the no-arg default has been seen to present
// single-select on some devices).
val picker = rememberLauncherForActivityResult(
ActivityResultContracts.PickMultipleVisualMedia(),
ActivityResultContracts.PickMultipleVisualMedia(maxItems = 50),
) { uris -> if (uris.isNotEmpty()) viewModel.selectImages(uris) }
val notificationPermission = rememberLauncherForActivityResult(
@@ -222,6 +222,19 @@ private fun EditDialog(
title = { Text("Edit ${post.slug}") },
text = {
Column {
val preview = post.preview
if (preview != null) {
Image(
bitmap = preview.asImageBitmap(),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier
.fillMaxWidth()
.height(160.dp)
.background(GruvBg1),
)
Spacer(Modifier.height(10.dp))
}
OutlinedTextField(
value = caption,
onValueChange = { caption = it },
@@ -283,7 +283,10 @@ class UploadViewModel(app: Application) : AndroidViewModel(app) {
private fun loadThumbnails(photos: List<RemotePhoto>) {
for (p in photos) {
viewModelScope.launch {
val bmp = withContext(Dispatchers.IO) { client().fetchThumbnail(p.thumb) }
// Fetch via the authed /api/thumb/<slug> endpoint, not the static
// /thumbs/ path: a front-door botwall can block this non-browser
// client from the static site while /api/ is proxied to the app.
val bmp = withContext(Dispatchers.IO) { client().fetchThumbnail("/api/thumb/" + p.slug) }
if (bmp != null) updateManage(p.slug) { it.copy(preview = bmp) }
}
}