Merge pull request #3533 from element-hq/feature/bma/fixCrashes

Fix various crashes
This commit is contained in:
Benoit Marty 2024-09-25 08:28:54 +02:00 committed by GitHub
commit b087874ad0
13 changed files with 146 additions and 51 deletions

View file

@ -281,7 +281,11 @@ class ElementCallActivity :
@RequiresApi(Build.VERSION_CODES.O) @RequiresApi(Build.VERSION_CODES.O)
override fun enterPipMode(): Boolean { override fun enterPipMode(): Boolean {
return enterPictureInPictureMode(getPictureInPictureParams()) return if (lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) {
enterPictureInPictureMode(getPictureInPictureParams())
} else {
false
}
} }
@RequiresApi(Build.VERSION_CODES.O) @RequiresApi(Build.VERSION_CODES.O)

View file

@ -27,6 +27,7 @@ import io.element.android.features.messages.impl.voicemessages.VoiceMessageExcep
import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.architecture.runUpdatingState import io.element.android.libraries.architecture.runUpdatingState
import io.element.android.libraries.core.extensions.flatMap
import io.element.android.libraries.di.RoomScope import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.ui.utils.time.formatShort import io.element.android.libraries.ui.utils.time.formatShort
import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analytics.api.AnalyticsService
@ -126,8 +127,8 @@ class VoiceMessagePresenter @AssistedInject constructor(
it it
}, },
) { ) {
player.prepare().apply { player.prepare().flatMap {
player.play() runCatching { player.play() }
} }
} }
} }

View file

@ -71,7 +71,10 @@ fun Context.copyToClipboard(
* Shows notification settings for the current app. * Shows notification settings for the current app.
* In android O will directly opens the notification settings, in lower version it will show the App settings * In android O will directly opens the notification settings, in lower version it will show the App settings
*/ */
fun Context.startNotificationSettingsIntent(activityResultLauncher: ActivityResultLauncher<Intent>? = null) { fun Context.startNotificationSettingsIntent(
activityResultLauncher: ActivityResultLauncher<Intent>? = null,
noActivityFoundMessage: String = getString(R.string.error_no_compatible_app_found),
) {
val intent = Intent() val intent = Intent()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
intent.action = Settings.ACTION_APP_NOTIFICATION_SETTINGS intent.action = Settings.ACTION_APP_NOTIFICATION_SETTINGS
@ -85,10 +88,14 @@ fun Context.startNotificationSettingsIntent(activityResultLauncher: ActivityResu
intent.data = Uri.fromParts("package", packageName, null) intent.data = Uri.fromParts("package", packageName, null)
} }
if (activityResultLauncher != null) { try {
activityResultLauncher.launch(intent) if (activityResultLauncher != null) {
} else { activityResultLauncher.launch(intent)
startActivity(intent) } else {
startActivity(intent)
}
} catch (activityNotFoundException: ActivityNotFoundException) {
toast(noActivityFoundMessage)
} }
} }

View file

