mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-10-31 08:15:02 +00:00
Merge pull request #7901 from element-hq/feature/bma/mainThreadBlocking
Avoid blocking the main thread in several places
This commit is contained in:
+51
-38
@@ -43,6 +43,7 @@ import io.element.android.features.messages.impl.timeline.TimelineController
|
||||
import io.element.android.features.messages.impl.utils.TextPillificationHelper
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.core.extensions.runCatchingExceptions
|
||||
import io.element.android.libraries.core.mimetype.MimeTypes
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
||||
@@ -110,6 +111,7 @@ import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.merge
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
import io.element.android.libraries.core.mimetype.MimeTypes.Any as AnyMimeTypes
|
||||
@@ -145,6 +147,7 @@ class MessageComposerPresenter(
|
||||
private val featureFlagService: FeatureFlagService,
|
||||
private val contentScannerService: ContentScannerService,
|
||||
private val contentValidationCache: EventContentValidationCache,
|
||||
private val dispatchers: CoroutineDispatchers,
|
||||
) : Presenter<MessageComposerState> {
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
@@ -190,22 +193,22 @@ class MessageComposerPresenter(
|
||||
.collectAsState(initial = false)
|
||||
|
||||
val galleryMediaPicker = mediaPickerProvider.registerGalleryPicker { uri, mimeType ->
|
||||
handlePickedMedia(uri, mimeType)
|
||||
localCoroutineScope.handlePickedMedia(uri, mimeType)
|
||||
}
|
||||
val galleryMultiMediaPicker = mediaPickerProvider.registerGalleryMultiPicker { uris ->
|
||||
handlePickedMediaList(uris)
|
||||
localCoroutineScope.handlePickedMediaList(uris)
|
||||
}
|
||||
val filesPicker = mediaPickerProvider.registerFileMultiPicker(AnyMimeTypes) { uris ->
|
||||
handlePickedMediaList(uris, sendAsFile = true)
|
||||
localCoroutineScope.handlePickedMediaList(uris, sendAsFile = true)
|
||||
}
|
||||
val fileSinglePicker = mediaPickerProvider.registerFilePicker(AnyMimeTypes) { uri, mimeType ->
|
||||
handlePickedMedia(uri, mimeType ?: MimeTypes.OctetStream, sendAsFile = true)
|
||||
localCoroutineScope.handlePickedMedia(uri, mimeType ?: MimeTypes.OctetStream, sendAsFile = true)
|
||||
}
|
||||
val cameraPhotoPicker = mediaPickerProvider.registerCameraPhotoPicker { uri ->
|
||||
handlePickedMedia(uri, MimeTypes.Jpeg)
|
||||
localCoroutineScope.handlePickedMedia(uri, MimeTypes.Jpeg)
|
||||
}
|
||||
val cameraVideoPicker = mediaPickerProvider.registerCameraVideoPicker { uri ->
|
||||
handlePickedMedia(uri, MimeTypes.Mp4)
|
||||
localCoroutineScope.handlePickedMedia(uri, MimeTypes.Mp4)
|
||||
}
|
||||
val isFullScreen = rememberSaveable {
|
||||
mutableStateOf(false)
|
||||
@@ -283,17 +286,16 @@ class MessageComposerPresenter(
|
||||
}
|
||||
is MessageComposerEvent.SendUri -> {
|
||||
val inReplyToEventId = (messageComposerContext.composerMode as? MessageComposerMode.Reply)?.eventId
|
||||
sessionCoroutineScope.sendAttachment(
|
||||
attachment = Attachment.Media(
|
||||
localMedia = localMediaFactory.createFromUri(
|
||||
uri = event.uri,
|
||||
mimeType = null,
|
||||
name = null,
|
||||
formattedFileSize = null
|
||||
),
|
||||
),
|
||||
inReplyToEventId = inReplyToEventId,
|
||||
)
|
||||
sessionCoroutineScope.launch {
|
||||
val localMedia = createLocalMedia(
|
||||
uri = event.uri,
|
||||
mimeType = null,
|
||||
)
|
||||
sendAttachment(
|
||||
attachment = Attachment.Media(localMedia),
|
||||
inReplyToEventId = inReplyToEventId,
|
||||
)
|
||||
}
|
||||
|
||||
resetComposerModeAfterAttaching()
|
||||
}
|
||||
@@ -637,17 +639,28 @@ class MessageComposerPresenter(
|
||||
}
|
||||
}
|
||||
|
||||
private fun handlePickedMedia(
|
||||
uri: Uri?,
|
||||
mimeType: String? = null,
|
||||
sendAsFile: Boolean = false,
|
||||
) {
|
||||
uri ?: return
|
||||
val localMedia = localMediaFactory.createFromUri(
|
||||
// Creating the LocalMedia queries the ContentResolver and can decode the media, so do it off the main thread
|
||||
private suspend fun createLocalMedia(
|
||||
uri: Uri,
|
||||
mimeType: String?,
|
||||
) = withContext(dispatchers.io) {
|
||||
localMediaFactory.createFromUri(
|
||||
uri = uri,
|
||||
mimeType = mimeType,
|
||||
name = null,
|
||||
formattedFileSize = null
|
||||
formattedFileSize = null,
|
||||
)
|
||||
}
|
||||
|
||||
private fun CoroutineScope.handlePickedMedia(
|
||||
uri: Uri?,
|
||||
mimeType: String? = null,
|
||||
sendAsFile: Boolean = false,
|
||||
) = launch {
|
||||
uri ?: return@launch
|
||||
val localMedia = createLocalMedia(
|
||||
uri = uri,
|
||||
mimeType = mimeType,
|
||||
)
|
||||
val mediaAttachment = Attachment.Media(localMedia, sendAsFile = sendAsFile)
|
||||
val inReplyToEventId = (messageComposerContext.composerMode as? MessageComposerMode.Reply)?.eventId
|
||||
@@ -656,7 +669,7 @@ class MessageComposerPresenter(
|
||||
resetComposerModeAfterAttaching()
|
||||
}
|
||||
|
||||
private fun handlePickedMediaList(
|
||||
private fun CoroutineScope.handlePickedMediaList(
|
||||
uris: List<Uri>,
|
||||
sendAsFile: Boolean = false,
|
||||
) {
|
||||
@@ -665,19 +678,19 @@ class MessageComposerPresenter(
|
||||
handlePickedMedia(uris.first(), sendAsFile = sendAsFile)
|
||||
return
|
||||
}
|
||||
val attachments = uris.map { uri ->
|
||||
val localMedia = localMediaFactory.createFromUri(
|
||||
uri = uri,
|
||||
mimeType = null,
|
||||
name = null,
|
||||
formattedFileSize = null,
|
||||
)
|
||||
Attachment.Media(localMedia, sendAsFile = sendAsFile)
|
||||
}.toImmutableList()
|
||||
val inReplyToEventId = (messageComposerContext.composerMode as? MessageComposerMode.Reply)?.eventId
|
||||
navigator.navigateToPreviewAttachments(attachments, inReplyToEventId)
|
||||
launch {
|
||||
val attachments = uris.map { uri ->
|
||||
val localMedia = createLocalMedia(
|
||||
uri = uri,
|
||||
mimeType = null,
|
||||
)
|
||||
Attachment.Media(localMedia, sendAsFile = sendAsFile)
|
||||
}.toImmutableList()
|
||||
val inReplyToEventId = (messageComposerContext.composerMode as? MessageComposerMode.Reply)?.eventId
|
||||
navigator.navigateToPreviewAttachments(attachments, inReplyToEventId)
|
||||
|
||||
resetComposerModeAfterAttaching()
|
||||
resetComposerModeAfterAttaching()
|
||||
}
|
||||
}
|
||||
|
||||
private fun resetComposerModeAfterAttaching() {
|
||||
|
||||
+1
@@ -324,6 +324,7 @@ class MessageComposerPresenterSlashCommandTest {
|
||||
featureFlagService = featureFlagService,
|
||||
contentScannerService = { _, _ -> },
|
||||
contentValidationCache = InMemoryEventContentValidationCache(),
|
||||
dispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true),
|
||||
).apply {
|
||||
isTesting = true
|
||||
showTextFormatting = isRichTextEditorEnabled
|
||||
|
||||
+1
@@ -1723,6 +1723,7 @@ class MessageComposerPresenterTest : RobolectricTest() {
|
||||
featureFlagService = featureFlagService,
|
||||
contentScannerService = { _, _ -> },
|
||||
contentValidationCache = InMemoryEventContentValidationCache(),
|
||||
dispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true),
|
||||
).apply {
|
||||
isTesting = true
|
||||
showTextFormatting = isRichTextEditorEnabled
|
||||
|
||||
+13
-7
@@ -26,6 +26,7 @@ import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.architecture.runUpdatingStateNoSuccess
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.core.extensions.runCatchingExceptions
|
||||
import io.element.android.libraries.di.annotations.SessionCoroutineScope
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
@@ -57,6 +58,7 @@ import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@@ -76,6 +78,7 @@ class NotificationSettingsPresenter(
|
||||
@SessionCoroutineScope
|
||||
private val sessionCoroutineScope: CoroutineScope,
|
||||
private val featureFlagService: FeatureFlagService,
|
||||
private val dispatchers: CoroutineDispatchers,
|
||||
) : Presenter<NotificationSettingsState> {
|
||||
// Serializes the pick → copy → persist → recreate pipeline per slot. The copier already locks
|
||||
// its `<slot>.tmp` file, but the persist+recreate window outside the copier is unprotected;
|
||||
@@ -111,16 +114,19 @@ class NotificationSettingsPresenter(
|
||||
}
|
||||
|
||||
// List of PushProvider -> Distributor
|
||||
val distributors = remember {
|
||||
pushService.getAvailablePushProviders()
|
||||
.flatMap { pushProvider ->
|
||||
pushProvider.getDistributors().map { distributor ->
|
||||
pushProvider to distributor
|
||||
// Getting the distributors can query the PackageManager and the Play Services, so do it off the main thread
|
||||
val distributors by produceState(emptyList<Pair<PushProvider, Distributor>>()) {
|
||||
value = withContext(dispatchers.io) {
|
||||
pushService.getAvailablePushProviders()
|
||||
.flatMap { pushProvider ->
|
||||
pushProvider.getDistributors().map { distributor ->
|
||||
pushProvider to distributor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// List of Distributors
|
||||
val availableDistributors = remember {
|
||||
val availableDistributors = remember(distributors) {
|
||||
distributors.map { it.second }.toImmutableList()
|
||||
}
|
||||
|
||||
|
||||
+2
@@ -40,6 +40,7 @@ import io.element.android.tests.testutils.consumeItemsUntilPredicate
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.value
|
||||
import io.element.android.tests.testutils.test
|
||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
@@ -1176,6 +1177,7 @@ class NotificationSettingsPresenterTest {
|
||||
stringProvider = FakeStringProvider(),
|
||||
sessionCoroutineScope = backgroundScope,
|
||||
featureFlagService = featureFlagService,
|
||||
dispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user