Compare commits
2 Commits
eef4f61050
...
fbd228b768
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fbd228b768 | ||
|
|
309031aed7 |
@@ -94,6 +94,10 @@ class AppState(private val configStore: ConfigStore) {
|
||||
private val _selectedDevice = MutableStateFlow<DeviceInfo?>(null)
|
||||
val selectedDevice: StateFlow<DeviceInfo?> = _selectedDevice.asStateFlow()
|
||||
|
||||
/** Incremented after each thumbnail batch completes — DenseThumb observes this to retry */
|
||||
private val _thumbnailVersion = MutableStateFlow(0)
|
||||
val thumbnailVersion: StateFlow<Int> = _thumbnailVersion.asStateFlow()
|
||||
|
||||
fun scanForDevices() {
|
||||
scope.launch {
|
||||
_deviceScan.value = DeviceScanState.Scanning
|
||||
@@ -240,6 +244,10 @@ class AppState(private val configStore: ConfigStore) {
|
||||
}
|
||||
}
|
||||
loaded += batchLoaded
|
||||
if (batchLoaded > 0) {
|
||||
// Notify UI that new thumbnails are available
|
||||
_thumbnailVersion.value++
|
||||
}
|
||||
log.info("iOS thumbnails: $loaded / ${needed.size} loaded (batch ${batchIdx + 1}/${(needed.size + batchSize - 1) / batchSize})")
|
||||
} catch (e: Exception) {
|
||||
log.warn("iOS thumbnail batch ${batchIdx + 1} failed: ${e.message}")
|
||||
|
||||
@@ -92,6 +92,7 @@ fun BrowseScreen(state: AppState) {
|
||||
|
||||
VerticalDivider()
|
||||
|
||||
val thumbnailVersion by state.thumbnailVersion.collectAsState()
|
||||
DensePhotoGrid(
|
||||
folderSections = folderSections,
|
||||
selectedPhotos = selectedPhotos,
|
||||
@@ -101,6 +102,7 @@ fun BrowseScreen(state: AppState) {
|
||||
state.connectorFor(d).pullToFile(d, devicePath, localPath)
|
||||
} ?: false
|
||||
},
|
||||
thumbnailVersion = thumbnailVersion,
|
||||
columnCount = columnCount,
|
||||
listState = listState,
|
||||
onTogglePhoto = { state.togglePhotoSelection(it) },
|
||||
|
||||
@@ -31,6 +31,8 @@ import java.time.LocalDate
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
private val thumbLog = org.slf4j.LoggerFactory.getLogger("DenseThumb")
|
||||
|
||||
/**
|
||||
* Dense grid: folder headers + flat photo grid per folder.
|
||||
* Each photo that is the first of its day gets a small date label
|
||||
@@ -43,6 +45,7 @@ fun DensePhotoGrid(
|
||||
selectedPhotos: Set<PhonePhoto>,
|
||||
cacheKey: String,
|
||||
pullFn: suspend (devicePath: String, localDestPath: Path) -> Boolean,
|
||||
thumbnailVersion: Int = 0,
|
||||
columnCount: Int,
|
||||
listState: LazyListState,
|
||||
onTogglePhoto: (PhonePhoto) -> Unit,
|
||||
@@ -140,6 +143,7 @@ fun DensePhotoGrid(
|
||||
isSelected = labeled.photo in selectedPhotos,
|
||||
cacheKey = cacheKey,
|
||||
pullFn = pullFn,
|
||||
thumbnailVersion = thumbnailVersion,
|
||||
onToggle = { onTogglePhoto(labeled.photo) },
|
||||
dateLabel = labeled.dateLabel,
|
||||
modifier = Modifier.weight(1f),
|
||||
@@ -162,15 +166,16 @@ private fun DenseThumb(
|
||||
isSelected: Boolean,
|
||||
cacheKey: String,
|
||||
pullFn: suspend (devicePath: String, localDestPath: Path) -> Boolean,
|
||||
thumbnailVersion: Int,
|
||||
onToggle: () -> Unit,
|
||||
dateLabel: String?,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
var thumbnail by remember(photo.devicePath) { mutableStateOf<ImageBitmap?>(null) }
|
||||
var thumbFailed by remember(photo.devicePath) { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(photo.devicePath) {
|
||||
if (!thumbFailed && thumbnail == null) {
|
||||
// Re-run when thumbnailVersion increments (new batch cached) or on first composition
|
||||
LaunchedEffect(photo.devicePath, thumbnailVersion) {
|
||||
if (thumbnail == null) {
|
||||
val bytes = withContext(Dispatchers.IO) {
|
||||
ThumbnailCache.get(
|
||||
cacheKey = cacheKey,
|
||||
@@ -179,12 +184,12 @@ private fun DenseThumb(
|
||||
pullFn = pullFn,
|
||||
)
|
||||
}
|
||||
if (bytes != null) {
|
||||
if (bytes != null && bytes.isNotEmpty()) {
|
||||
runCatching {
|
||||
thumbnail = SkiaImage.makeFromEncoded(bytes).toComposeImageBitmap()
|
||||
}.onFailure { thumbFailed = true }
|
||||
} else {
|
||||
thumbFailed = true
|
||||
}.onFailure { e ->
|
||||
thumbLog.warn("Decode failed for ${photo.filename} (${bytes.size} bytes): ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user