@ -68,7 +68,6 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.RoomInfo import org.matrix.rustcomponents.sdk.RoomInfo
import org.matrix.rustcomponents.sdk.RoomInfoListener import org.matrix.rustcomponents.sdk.RoomInfoListener
@ -104,10 +103,12 @@ class RustMatrixRoom(
override val roomId = RoomId(innerRoom.id()) override val roomId = RoomId(innerRoom.id())
override val roomInfoFlow: Flow<MatrixRoomInfo> = mxCallbackFlow { override val roomInfoFlow: Flow<MatrixRoomInfo> = mxCallbackFlow {
launch { runCatching { innerRoom.roomInfo() }
val initial = innerRoom.roomInfo().let(matrixRoomInfoMapper::map) .getOrNull()
channel.trySend(initial) ?.let(matrixRoomInfoMapper::map)
} ?.let { initial ->
channel.trySend(initial)
}
innerRoom.subscribeToRoomInfoUpdates(object : RoomInfoListener { innerRoom.subscribeToRoomInfoUpdates(object : RoomInfoListener {
override fun call(roomInfo: RoomInfo) { override fun call(roomInfo: RoomInfo) {
channel.trySend(matrixRoomInfoMapper.map(roomInfo)) channel.trySend(matrixRoomInfoMapper.map(roomInfo))
@ -116,10 +117,8 @@ class RustMatrixRoom(
} }
override val roomTypingMembersFlow: Flow<List<UserId>> = mxCallbackFlow { override val roomTypingMembersFlow: Flow<List<UserId>> = mxCallbackFlow {
launch { val initial = emptyList<UserId>()
val initial = emptyList<UserId>() channel.trySend(initial)
channel.trySend(initial)
}
innerRoom.subscribeToTypingNotifications(object : TypingNotificationsListener { innerRoom.subscribeToTypingNotifications(object : TypingNotificationsListener {
override fun call(typingUserIds: List<String>) { override fun call(typingUserIds: List<String>) {
channel.trySend( channel.trySend(
@ -625,9 +624,13 @@ class RustMatrixRoom(
innerRoom.sendCallNotificationIfNeeded() innerRoom.sendCallNotificationIfNeeded()
} }
override suspend fun setSendQueueEnabled(enabled: Boolean) = withContext(roomDispatcher) { override suspend fun setSendQueueEnabled(enabled: Boolean) {
Timber.d("setSendQueuesEnabled: $enabled") withContext(roomDispatcher) {
innerRoom.enableSendQueue(enabled) Timber.d("setSendQueuesEnabled: $enabled")
runCatching {
innerRoom.enableSendQueue(enabled)
}
}
} }
override suspend fun saveComposerDraft(composerDraft: ComposerDraft): Result<Unit> = runCatching { override suspend fun saveComposerDraft(composerDraft: ComposerDraft): Result<Unit> = runCatching {

View file

@ -97,9 +97,7 @@ internal fun RoomListServiceInterface.stateFlow(): Flow<RoomListServiceState> =
trySendBlocking(state) trySendBlocking(state)
} }
} }
tryOrNull { state(listener)
state(listener)
}
}.buffer(Channel.UNLIMITED) }.buffer(Channel.UNLIMITED)
internal fun RoomListServiceInterface.syncIndicator(): Flow<RoomListServiceSyncIndicator> = internal fun RoomListServiceInterface.syncIndicator(): Flow<RoomListServiceSyncIndicator> =
@ -109,13 +107,11 @@ internal fun RoomListServiceInterface.syncIndicator(): Flow<RoomListServiceSyncI
trySendBlocking(syncIndicator) trySendBlocking(syncIndicator)
} }
} }
tryOrNull { syncIndicator(
syncIndicator( SYNC_INDICATOR_DELAY_BEFORE_SHOWING,
SYNC_INDICATOR_DELAY_BEFORE_SHOWING, SYNC_INDICATOR_DELAY_BEFORE_HIDING,
SYNC_INDICATOR_DELAY_BEFORE_HIDING, listener,
listener, )
)
}
}.buffer(Channel.UNLIMITED) }.buffer(Channel.UNLIMITED)
internal fun RoomListServiceInterface.roomOrNull(roomId: String): RoomListItem? { internal fun RoomListServiceInterface.roomOrNull(roomId: String): RoomListItem? {

View file

@ -7,7 +7,6 @@
package io.element.android.libraries.matrix.impl.sync package io.element.android.libraries.matrix.impl.sync
import io.element.android.libraries.core.data.tryOrNull
import io.element.android.libraries.matrix.impl.util.mxCallbackFlow import io.element.android.libraries.matrix.impl.util.mxCallbackFlow
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.trySendBlocking import kotlinx.coroutines.channels.trySendBlocking
@ -24,7 +23,5 @@ fun SyncServiceInterface.stateFlow(): Flow<SyncServiceState> =
trySendBlocking(state) trySendBlocking(state)
} }
} }
tryOrNull { state(listener)
state(listener)
}
}.buffer(Channel.UNLIMITED) }.buffer(Channel.UNLIMITED)

View file

@ -13,7 +13,7 @@ import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.callbackFlow
import org.matrix.rustcomponents.sdk.TaskHandle import org.matrix.rustcomponents.sdk.TaskHandle
internal fun <T> mxCallbackFlow(block: suspend ProducerScope<T>.() -> TaskHandle?) = internal fun <T> mxCallbackFlow(block: suspend ProducerScope<T>.() -> TaskHandle) =
callbackFlow { callbackFlow {
val taskHandle: TaskHandle? = tryOrNull { val taskHandle: TaskHandle? = tryOrNull {
block(this) block(this)

View file

@ -7,7 +7,9 @@
package io.element.android.libraries.mediapickers.api package io.element.android.libraries.mediapickers.api
import android.content.ActivityNotFoundException
import androidx.activity.compose.ManagedActivityResultLauncher import androidx.activity.compose.ManagedActivityResultLauncher
import timber.log.Timber
/** /**
* Wrapper around [ManagedActivityResultLauncher] to be used with media/file pickers. * Wrapper around [ManagedActivityResultLauncher] to be used with media/file pickers.
@ -25,11 +27,19 @@ class ComposePickerLauncher<Input, Output>(
private val defaultRequest: Input, private val defaultRequest: Input,
) : PickerLauncher<Input, Output> { ) : PickerLauncher<Input, Output> {
override fun launch() { override fun launch() {
managedLauncher.launch(defaultRequest) try {
managedLauncher.launch(defaultRequest)
} catch (activityNotFoundException: ActivityNotFoundException) {
Timber.w(activityNotFoundException, "No activity found")
}
} }
override fun launch(customInput: Input) { override fun launch(customInput: Input) {
managedLauncher.launch(customInput) try {
managedLauncher.launch(customInput)
} catch (activityNotFoundException: ActivityNotFoundException) {
Timber.w(activityNotFoundException, "No activity found")
}
} }
} }

View file

@ -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,30 @@ 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 { pdfRenderer = 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) pdfRenderer.apply {
mutablePdfPages.value = firstPages + nextPages // Preload just 3 pages so we can render faster
} val firstPages = loadPages(from = 0, to = 3)
mutablePdfPages.value = AsyncData.Success(firstPages.toImmutableList())
val nextPages = loadPages(from = 3, to = pageCount)
mutablePdfPages.value = AsyncData.Success((firstPages + nextPages).toImmutableList())
}
},
onFailure = {
mutablePdfPages.value = AsyncData.Failure(it)
null
}
)
} }
} }
} }
@ -47,7 +60,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()

View file

@ -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
private 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"),
)
}

View file

@ -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) {

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:cb0e18f25f8cd978830eb2a4db0cbef76376cdd34c025dc5e21dca39f0b8bd25
size 13315

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:336450bda0f5438f30b14fdb9d3a1d95ff9fe3a9ff325c4f860c50dbf6e7256b
size 13056