Restore viewer window state and handle photo decoding errors
This commit is contained in:
1 parent
bd4bd47e00
commit
efe421b162
2 files changed
+69
-23
No files matched your search
+10
-23
@@ -2,9 +2,6 @@ package com.utun.chat.ui.screens
|
||||
|
||||
import com.utun.chat.R
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import android.graphics.Bitmap
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.background
|
||||
@@ -19,43 +16,33 @@ import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
import com.utun.chat.data.LogManager
|
||||
import com.utun.chat.data.PhotoUtil
|
||||
import com.utun.chat.ui.components.ZoomableImage
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
private const val MAX_DECODE_DIM = 4096
|
||||
|
||||
private tailrec fun Context.findActivity(): Activity? = when (this) {
|
||||
is Activity -> this
|
||||
is ContextWrapper -> baseContext.findActivity()
|
||||
else -> null
|
||||
}
|
||||
|
||||
/** Локальное изображение с EXIF и Fit/zoom; декодирование в IO ограничено стороной 4096 пикселей.
|
||||
* Экран владеет Bitmap через Compose; смена пути отменяет доставку старого результата. Ошибка оставляет доступным выход. */
|
||||
@Composable
|
||||
fun PhotoViewerScreen(filePath: String, onClose: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
var bmp by remember(filePath) { mutableStateOf<Bitmap?>(null) }
|
||||
var loaded by remember(filePath) { mutableStateOf(false) }
|
||||
BackHandler(onBack = onClose)
|
||||
|
||||
/* ── immersive: hide system bars ── */
|
||||
DisposableEffect(Unit) {
|
||||
val act = context.findActivity()
|
||||
val controller = act?.let { WindowCompat.getInsetsController(it.window, it.window.decorView) }
|
||||
controller?.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||
controller?.hide(WindowInsetsCompat.Type.systemBars())
|
||||
onDispose { controller?.show(WindowInsetsCompat.Type.systemBars()) }
|
||||
}
|
||||
ViewerFullscreenWindow("photo")
|
||||
|
||||
LaunchedEffect(filePath) {
|
||||
bmp = withContext(Dispatchers.IO) { PhotoUtil.decodeFitted(filePath, MAX_DECODE_DIM) }
|
||||
bmp = try { withContext(Dispatchers.IO) { PhotoUtil.decodeFitted(filePath, MAX_DECODE_DIM) } }
|
||||
catch (e: CancellationException) { throw e }
|
||||
catch (e: Exception) {
|
||||
LogManager.addLog("ERROR", "PhotoViewer", "decode exception path=$filePath: $e")
|
||||
null
|
||||
}
|
||||
loaded = true
|
||||
if (bmp == null) LogManager.addLog("ERROR", "PhotoViewer", "decode failed: $filePath")
|
||||
else LogManager.addLog("DEBUG", "PhotoViewer", "Opened image ${bmp?.width}x${bmp?.height} path=$filePath")
|
||||
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
package com.utun.chat.ui.screens
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.ContextWrapper
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
import com.utun.chat.data.LogManager
|
||||
|
||||
/** Immersive-режим viewer без смены ориентации; возвращает прежнюю видимость обеих панелей и поведение swipe.
|
||||
* Вызывающий экран должен быть единственным владельцем fullscreen окна. identity — диагностическое имя без секретов. */
|
||||
@Composable
|
||||
internal fun ViewerFullscreenWindow(identity: String) {
|
||||
val context = LocalContext.current
|
||||
val activity = remember(context) {
|
||||
generateSequence(context) { (it as? ContextWrapper)?.baseContext }.filterIsInstance<Activity>().firstOrNull()
|
||||
}
|
||||
DisposableEffect(activity, identity) {
|
||||
val window = activity?.window
|
||||
val controller = window?.let { WindowCompat.getInsetsController(it, it.decorView) }
|
||||
val insets = window?.let { ViewCompat.getRootWindowInsets(it.decorView) }
|
||||
val statusVisible = insets?.isVisible(WindowInsetsCompat.Type.statusBars()) ?: true
|
||||
val navigationVisible = insets?.isVisible(WindowInsetsCompat.Type.navigationBars()) ?: true
|
||||
val behavior = controller?.systemBarsBehavior
|
||||
if (controller != null) try {
|
||||
controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||
controller.hide(WindowInsetsCompat.Type.systemBars())
|
||||
LogManager.addLog("INFO", "Viewer", "Fullscreen entered viewer=$identity")
|
||||
} catch (e: Exception) {
|
||||
LogManager.addLog("ERROR", "Viewer", "Cannot enter fullscreen viewer=$identity: $e")
|
||||
}
|
||||
onDispose {
|
||||
if (controller != null && behavior != null) {
|
||||
val restore: List<Pair<String, () -> Unit>> = listOf(
|
||||
"behavior" to { controller.systemBarsBehavior = behavior },
|
||||
"status" to {
|
||||
if (statusVisible) controller.show(WindowInsetsCompat.Type.statusBars())
|
||||
else controller.hide(WindowInsetsCompat.Type.statusBars())
|
||||
},
|
||||
"navigation" to {
|
||||
if (navigationVisible) controller.show(WindowInsetsCompat.Type.navigationBars())
|
||||
else controller.hide(WindowInsetsCompat.Type.navigationBars())
|
||||
}
|
||||
)
|
||||
var failures = 0
|
||||
for ((part, action) in restore) try { action() } catch (e: Exception) {
|
||||
failures++
|
||||
LogManager.addLog("ERROR", "Viewer", "Window restore failed viewer=$identity part=$part: $e")
|
||||
}
|
||||
if (failures == 0) LogManager.addLog("INFO", "Viewer", "Window restored viewer=$identity")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user