Benoit Marty 3 weeks ago
parent
commit
92834e8985
  1. 31
      libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/pdf/PdfRendererManager.kt
  2. 60
      libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/pdf/PdfViewer.kt
  3. 6
      libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/pdf/PdfViewerState.kt

31
libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/pdf/PdfRendererManager.kt

@ -9,6 +9,9 @@ package io.element.android.libraries.mediaviewer.api.local.pdf
import android.graphics.pdf.PdfRenderer import android.graphics.pdf.PdfRenderer
import android.os.ParcelFileDescriptor import android.os.ParcelFileDescriptor
import io.element.android.libraries.architecture.AsyncData
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@ -25,20 +28,28 @@ class PdfRendererManager(
) { ) {
private val mutex = Mutex() private val mutex = Mutex()
private var pdfRenderer: PdfRenderer? = null private var pdfRenderer: PdfRenderer? = null
private val mutablePdfPages = MutableStateFlow<List<PdfPage>>(emptyList()) private val mutablePdfPages = MutableStateFlow<AsyncData<ImmutableList<PdfPage>>>(AsyncData.Uninitialized)
val pdfPages: StateFlow<List<PdfPage>> = mutablePdfPages val pdfPages: StateFlow<AsyncData<ImmutableList<PdfPage>>> = mutablePdfPages
fun open() { fun open() {
coroutineScope.launch { coroutineScope.launch {
mutex.withLock { mutex.withLock {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
pdfRenderer = PdfRenderer(parcelFileDescriptor).apply { runCatching {
// Preload just 3 pages so we can render faster PdfRenderer(parcelFileDescriptor)
val firstPages = loadPages(from = 0, to = 3) }.fold(
mutablePdfPages.value = firstPages onSuccess = { pdfRenderer ->
val nextPages = loadPages(from = 3, to = pageCount) this@PdfRendererManager.pdfRenderer = pdfRenderer
mutablePdfPages.value = firstPages + nextPages // Preload just 3 pages so we can render faster
} val firstPages = pdfRenderer.loadPages(from = 0, to = 3)
mutablePdfPages.value = AsyncData.Success(firstPages.toImmutableList())
val nextPages = pdfRenderer.loadPages(from = 3, to = pdfRenderer.pageCount)
mutablePdfPages.value = AsyncData.Success((firstPages + nextPages).toImmutableList())
},
onFailure = {
mutablePdfPages.value = AsyncData.Failure(it)
}
)
} }
} }
} }
@ -47,7 +58,7 @@ class PdfRendererManager(
fun close() { fun close() {
coroutineScope.launch { coroutineScope.launch {
mutex.withLock { mutex.withLock {
mutablePdfPages.value.forEach { pdfPage -> mutablePdfPages.value.dataOrNull()?.forEach { pdfPage ->
pdfPage.close() pdfPage.close()
} }
pdfRenderer?.close() pdfRenderer?.close()

60
libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/pdf/PdfViewer.kt

@ -28,13 +28,19 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.text.roundToPx import io.element.android.libraries.designsystem.text.roundToPx
import io.element.android.libraries.designsystem.text.toDp import io.element.android.libraries.designsystem.text.toDp
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import me.saket.telephoto.zoomable.zoomable import me.saket.telephoto.zoomable.zoomable
import java.io.IOException
@Composable @Composable
fun PdfViewer( fun PdfViewer(
@ -59,7 +65,7 @@ fun PdfViewer(
} }
val pdfPages = pdfViewerState.getPages() val pdfPages = pdfViewerState.getPages()
PdfPagesView( PdfPagesView(
pdfPages = pdfPages.toImmutableList(), pdfPages = pdfPages,
lazyListState = pdfViewerState.lazyListState, lazyListState = pdfViewerState.lazyListState,
) )
} }
@ -67,6 +73,48 @@ fun PdfViewer(
@Composable @Composable
private fun PdfPagesView( private fun PdfPagesView(
pdfPages: AsyncData<ImmutableList<PdfPage>>,
lazyListState: LazyListState,
modifier: Modifier = Modifier,
) {
when (pdfPages) {
is AsyncData.Uninitialized,
is AsyncData.Loading -> Unit
is AsyncData.Failure -> PdfPagesErrorView(
pdfPages.error,
modifier,
)
is AsyncData.Success -> PdfPagesContentView(
pdfPages = pdfPages.data,
lazyListState = lazyListState,
modifier = modifier
)
}
}
@Composable
fun PdfPagesErrorView(
error: Throwable,
modifier: Modifier = Modifier,
) {
Box(
modifier = modifier.fillMaxSize(),
contentAlignment = Alignment.Center,
) {
Text(
text = buildString {
append(stringResource(id = CommonStrings.error_unknown))
append("\n\n")
append(error.localizedMessage)
},
textAlign = TextAlign.Center,
style = ElementTheme.typography.fontBodyLgRegular,
)
}
}
@Composable
private fun PdfPagesContentView(
pdfPages: ImmutableList<PdfPage>, pdfPages: ImmutableList<PdfPage>,
lazyListState: LazyListState, lazyListState: LazyListState,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
@ -117,3 +165,11 @@ private fun PdfPageView(
} }
} }
} }
@PreviewsDayNight
@Composable
internal fun PdfPagesErrorViewPreview() = ElementPreview {
PdfPagesErrorView(
error = IOException("file not in PDF format or corrupted"),
)
}

6
libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/pdf/PdfViewerState.kt

@ -19,6 +19,8 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import io.element.android.libraries.architecture.AsyncData
import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import me.saket.telephoto.zoomable.ZoomableState import me.saket.telephoto.zoomable.ZoomableState
import me.saket.telephoto.zoomable.rememberZoomableState import me.saket.telephoto.zoomable.rememberZoomableState
@ -35,10 +37,10 @@ class PdfViewerState(
private var pdfRendererManager by mutableStateOf<PdfRendererManager?>(null) private var pdfRendererManager by mutableStateOf<PdfRendererManager?>(null)
@Composable @Composable
fun getPages(): List<PdfPage> { fun getPages(): AsyncData<ImmutableList<PdfPage>> {
return pdfRendererManager?.run { return pdfRendererManager?.run {
pdfPages.collectAsState().value pdfPages.collectAsState().value
} ?: emptyList() } ?: AsyncData.Uninitialized
} }
fun openForWidth(maxWidth: Int) { fun openForWidth(maxWidth: Int) {

Loading…
Cancel
Save