Fix crash when pdf cannot be opened.
https://sentry.tools.element.io/organizations/element/issues/1570011/events/efcaaf7382194fd4b7991ca6b05e7532/
This commit is contained in:
parent
efe47d6c80
commit
4d91260a8e
3 changed files with 83 additions and 14 deletions
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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…
Add table
Add a link
Reference in a new issue