Merge pull request #3533 from element-hq/feature/bma/fixCrashes
Fix various crashes
This commit is contained in:
@@ -281,7 +281,11 @@ class ElementCallActivity :
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
override fun enterPipMode(): Boolean {
|
||||
return enterPictureInPictureMode(getPictureInPictureParams())
|
||||
return if (lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) {
|
||||
enterPictureInPictureMode(getPictureInPictureParams())
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
|
||||
@@ -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.Presenter
|
||||
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.ui.utils.time.formatShort
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
@@ -126,8 +127,8 @@ class VoiceMessagePresenter @AssistedInject constructor(
|
||||
it
|
||||
},
|
||||
) {
|
||||
player.prepare().apply {
|
||||
player.play()
|
||||
player.prepare().flatMap {
|
||||
runCatching { player.play() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,10 @@ fun Context.copyToClipboard(
|
||||
* 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
|
||||
*/
|
||||
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()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
intent.action = Settings.ACTION_APP_NOTIFICATION_SETTINGS
|
||||
@@ -85,10 +88,14 @@ fun Context.startNotificationSettingsIntent(activityResultLauncher: ActivityResu
|
||||
intent.data = Uri.fromParts("package", packageName, null)
|
||||
}
|
||||
|
||||
if (activityResultLauncher != null) {
|
||||
activityResultLauncher.launch(intent)
|
||||
} else {
|
||||
startActivity(intent)
|
||||
try {
|
||||
if (activityResultLauncher != null) {
|
||||
activityResultLauncher.launch(intent)
|
||||
} else {
|
||||
startActivity(intent)
|
||||
}
|
||||
} catch (activityNotFoundException: ActivityNotFoundException) {
|
||||
toast(noActivityFoundMessage)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -68,7 +68,6 @@ import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.matrix.rustcomponents.sdk.RoomInfo
|
||||
import org.matrix.rustcomponents.sdk.RoomInfoListener
|
||||
@@ -104,10 +103,12 @@ class RustMatrixRoom(
|
||||
override val roomId = RoomId(innerRoom.id())
|
||||
|
||||
override val roomInfoFlow: Flow<MatrixRoomInfo> = mxCallbackFlow {
|
||||
launch {
|
||||
val initial = innerRoom.roomInfo().let(matrixRoomInfoMapper::map)
|
||||
channel.trySend(initial)
|
||||
}
|
||||
runCatching { innerRoom.roomInfo() }
|
||||
.getOrNull()
|
||||
?.let(matrixRoomInfoMapper::map)
|
||||
?.let { initial ->
|
||||
channel.trySend(initial)
|
||||
}
|
||||
innerRoom.subscribeToRoomInfoUpdates(object : RoomInfoListener {
|
||||
override fun call(roomInfo: RoomInfo) {
|
||||
channel.trySend(matrixRoomInfoMapper.map(roomInfo))
|
||||
@@ -116,10 +117,8 @@ class RustMatrixRoom(
|
||||
}
|
||||
|
||||
override val roomTypingMembersFlow: Flow<List<UserId>> = mxCallbackFlow {
|
||||
launch {
|
||||
val initial = emptyList<UserId>()
|
||||
channel.trySend(initial)
|
||||
}
|
||||
val initial = emptyList<UserId>()
|
||||
channel.trySend(initial)
|
||||
innerRoom.subscribeToTypingNotifications(object : TypingNotificationsListener {
|
||||
override fun call(typingUserIds: List<String>) {
|
||||
channel.trySend(
|
||||
@@ -625,9 +624,13 @@ class RustMatrixRoom(
|
||||
innerRoom.sendCallNotificationIfNeeded()
|
||||
}
|
||||
|
||||
override suspend fun setSendQueueEnabled(enabled: Boolean) = withContext(roomDispatcher) {
|
||||
Timber.d("setSendQueuesEnabled: $enabled")
|
||||
innerRoom.enableSendQueue(enabled)
|
||||
override suspend fun setSendQueueEnabled(enabled: Boolean) {
|
||||
withContext(roomDispatcher) {
|
||||
Timber.d("setSendQueuesEnabled: $enabled")
|
||||
runCatching {
|
||||
innerRoom.enableSendQueue(enabled)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun saveComposerDraft(composerDraft: ComposerDraft): Result<Unit> = runCatching {
|
||||
|
||||
@@ -97,9 +97,7 @@ internal fun RoomListServiceInterface.stateFlow(): Flow<RoomListServiceState> =
|
||||
trySendBlocking(state)
|
||||
}
|
||||
}
|
||||
tryOrNull {
|
||||
state(listener)
|
||||
}
|
||||
state(listener)
|
||||
}.buffer(Channel.UNLIMITED)
|
||||
|
||||
internal fun RoomListServiceInterface.syncIndicator(): Flow<RoomListServiceSyncIndicator> =
|
||||
@@ -109,13 +107,11 @@ internal fun RoomListServiceInterface.syncIndicator(): Flow<RoomListServiceSyncI
|
||||
trySendBlocking(syncIndicator)
|
||||
}
|
||||
}
|
||||
tryOrNull {
|
||||
syncIndicator(
|
||||
SYNC_INDICATOR_DELAY_BEFORE_SHOWING,
|
||||
SYNC_INDICATOR_DELAY_BEFORE_HIDING,
|
||||
listener,
|
||||
)
|
||||
}
|
||||
syncIndicator(
|
||||
SYNC_INDICATOR_DELAY_BEFORE_SHOWING,
|
||||
SYNC_INDICATOR_DELAY_BEFORE_HIDING,
|
||||
listener,
|
||||
)
|
||||
}.buffer(Channel.UNLIMITED)
|
||||
|
||||
internal fun RoomListServiceInterface.roomOrNull(roomId: String): RoomListItem? {
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
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 kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.channels.trySendBlocking
|
||||
@@ -24,7 +23,5 @@ fun SyncServiceInterface.stateFlow(): Flow<SyncServiceState> =
|
||||
trySendBlocking(state)
|
||||
}
|
||||
}
|
||||
tryOrNull {
|
||||
state(listener)
|
||||
}
|
||||
state(listener)
|
||||
}.buffer(Channel.UNLIMITED)
|
||||
|
||||
@@ -13,7 +13,7 @@ import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
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 {
|
||||
val taskHandle: TaskHandle? = tryOrNull {
|
||||
block(this)
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
|
||||
package io.element.android.libraries.mediapickers.api
|
||||
|
||||
import android.content.ActivityNotFoundException
|
||||
import androidx.activity.compose.ManagedActivityResultLauncher
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* Wrapper around [ManagedActivityResultLauncher] to be used with media/file pickers.
|
||||
@@ -25,11 +27,19 @@ class ComposePickerLauncher<Input, Output>(
|
||||
private val defaultRequest: Input,
|
||||
) : PickerLauncher<Input, Output> {
|
||||
override fun launch() {
|
||||
managedLauncher.launch(defaultRequest)
|
||||
try {
|
||||
managedLauncher.launch(defaultRequest)
|
||||
} catch (activityNotFoundException: ActivityNotFoundException) {
|
||||
Timber.w(activityNotFoundException, "No activity found")
|
||||
}
|
||||
}
|
||||
|
||||
override fun launch(customInput: Input) {
|
||||
managedLauncher.launch(customInput)
|
||||
try {
|
||||
managedLauncher.launch(customInput)
|
||||
} catch (activityNotFoundException: ActivityNotFoundException) {
|
||||
Timber.w(activityNotFoundException, "No activity found")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,9 @@ package io.element.android.libraries.mediaviewer.api.local.pdf
|
||||
|
||||
import android.graphics.pdf.PdfRenderer
|
||||
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.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
@@ -25,20 +28,30 @@ class PdfRendererManager(
|
||||
) {
|
||||
private val mutex = Mutex()
|
||||
private var pdfRenderer: PdfRenderer? = null
|
||||
private val mutablePdfPages = MutableStateFlow<List<PdfPage>>(emptyList())
|
||||
val pdfPages: StateFlow<List<PdfPage>> = mutablePdfPages
|
||||
private val mutablePdfPages = MutableStateFlow<AsyncData<ImmutableList<PdfPage>>>(AsyncData.Uninitialized)
|
||||
val pdfPages: StateFlow<AsyncData<ImmutableList<PdfPage>>> = mutablePdfPages
|
||||
|
||||
fun open() {
|
||||
coroutineScope.launch {
|
||||
mutex.withLock {
|
||||
withContext(Dispatchers.IO) {
|
||||
pdfRenderer = PdfRenderer(parcelFileDescriptor).apply {
|
||||
// Preload just 3 pages so we can render faster
|
||||
val firstPages = loadPages(from = 0, to = 3)
|
||||
mutablePdfPages.value = firstPages
|
||||
val nextPages = loadPages(from = 3, to = pageCount)
|
||||
mutablePdfPages.value = firstPages + nextPages
|
||||
}
|
||||
pdfRenderer = runCatching {
|
||||
PdfRenderer(parcelFileDescriptor)
|
||||
}.fold(
|
||||
onSuccess = { pdfRenderer ->
|
||||
pdfRenderer.apply {
|
||||
// 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() {
|
||||
coroutineScope.launch {
|
||||
mutex.withLock {
|
||||
mutablePdfPages.value.forEach { pdfPage ->
|
||||
mutablePdfPages.value.dataOrNull()?.forEach { pdfPage ->
|
||||
pdfPage.close()
|
||||
}
|
||||
pdfRenderer?.close()
|
||||
|
||||
@@ -28,13 +28,19 @@ import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
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.toDp
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import me.saket.telephoto.zoomable.zoomable
|
||||
import java.io.IOException
|
||||
|
||||
@Composable
|
||||
fun PdfViewer(
|
||||
@@ -59,7 +65,7 @@ fun PdfViewer(
|
||||
}
|
||||
val pdfPages = pdfViewerState.getPages()
|
||||
PdfPagesView(
|
||||
pdfPages = pdfPages.toImmutableList(),
|
||||
pdfPages = pdfPages,
|
||||
lazyListState = pdfViewerState.lazyListState,
|
||||
)
|
||||
}
|
||||
@@ -67,6 +73,48 @@ fun PdfViewer(
|
||||
|
||||
@Composable
|
||||
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>,
|
||||
lazyListState: LazyListState,
|
||||
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.setValue
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import me.saket.telephoto.zoomable.ZoomableState
|
||||
import me.saket.telephoto.zoomable.rememberZoomableState
|
||||
@@ -35,10 +37,10 @@ class PdfViewerState(
|
||||
private var pdfRendererManager by mutableStateOf<PdfRendererManager?>(null)
|
||||
|
||||
@Composable
|
||||
fun getPages(): List<PdfPage> {
|
||||
fun getPages(): AsyncData<ImmutableList<PdfPage>> {
|
||||
return pdfRendererManager?.run {
|
||||
pdfPages.collectAsState().value
|
||||
} ?: emptyList()
|
||||
} ?: AsyncData.Uninitialized
|
||||
}
|
||||
|
||||
fun openForWidth(maxWidth: Int) {
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user