diff --git a/.github/workflows/sync-localazy.yml b/.github/workflows/sync-localazy.yml index 1330b373bf..f559386ffe 100644 --- a/.github/workflows/sync-localazy.yml +++ b/.github/workflows/sync-localazy.yml @@ -36,7 +36,7 @@ jobs: ./tools/localazy/importSupportedLocalesFromLocalazy.py ./tools/test/generateAllScreenshots.py - name: Create Pull Request for Strings - uses: peter-evans/create-pull-request@v6 + uses: peter-evans/create-pull-request@v7 with: token: ${{ secrets.DANGER_GITHUB_API_TOKEN }} commit-message: Sync Strings from Localazy diff --git a/.github/workflows/sync-sas-strings.yml b/.github/workflows/sync-sas-strings.yml index d06cfe268a..632eb5d8ec 100644 --- a/.github/workflows/sync-sas-strings.yml +++ b/.github/workflows/sync-sas-strings.yml @@ -23,7 +23,7 @@ jobs: - name: Run SAS String script run: ./tools/sas/import_sas_strings.py - name: Create Pull Request for SAS Strings - uses: peter-evans/create-pull-request@v6 + uses: peter-evans/create-pull-request@v7 with: commit-message: Sync SAS Strings title: Sync SAS Strings diff --git a/.idea/dictionaries/shared.xml b/.idea/dictionaries/shared.xml index e7bc929689..67708285c3 100644 --- a/.idea/dictionaries/shared.xml +++ b/.idea/dictionaries/shared.xml @@ -1,6 +1,7 @@ + agpl backstack blurhash fdroid @@ -17,6 +18,7 @@ securebackup showkase snackbar + spdx swipeable textfields tombstoned diff --git a/CHANGES.md b/CHANGES.md index 9d7ea45760..2097277556 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,35 @@ +Changes in Element X v0.5.3 (2024-09-10) +======================================== + +### ✨ Features +* Add banner for optional migration to simplified sliding sync by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3429 + +### 🙌 Improvements +* Timeline : remove the encrypted history banner by @ganfra in https://github.com/element-hq/element-x-android/pull/3410 + +### 🐛 Bugfixes +* Fix new logins with Simplified SS using the proxy by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3417 +* Ensure Call is not hang up when user is asked to grant system permissions by @bmarty in https://github.com/element-hq/element-x-android/pull/3419 +* Wait for a room with joined state in `/sync` after creating it by @jmartinesp in https://github.com/element-hq/element-x-android/pull/3421 +* [Bugfix] : fix self verification flow by @ganfra in https://github.com/element-hq/element-x-android/pull/3426 + +### 🗣 Translations +* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/3425 + +### 🚧 In development 🚧 +* [Feature] Pinned messages list by @ganfra in https://github.com/element-hq/element-x-android/pull/3392 +* Pinned messages banner : adjust indicator to match design. by @ganfra in https://github.com/element-hq/element-x-android/pull/3415 + +### Dependency upgrades +* Update plugin dependencycheck to v10.0.4 by @renovate in https://github.com/element-hq/element-x-android/pull/3372 +* Update plugin detekt to v1.23.7 by @renovate in https://github.com/element-hq/element-x-android/pull/3424 + +### Others +* Delete old log files by @bmarty in https://github.com/element-hq/element-x-android/pull/3413 +* Recovery key formatting and wording iteration by @bmarty in https://github.com/element-hq/element-x-android/pull/3409 +* Change license to AGPL by @bmarty in https://github.com/element-hq/element-x-android/pull/3422 +* Remove Wait list screen by @bmarty in https://github.com/element-hq/element-x-android/pull/3428 + Changes in Element X v0.5.2 (2024-09-05) ========================================= diff --git a/fastlane/metadata/android/en-US/changelogs/40006000.txt b/fastlane/metadata/android/en-US/changelogs/40006000.txt new file mode 100644 index 0000000000..0574894881 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40006000.txt @@ -0,0 +1,2 @@ +Element X is the new generation of Element for professional and personal use on mobile. It’s the fastest Matrix client with a seamless & intuitive user interface. +Full changelog: https://github.com/element-hq/element-x-android/releases diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt index 7272246de5..b0863151ea 100644 --- a/fastlane/metadata/android/en-US/full_description.txt +++ b/fastlane/metadata/android/en-US/full_description.txt @@ -1,23 +1,35 @@ -Element X is the future Element. - -It is the brand new, and fastest ever, Matrix client. It is for personal and community use, and will support enterprise functionality later this year. - -A complete new build, Element X transforms performance. It’s not just the fastest Matrix client, it’s also fresher and more reliable. - -It’s so fast for a number of reasons, but in particular we’ve introduced a completely new syncing service (‘sliding sync’). So even in big end-to-end encrypted chat rooms it operates incredibly quickly. - -It’s fresher because we’ve rebuilt the entire user experience. All the power of Matrix - and the complexity of decentralized end-to-end encryption - is now hidden under a beautiful and intuitive user interface using the very latest frameworks and accessibility features. - -Element X delivers speed, usability and reliability on the decentralized Matrix open standard. +Element X brings you both sovereign & seamless collaboration built on Matrix. + +The collaboration capabilities include chat & video calls with the modern set of features such as: + • public & private channels + • room moderation & access conUpdatetrol + • replies, reactions, polls, read receipts, pinned messages, etc. + • simultaneous chat & calls (picture in picture) + • decentralized & federated communication across organizations + +All this comes in a secure & sovereign fashion without compromising responsiveness or overall usability of the app: + • enterprise-grade single sign-on + • easy & secure login & device verification via QR-code + • end to end encryption & zero trust + • protection against MITM & other cyber attacks + +If you’re a new user, use the new Element X app from the start. Compared to the current Element app you will get: + • greatly enhanced performance, sleek user interface and overall better user experience + • enterprise-grade support for single sign-on (OIDC) + • QR-code based login & device verification + • natively integrated Element Call for video calls + • continuous improvements, bug fixes and new features + +If you’re an existing user, using the current Element app - check out the new Element X and start planning your transition. The current Element app will be phased out and will only get critical security updates. Own your data Matrix-based, Element X lets you self-host your data or choose from any free public server (the default is matrix.org, but there are plenty of others to choose from). However you host, you have ownership; it’s your data. You’re not the product. You’re in control. Interoperate natively -Enjoy the freedom of the Matrix open standard! You have native interoperability with any other Matrix-based app. So just like email, it doesn't matter if your friends are on a different Matrix-based app you can still connect and chat. +Enjoy the freedom of the Matrix open standard! You have native interoperability with any other Matrix-based app. So just like email, it doesn't matter if your friends, partners or customers are on a different Matrix-based app - you can still connect. Encrypt your data -Enjoy your right to private conversations - free from data mining, ads and all the rest of it - and stay secure. Only the people in your conversation can read your messages. And Element X E2EE applies to voice and video calls too. +Enjoy your right to private conversations - free from data mining, ads and all the rest of it - and stay secure. Only the people in your conversation can read your messages. Chat across multiple devices -Stay in touch wherever you are with fully synchronized message history across all your devices, even those running ‘traditional’ Element, and on the web at https://app.element.io \ No newline at end of file +Stay in touch wherever you are with fully synchronized message history across all your devices, even those running Element legacy app, and on the web at https://app.element.io \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png new file mode 100644 index 0000000000..b317b7de64 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png differ diff --git a/fastlane/metadata/android/en-US/short_description.txt b/fastlane/metadata/android/en-US/short_description.txt index c474361017..6a18cdc82d 100644 --- a/fastlane/metadata/android/en-US/short_description.txt +++ b/fastlane/metadata/android/en-US/short_description.txt @@ -1 +1 @@ -Fastest ever Matrix client \ No newline at end of file +Sovereign. Seamless. On Matrix \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/title.txt b/fastlane/metadata/android/en-US/title.txt index 1e66e9042e..92577b2f3b 100644 --- a/fastlane/metadata/android/en-US/title.txt +++ b/fastlane/metadata/android/en-US/title.txt @@ -1 +1 @@ -Element X - Secure messenger \ No newline at end of file +Element X - Secure Chat & Call \ No newline at end of file diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/PinnedEventsTimelineProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/PinnedEventsTimelineProvider.kt index 4a1deb729b..75b639de85 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/PinnedEventsTimelineProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/PinnedEventsTimelineProvider.kt @@ -18,11 +18,13 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.matrix.api.timeline.TimelineProvider import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onCompletion +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import javax.inject.Inject @@ -32,7 +34,8 @@ class PinnedEventsTimelineProvider @Inject constructor( private val networkMonitor: NetworkMonitor, private val featureFlagService: FeatureFlagService, ) : TimelineProvider { - private val _timelineStateFlow: MutableStateFlow> = MutableStateFlow(AsyncData.Uninitialized) + private val _timelineStateFlow: MutableStateFlow> = + MutableStateFlow(AsyncData.Uninitialized) override fun activeTimelineFlow(): StateFlow { return _timelineStateFlow @@ -44,25 +47,46 @@ class PinnedEventsTimelineProvider @Inject constructor( val timelineStateFlow = _timelineStateFlow fun launchIn(scope: CoroutineScope) { + _timelineStateFlow.subscriptionCount + .map { count -> count > 0 } + .distinctUntilChanged() + .onEach { isActive -> + if (isActive) { + onActive() + } else { + onInactive() + } + } + .launchIn(scope) + } + + private suspend fun onActive() = coroutineScope { combine( featureFlagService.isFeatureEnabledFlow(FeatureFlags.PinnedEvents), networkMonitor.connectivity - ) { - // do not use connectivity here as data can be loaded from cache, it's just to trigger retry if needed - isEnabled, _ -> + ) { isEnabled, _ -> + // do not use connectivity here as data can be loaded from cache, it's just to trigger retry if needed isEnabled } .onEach { isFeatureEnabled -> if (isFeatureEnabled) { loadTimelineIfNeeded() } else { - _timelineStateFlow.value = AsyncData.Uninitialized + resetTimeline() } } - .onCompletion { - invokeOnTimeline { close() } - } - .launchIn(scope) + .launchIn(this) + } + + private suspend fun onInactive() { + resetTimeline() + } + + private suspend fun resetTimeline() { + invokeOnTimeline { + close() + } + _timelineStateFlow.emit(AsyncData.Uninitialized) } suspend fun invokeOnTimeline(action: suspend Timeline.() -> Unit) { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenter.kt index f005690ef0..b31e38d99f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenter.kt @@ -15,7 +15,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.produceState import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.setValue import dagger.assisted.Assisted @@ -27,6 +26,7 @@ import io.element.android.features.messages.impl.actionlist.model.TimelineItemAc import io.element.android.features.messages.impl.pinned.PinnedEventsTimelineProvider import io.element.android.features.messages.impl.timeline.TimelineRoomInfo import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactory +import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactoryConfig import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter @@ -55,16 +55,23 @@ import kotlin.time.Duration.Companion.milliseconds class PinnedMessagesListPresenter @AssistedInject constructor( @Assisted private val navigator: PinnedMessagesListNavigator, private val room: MatrixRoom, - private val timelineItemsFactory: TimelineItemsFactory, + timelineItemsFactoryCreator: TimelineItemsFactory.Creator, private val timelineProvider: PinnedEventsTimelineProvider, private val snackbarDispatcher: SnackbarDispatcher, actionListPresenterFactory: ActionListPresenter.Factory, + private val appCoroutineScope: CoroutineScope, ) : Presenter { @AssistedFactory interface Factory { fun create(navigator: PinnedMessagesListNavigator): PinnedMessagesListPresenter } + private val timelineItemsFactory: TimelineItemsFactory = timelineItemsFactoryCreator.create( + config = TimelineItemsFactoryConfig( + computeReadReceipts = false, + computeReactions = false, + ) + ) private val actionListPresenter = actionListPresenterFactory.create(PinnedMessagesListTimelineActionPostProcessor()) @Composable @@ -93,10 +100,9 @@ class PinnedMessagesListPresenter @AssistedInject constructor( } ) - val coroutineScope = rememberCoroutineScope() fun handleEvents(event: PinnedMessagesListEvents) { when (event) { - is PinnedMessagesListEvents.HandleAction -> coroutineScope.handleTimelineAction(event.action, event.event) + is PinnedMessagesListEvents.HandleAction -> appCoroutineScope.handleTimelineAction(event.action, event.event) } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt index 9466ac16f5..e4287cb3ed 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt @@ -22,6 +22,7 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import io.element.android.features.messages.impl.MessagesNavigator import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactory +import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactoryConfig import io.element.android.features.messages.impl.timeline.model.NewEventState import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.voicemessages.timeline.RedactedVoiceMessageManager @@ -54,7 +55,7 @@ import kotlinx.coroutines.withContext const val FOCUS_ON_PINNED_EVENT_DEBOUNCE_DURATION_IN_MILLIS = 200L class TimelinePresenter @AssistedInject constructor( - private val timelineItemsFactory: TimelineItemsFactory, + timelineItemsFactoryCreator: TimelineItemsFactory.Creator, private val timelineItemIndexer: TimelineItemIndexer, private val room: MatrixRoom, private val dispatchers: CoroutineDispatchers, @@ -71,6 +72,13 @@ class TimelinePresenter @AssistedInject constructor( fun create(navigator: MessagesNavigator): TimelinePresenter } + private val timelineItemsFactory: TimelineItemsFactory = timelineItemsFactoryCreator.create( + config = TimelineItemsFactoryConfig( + computeReadReceipts = true, + computeReactions = true, + ) + ) + @Composable override fun present(): TimelineState { val localScope = rememberCoroutineScope() diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/TimelineItemsFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/TimelineItemsFactory.kt index 18f48c5e11..f9857328cd 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/TimelineItemsFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/TimelineItemsFactory.kt @@ -7,6 +7,9 @@ package io.element.android.features.messages.impl.timeline.factories +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import io.element.android.features.messages.impl.timeline.TimelineItemIndexer import io.element.android.features.messages.impl.timeline.diff.TimelineItemsCacheInvalidator import io.element.android.features.messages.impl.timeline.factories.event.TimelineItemEventFactory @@ -26,15 +29,21 @@ import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext -import javax.inject.Inject -class TimelineItemsFactory @Inject constructor( +class TimelineItemsFactory @AssistedInject constructor( + @Assisted config: TimelineItemsFactoryConfig, + eventItemFactoryCreator: TimelineItemEventFactory.Creator, private val dispatchers: CoroutineDispatchers, - private val eventItemFactory: TimelineItemEventFactory, private val virtualItemFactory: TimelineItemVirtualFactory, private val timelineItemGrouper: TimelineItemGrouper, private val timelineItemIndexer: TimelineItemIndexer, ) { + @AssistedFactory + interface Creator { + fun create(config: TimelineItemsFactoryConfig): TimelineItemsFactory + } + + private val eventItemFactory = eventItemFactoryCreator.create(config) private val _timelineItems = MutableSharedFlow>(replay = 1) private val lock = Mutex() private val diffCache = MutableListDiffCache() diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/TimelineItemsFactoryConfig.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/TimelineItemsFactoryConfig.kt new file mode 100644 index 0000000000..3fbbc10acb --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/TimelineItemsFactoryConfig.kt @@ -0,0 +1,18 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.messages.impl.timeline.factories + +/** + * Some data used to configure the creation of timeline items. + * @param computeReadReceipts when false, read receipts will be empty. + * @param computeReactions when false, reactions will be empty. + */ +data class TimelineItemsFactoryConfig( + val computeReadReceipts: Boolean, + val computeReactions: Boolean, +) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt index dd7b5591fe..c71e9f6a26 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt @@ -7,6 +7,10 @@ package io.element.android.features.messages.impl.timeline.factories.event +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactoryConfig import io.element.android.features.messages.impl.timeline.groups.canBeDisplayedInBubbleBlock import io.element.android.features.messages.impl.timeline.model.AggregatedReaction import io.element.android.features.messages.impl.timeline.model.AggregatedReactionSender @@ -26,17 +30,23 @@ import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.libraries.matrix.api.timeline.item.event.getAvatarUrl import io.element.android.libraries.matrix.api.timeline.item.event.getDisambiguatedDisplayName import io.element.android.libraries.matrix.ui.messages.reply.map +import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import java.text.DateFormat import java.util.Date -import javax.inject.Inject -class TimelineItemEventFactory @Inject constructor( +class TimelineItemEventFactory @AssistedInject constructor( + @Assisted private val config: TimelineItemsFactoryConfig, private val contentFactory: TimelineItemContentFactory, private val matrixClient: MatrixClient, private val lastMessageTimestampFormatter: LastMessageTimestampFormatter, private val permalinkParser: PermalinkParser, ) { + @AssistedFactory + interface Creator { + fun create(config: TimelineItemsFactoryConfig): TimelineItemEventFactory + } + suspend fun create( currentTimelineItem: MatrixTimelineItem.Event, index: Int, @@ -92,8 +102,11 @@ class TimelineItemEventFactory @Inject constructor( } private fun MatrixTimelineItem.Event.computeReactionsState(): TimelineItemReactions { + if (!config.computeReactions) { + return TimelineItemReactions(reactions = persistentListOf()) + } val timeFormatter = DateFormat.getTimeInstance(DateFormat.SHORT) - var aggregatedReactions = event.reactions.map { reaction -> + var aggregatedReactions = this.event.reactions.map { reaction -> // Sort reactions within an aggregation by timestamp descending. // This puts the most recent at the top, useful in cases like the // reaction summary view or getting the most recent reaction. @@ -129,6 +142,9 @@ class TimelineItemEventFactory @Inject constructor( private fun MatrixTimelineItem.Event.computeReadReceiptState( roomMembers: List, ): TimelineItemReadReceipts { + if (!config.computeReadReceipts) { + return TimelineItemReadReceipts(receipts = persistentListOf()) + } return TimelineItemReadReceipts( receipts = event.receipts .map { receipt -> diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt index 6109b6f1c3..df873d2328 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt @@ -18,7 +18,7 @@ import io.element.android.features.messages.impl.actionlist.FakeActionListPresen import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction import io.element.android.features.messages.impl.draft.FakeComposerDraftService import io.element.android.features.messages.impl.fixtures.aMessageEvent -import io.element.android.features.messages.impl.fixtures.aTimelineItemsFactory +import io.element.android.features.messages.impl.fixtures.aTimelineItemsFactoryCreator import io.element.android.features.messages.impl.messagecomposer.DefaultMessageComposerContext import io.element.android.features.messages.impl.messagecomposer.FakeRoomAliasSuggestionsDataSource import io.element.android.features.messages.impl.messagecomposer.MessageComposerPresenter @@ -1024,7 +1024,7 @@ class MessagesPresenterTest { permissionsPresenterFactory, ) val timelinePresenter = TimelinePresenter( - timelineItemsFactory = aTimelineItemsFactory(), + timelineItemsFactoryCreator = aTimelineItemsFactoryCreator(), room = matrixRoom, dispatchers = coroutineDispatchers, appScope = this, diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/TimelineItemsFactoryFixtures.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/TimelineItemsFactoryFixtures.kt index ee6db627e5..c9439e0a3f 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/TimelineItemsFactoryFixtures.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/TimelineItemsFactoryFixtures.kt @@ -9,6 +9,7 @@ package io.element.android.features.messages.impl.fixtures import io.element.android.features.messages.impl.timeline.TimelineItemIndexer import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactory +import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactoryConfig import io.element.android.features.messages.impl.timeline.factories.event.TimelineItemContentFactory import io.element.android.features.messages.impl.timeline.factories.event.TimelineItemContentFailedToParseMessageFactory import io.element.android.features.messages.impl.timeline.factories.event.TimelineItemContentFailedToParseStateFactory @@ -39,40 +40,56 @@ import io.element.android.libraries.mediaviewer.api.util.FileExtensionExtractorW import io.element.android.tests.testutils.testCoroutineDispatchers import kotlinx.coroutines.test.TestScope +internal fun TestScope.aTimelineItemsFactoryCreator( + timelineItemIndexer: TimelineItemIndexer = TimelineItemIndexer(), +): TimelineItemsFactory.Creator { + return object : TimelineItemsFactory.Creator { + override fun create(config: TimelineItemsFactoryConfig): TimelineItemsFactory { + return aTimelineItemsFactory(config, timelineItemIndexer) + } + } +} + internal fun TestScope.aTimelineItemsFactory( - timelineItemIndexer: TimelineItemIndexer = TimelineItemIndexer() + config: TimelineItemsFactoryConfig, + timelineItemIndexer: TimelineItemIndexer = TimelineItemIndexer(), ): TimelineItemsFactory { val timelineEventFormatter = aTimelineEventFormatter() val matrixClient = FakeMatrixClient() return TimelineItemsFactory( dispatchers = testCoroutineDispatchers(), - eventItemFactory = TimelineItemEventFactory( - contentFactory = TimelineItemContentFactory( - messageFactory = TimelineItemContentMessageFactory( - fileSizeFormatter = FakeFileSizeFormatter(), - fileExtensionExtractor = FileExtensionExtractorWithoutValidation(), - featureFlagService = FakeFeatureFlagService(), - htmlConverterProvider = FakeHtmlConverterProvider(), + eventItemFactoryCreator = object : TimelineItemEventFactory.Creator { + override fun create(config: TimelineItemsFactoryConfig): TimelineItemEventFactory { + return TimelineItemEventFactory( + contentFactory = TimelineItemContentFactory( + messageFactory = TimelineItemContentMessageFactory( + fileSizeFormatter = FakeFileSizeFormatter(), + fileExtensionExtractor = FileExtensionExtractorWithoutValidation(), + featureFlagService = FakeFeatureFlagService(), + htmlConverterProvider = FakeHtmlConverterProvider(), + permalinkParser = FakePermalinkParser(), + textPillificationHelper = FakeTextPillificationHelper(), + ), + redactedMessageFactory = TimelineItemContentRedactedFactory(), + stickerFactory = TimelineItemContentStickerFactory( + fileSizeFormatter = FakeFileSizeFormatter(), + fileExtensionExtractor = FileExtensionExtractorWithoutValidation() + ), + pollFactory = TimelineItemContentPollFactory(FakeFeatureFlagService(), FakePollContentStateFactory()), + utdFactory = TimelineItemContentUTDFactory(), + roomMembershipFactory = TimelineItemContentRoomMembershipFactory(timelineEventFormatter), + profileChangeFactory = TimelineItemContentProfileChangeFactory(timelineEventFormatter), + stateFactory = TimelineItemContentStateFactory(timelineEventFormatter), + failedToParseMessageFactory = TimelineItemContentFailedToParseMessageFactory(), + failedToParseStateFactory = TimelineItemContentFailedToParseStateFactory(), + ), + matrixClient = matrixClient, + lastMessageTimestampFormatter = FakeLastMessageTimestampFormatter(), permalinkParser = FakePermalinkParser(), - textPillificationHelper = FakeTextPillificationHelper(), - ), - redactedMessageFactory = TimelineItemContentRedactedFactory(), - stickerFactory = TimelineItemContentStickerFactory( - fileSizeFormatter = FakeFileSizeFormatter(), - fileExtensionExtractor = FileExtensionExtractorWithoutValidation() - ), - pollFactory = TimelineItemContentPollFactory(FakeFeatureFlagService(), FakePollContentStateFactory()), - utdFactory = TimelineItemContentUTDFactory(), - roomMembershipFactory = TimelineItemContentRoomMembershipFactory(timelineEventFormatter), - profileChangeFactory = TimelineItemContentProfileChangeFactory(timelineEventFormatter), - stateFactory = TimelineItemContentStateFactory(timelineEventFormatter), - failedToParseMessageFactory = TimelineItemContentFailedToParseMessageFactory(), - failedToParseStateFactory = TimelineItemContentFailedToParseStateFactory(), - ), - matrixClient = matrixClient, - lastMessageTimestampFormatter = FakeLastMessageTimestampFormatter(), - permalinkParser = FakePermalinkParser(), - ), + config = config + ) + } + }, virtualItemFactory = TimelineItemVirtualFactory( daySeparatorFactory = TimelineItemDaySeparatorFactory( FakeDaySeparatorFormatter() @@ -80,6 +97,7 @@ internal fun TestScope.aTimelineItemsFactory( ), timelineItemGrouper = TimelineItemGrouper(), timelineItemIndexer = timelineItemIndexer, + config = config ) } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenterTest.kt index 03927ec994..5c84fec8ac 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenterTest.kt @@ -10,7 +10,7 @@ package io.element.android.features.messages.impl.pinned.list import com.google.common.truth.Truth.assertThat import io.element.android.features.messages.impl.actionlist.FakeActionListPresenter import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction -import io.element.android.features.messages.impl.fixtures.aTimelineItemsFactory +import io.element.android.features.messages.impl.fixtures.aTimelineItemsFactoryCreator import io.element.android.features.messages.impl.pinned.PinnedEventsTimelineProvider import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.networkmonitor.api.NetworkMonitor @@ -35,11 +35,14 @@ import io.element.android.tests.testutils.lambda.assert import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.value import io.element.android.tests.testutils.test +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import org.junit.Test +@OptIn(ExperimentalCoroutinesApi::class) class PinnedMessagesListPresenterTest { @Test fun `present - initial state feature disabled`() = runTest { @@ -155,6 +158,7 @@ class PinnedMessagesListPresenterTest { val filledState = awaitItem() as PinnedMessagesListState.Filled val eventItem = filledState.timelineItems.first() as TimelineItem.Event filledState.eventSink(PinnedMessagesListEvents.HandleAction(TimelineItemAction.Redact, eventItem)) + advanceUntilIdle() cancelAndIgnoreRemainingEvents() assert(redactEventLambda) .isCalledOnce() @@ -184,9 +188,11 @@ class PinnedMessagesListPresenterTest { pinnedEventsTimeline.unpinEventLambda = successUnpinEventLambda filledState.eventSink(PinnedMessagesListEvents.HandleAction(TimelineItemAction.Unpin, eventItem)) + advanceUntilIdle() pinnedEventsTimeline.unpinEventLambda = failureUnpinEventLambda filledState.eventSink(PinnedMessagesListEvents.HandleAction(TimelineItemAction.Unpin, eventItem)) + advanceUntilIdle() cancelAndIgnoreRemainingEvents() @@ -221,6 +227,7 @@ class PinnedMessagesListPresenterTest { val filledState = awaitItem() as PinnedMessagesListState.Filled val eventItem = filledState.timelineItems.first() as TimelineItem.Event filledState.eventSink(PinnedMessagesListEvents.HandleAction(TimelineItemAction.ViewInTimeline, eventItem)) + advanceUntilIdle() cancelAndIgnoreRemainingEvents() assert(onViewInTimelineClickLambda) .isCalledOnce() @@ -249,6 +256,7 @@ class PinnedMessagesListPresenterTest { val filledState = awaitItem() as PinnedMessagesListState.Filled val eventItem = filledState.timelineItems.first() as TimelineItem.Event filledState.eventSink(PinnedMessagesListEvents.HandleAction(TimelineItemAction.ViewSource, eventItem)) + advanceUntilIdle() cancelAndIgnoreRemainingEvents() assert(onShowEventDebugInfoClickLambda) .isCalledOnce() @@ -277,6 +285,7 @@ class PinnedMessagesListPresenterTest { val filledState = awaitItem() as PinnedMessagesListState.Filled val eventItem = filledState.timelineItems.first() as TimelineItem.Event filledState.eventSink(PinnedMessagesListEvents.HandleAction(TimelineItemAction.Forward, eventItem)) + advanceUntilIdle() cancelAndIgnoreRemainingEvents() assert(onForwardEventClickLambda) .isCalledOnce() @@ -318,10 +327,11 @@ class PinnedMessagesListPresenterTest { return PinnedMessagesListPresenter( navigator = navigator, room = room, - timelineItemsFactory = aTimelineItemsFactory(), + timelineItemsFactoryCreator = aTimelineItemsFactoryCreator(), timelineProvider = timelineProvider, snackbarDispatcher = SnackbarDispatcher(), actionListPresenterFactory = FakeActionListPresenter.Factory, + appCoroutineScope = this, ) } } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt index e90a781918..9df9dfc9f8 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt @@ -14,9 +14,8 @@ import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.features.messages.impl.FakeMessagesNavigator import io.element.android.features.messages.impl.fixtures.aMessageEvent -import io.element.android.features.messages.impl.fixtures.aTimelineItemsFactory +import io.element.android.features.messages.impl.fixtures.aTimelineItemsFactoryCreator import io.element.android.features.messages.impl.timeline.components.aCriticalShield -import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactory import io.element.android.features.messages.impl.timeline.model.NewEventState import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.voicemessages.timeline.FakeRedactedVoiceMessageManager @@ -662,7 +661,6 @@ import kotlin.time.Duration.Companion.seconds liveTimeline = timeline, canUserSendMessageResult = { _, _ -> Result.success(true) } ), - timelineItemsFactory: TimelineItemsFactory = aTimelineItemsFactory(), redactedVoiceMessageManager: RedactedVoiceMessageManager = FakeRedactedVoiceMessageManager(), messagesNavigator: FakeMessagesNavigator = FakeMessagesNavigator(), endPollAction: EndPollAction = FakeEndPollAction(), @@ -671,7 +669,7 @@ import kotlin.time.Duration.Companion.seconds timelineItemIndexer: TimelineItemIndexer = TimelineItemIndexer(), ): TimelinePresenter { return TimelinePresenter( - timelineItemsFactory = timelineItemsFactory, + timelineItemsFactoryCreator = aTimelineItemsFactoryCreator(), room = room, dispatchers = testCoroutineDispatchers(), appScope = this, diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootState.kt index 981ce879ce..f6db1cbec1 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootState.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootState.kt @@ -9,12 +9,13 @@ package io.element.android.features.preferences.impl.root import io.element.android.features.logout.api.direct.DirectLogoutState import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage +import io.element.android.libraries.matrix.api.core.DeviceId import io.element.android.libraries.matrix.api.user.MatrixUser data class PreferencesRootState( val myUser: MatrixUser, val version: String, - val deviceId: String?, + val deviceId: DeviceId?, val showSecureBackup: Boolean, val showSecureBackupBadge: Boolean, val accountManagementUrl: String?, diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootStateProvider.kt index ca43c3bbd5..464288fd9b 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootStateProvider.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootStateProvider.kt @@ -9,6 +9,7 @@ package io.element.android.features.preferences.impl.root import io.element.android.features.logout.api.direct.aDirectLogoutState import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage +import io.element.android.libraries.matrix.api.core.DeviceId import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.ui.strings.CommonStrings @@ -18,7 +19,7 @@ fun aPreferencesRootState( ) = PreferencesRootState( myUser = myUser, version = "Version 1.1 (1)", - deviceId = "ILAKNDNASDLK", + deviceId = DeviceId("ILAKNDNASDLK"), showSecureBackup = true, showSecureBackupBadge = true, accountManagementUrl = "aUrl", diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt index 0ec381fd85..4f47c7eab9 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt @@ -36,6 +36,7 @@ import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.utils.CommonDrawables import io.element.android.libraries.designsystem.utils.snackbar.SnackbarHost import io.element.android.libraries.designsystem.utils.snackbar.rememberSnackbarHostState +import io.element.android.libraries.matrix.api.core.DeviceId import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.ui.components.MatrixUserProvider import io.element.android.libraries.ui.strings.CommonStrings @@ -229,7 +230,7 @@ private fun ColumnScope.GeneralSection( @Composable private fun ColumnScope.Footer( version: String, - deviceId: String?, + deviceId: DeviceId?, onClick: (() -> Unit)?, ) { val text = remember(version, deviceId) { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d4f8ea1ccf..f01f7ce1a6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -19,7 +19,7 @@ datastore = "1.0.0" constraintlayout = "2.1.4" constraintlayout_compose = "1.0.1" lifecycle = "2.8.4" -activity = "1.9.1" +activity = "1.9.2" media3 = "1.4.1" camera = "1.3.4" @@ -162,7 +162,7 @@ jsoup = "org.jsoup:jsoup:1.18.1" appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" } molecule-runtime = "app.cash.molecule:molecule-runtime:2.0.0" timber = "com.jakewharton.timber:timber:5.0.1" -matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.2.42" +matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.2.43" matrix_richtexteditor = { module = "io.element.android:wysiwyg", version.ref = "wysiwyg" } matrix_richtexteditor_compose = { module = "io.element.android:wysiwyg-compose", version.ref = "wysiwyg" } sqldelight-driver-android = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" } diff --git a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt index cc312b681e..124d3a3318 100644 --- a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt +++ b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlags.kt @@ -115,7 +115,7 @@ enum class FeatureFlags( key = "feature.pinnedEvents", title = "Pinned Events", description = "Allow user to pin events in a room", - defaultValue = { false }, + defaultValue = { true }, isFinished = false, ), SyncOnPush( diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt index 2591a5cfd3..e45686b1a2 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt @@ -7,6 +7,7 @@ package io.element.android.libraries.matrix.api +import io.element.android.libraries.matrix.api.core.DeviceId import io.element.android.libraries.matrix.api.core.ProgressCallback import io.element.android.libraries.matrix.api.core.RoomAlias import io.element.android.libraries.matrix.api.core.RoomId @@ -41,7 +42,7 @@ import java.util.Optional interface MatrixClient : Closeable { val sessionId: SessionId - val deviceId: String + val deviceId: DeviceId val userProfile: StateFlow val roomListService: RoomListService val mediaLoader: MatrixMediaLoader diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/DeviceId.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/DeviceId.kt new file mode 100644 index 0000000000..d2abc59abc --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/DeviceId.kt @@ -0,0 +1,15 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.matrix.api.core + +import java.io.Serializable + +@JvmInline +value class DeviceId(val value: String) : Serializable { + override fun toString(): String = value +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/oidc/AccountManagementAction.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/oidc/AccountManagementAction.kt index 48220bec63..9fc33dee4b 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/oidc/AccountManagementAction.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/oidc/AccountManagementAction.kt @@ -7,9 +7,11 @@ package io.element.android.libraries.matrix.api.oidc +import io.element.android.libraries.matrix.api.core.DeviceId + sealed interface AccountManagementAction { data object Profile : AccountManagementAction data object SessionsList : AccountManagementAction - data class SessionView(val deviceId: String) : AccountManagementAction - data class SessionEnd(val deviceId: String) : AccountManagementAction + data class SessionView(val deviceId: DeviceId) : AccountManagementAction + data class SessionEnd(val deviceId: DeviceId) : AccountManagementAction } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt index 86c14c2e54..0fc9f6695b 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt @@ -12,6 +12,7 @@ import io.element.android.libraries.androidutils.file.safeDelete import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.coroutine.childScope import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.core.DeviceId import io.element.android.libraries.matrix.api.core.ProgressCallback import io.element.android.libraries.matrix.api.core.RoomAlias import io.element.android.libraries.matrix.api.core.RoomId @@ -120,7 +121,7 @@ class RustMatrixClient( sessionDelegate: RustClientSessionDelegate, ) : MatrixClient { override val sessionId: UserId = UserId(client.userId()) - override val deviceId: String = client.deviceId() + override val deviceId: DeviceId = DeviceId(client.deviceId()) override val sessionCoroutineScope = appCoroutineScope.childScope(dispatchers.main, "Session-$sessionId") private val innerRoomListService = syncService.roomListService() @@ -173,13 +174,13 @@ class RustMatrixClient( roomListService = roomListService, innerRoomListService = innerRoomListService, sessionId = sessionId, + deviceId = deviceId, notificationSettingsService = notificationSettingsService, sessionCoroutineScope = sessionCoroutineScope, dispatchers = dispatchers, systemClock = clock, roomContentForwarder = RoomContentForwarder(innerRoomListService), roomSyncSubscriber = roomSyncSubscriber, - getSessionData = { sessionStore.getSession(sessionId.value)!! }, ) override val mediaLoader: MatrixMediaLoader = RustMediaLoader( diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/oidc/AccountManagementAction.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/oidc/AccountManagementAction.kt index 5111c2a8cf..7adfc4fbca 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/oidc/AccountManagementAction.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/oidc/AccountManagementAction.kt @@ -13,8 +13,8 @@ import org.matrix.rustcomponents.sdk.AccountManagementAction as RustAccountManag fun AccountManagementAction.toRustAction(): RustAccountManagementAction { return when (this) { AccountManagementAction.Profile -> RustAccountManagementAction.Profile - is AccountManagementAction.SessionEnd -> RustAccountManagementAction.SessionEnd(deviceId) - is AccountManagementAction.SessionView -> RustAccountManagementAction.SessionView(deviceId) + is AccountManagementAction.SessionEnd -> RustAccountManagementAction.SessionEnd(deviceId.value) + is AccountManagementAction.SessionView -> RustAccountManagementAction.SessionView(deviceId.value) AccountManagementAction.SessionsList -> RustAccountManagementAction.SessionsList } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt index 1f5ffe7eef..aad3777071 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt @@ -10,6 +10,7 @@ package io.element.android.libraries.matrix.impl.room import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.coroutine.childScope import io.element.android.libraries.core.extensions.mapFailure +import io.element.android.libraries.matrix.api.core.DeviceId import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.ProgressCallback import io.element.android.libraries.matrix.api.core.RoomAlias @@ -52,7 +53,6 @@ import io.element.android.libraries.matrix.impl.util.MessageEventContent import io.element.android.libraries.matrix.impl.util.mxCallbackFlow import io.element.android.libraries.matrix.impl.widget.RustWidgetDriver import io.element.android.libraries.matrix.impl.widget.generateWidgetWebViewUrl -import io.element.android.libraries.sessionstorage.api.SessionData import io.element.android.services.toolbox.api.systemclock.SystemClock import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -89,6 +89,7 @@ import org.matrix.rustcomponents.sdk.Timeline as InnerTimeline @OptIn(ExperimentalCoroutinesApi::class) class RustMatrixRoom( override val sessionId: SessionId, + private val deviceId: DeviceId, private val roomListItem: RoomListItem, private val innerRoom: InnerRoom, innerTimeline: InnerTimeline, @@ -97,7 +98,6 @@ class RustMatrixRoom( private val coroutineDispatchers: CoroutineDispatchers, private val systemClock: SystemClock, private val roomContentForwarder: RoomContentForwarder, - private val sessionData: SessionData, private val roomSyncSubscriber: RoomSyncSubscriber, private val matrixRoomInfoMapper: MatrixRoomInfoMapper, ) : MatrixRoom { @@ -124,7 +124,7 @@ class RustMatrixRoom( override fun call(typingUserIds: List) { channel.trySend( typingUserIds - .filter { it != sessionData.userId } + .filter { it != sessionId.value } .map(::UserId) ) } @@ -188,6 +188,7 @@ class RustMatrixRoom( innerRoom.pinnedEventsTimeline( internalIdPrefix = "pinned_events", maxEventsToLoad = 100u, + maxConcurrentRequests = 10u, ).let { inner -> createTimeline(inner, mode = Timeline.Mode.PINNED_EVENTS) } @@ -606,7 +607,7 @@ class RustMatrixRoom( room = innerRoom, widgetCapabilitiesProvider = object : WidgetCapabilitiesProvider { override fun acquireCapabilities(capabilities: WidgetCapabilities): WidgetCapabilities { - return getElementCallRequiredPermissions(sessionId.value, sessionData.deviceId) + return getElementCallRequiredPermissions(sessionId.value, deviceId.value) } }, ) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomFactory.kt index 8db2c498ef..04210d6959 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomFactory.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomFactory.kt @@ -10,6 +10,7 @@ package io.element.android.libraries.matrix.impl.room import androidx.collection.lruCache import io.element.android.appconfig.TimelineConfig import io.element.android.libraries.core.coroutine.CoroutineDispatchers +import io.element.android.libraries.matrix.api.core.DeviceId import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService @@ -18,7 +19,6 @@ import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.api.roomlist.awaitLoaded import io.element.android.libraries.matrix.impl.roomlist.fullRoomWithTimeline import io.element.android.libraries.matrix.impl.roomlist.roomOrNull -import io.element.android.libraries.sessionstorage.api.SessionData import io.element.android.services.toolbox.api.systemclock.SystemClock import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -38,6 +38,7 @@ private const val CACHE_SIZE = 16 class RustRoomFactory( private val sessionId: SessionId, + private val deviceId: DeviceId, private val notificationSettingsService: NotificationSettingsService, private val sessionCoroutineScope: CoroutineScope, private val dispatchers: CoroutineDispatchers, @@ -46,7 +47,6 @@ class RustRoomFactory( private val roomListService: RoomListService, private val innerRoomListService: InnerRoomListService, private val roomSyncSubscriber: RoomSyncSubscriber, - private val getSessionData: suspend () -> SessionData, ) { @OptIn(ExperimentalCoroutinesApi::class) private val dispatcher = dispatchers.io.limitedParallelism(1) @@ -108,6 +108,7 @@ class RustRoomFactory( val liveTimeline = roomReferences.fullRoom.timeline() RustMatrixRoom( sessionId = sessionId, + deviceId = deviceId, roomListItem = roomReferences.roomListItem, innerRoom = roomReferences.fullRoom, innerTimeline = liveTimeline, @@ -116,7 +117,6 @@ class RustRoomFactory( coroutineDispatchers = dispatchers, systemClock = systemClock, roomContentForwarder = roomContentForwarder, - sessionData = getSessionData(), roomSyncSubscriber = roomSyncSubscriber, matrixRoomInfoMapper = matrixRoomInfoMapper, ) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt index f48af8d3a3..25d7c52c7b 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt @@ -61,9 +61,11 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import org.matrix.rustcomponents.sdk.EditedContent import org.matrix.rustcomponents.sdk.EventTimelineItem import org.matrix.rustcomponents.sdk.FormattedBody import org.matrix.rustcomponents.sdk.MessageFormat +import org.matrix.rustcomponents.sdk.PollData import org.matrix.rustcomponents.sdk.SendAttachmentJoinHandle import org.matrix.rustcomponents.sdk.use import timber.log.Timber @@ -274,8 +276,15 @@ class RustTimeline( withContext(dispatcher) { runCatching { getEventTimelineItem(originalEventId, transactionId).use { item -> + val editedContent = EditedContent.RoomMessage( + content = MessageEventContent.from( + body = body, + htmlBody = htmlBody, + intentionalMentions = intentionalMentions + ), + ) inner.edit( - newContent = MessageEventContent.from(body, htmlBody, intentionalMentions), + newContent = editedContent, item = item, ) } @@ -434,16 +443,21 @@ class RustTimeline( inner.getEventTimelineItemByEventId( eventId = pollStartId.value ) - pollStartEvent.use { - inner.editPoll( + val editedContent = EditedContent.PollStart( + pollData = PollData( question = question, answers = answers, maxSelections = maxSelections.toUByte(), pollKind = pollKind.toInner(), - editItem = pollStartEvent, + ), + ) + pollStartEvent.use { + inner.edit( + newContent = editedContent, + item = it, ) } - } + }.map { } } override suspend fun sendPollResponse( diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt index f4085bfb51..ed484f4426 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt @@ -8,6 +8,7 @@ package io.element.android.libraries.matrix.test import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.core.DeviceId import io.element.android.libraries.matrix.api.core.ProgressCallback import io.element.android.libraries.matrix.api.core.RoomAlias import io.element.android.libraries.matrix.api.core.RoomId @@ -58,7 +59,7 @@ import java.util.Optional class FakeMatrixClient( override val sessionId: SessionId = A_SESSION_ID, - override val deviceId: String = "A_DEVICE_ID", + override val deviceId: DeviceId = A_DEVICE_ID, override val sessionCoroutineScope: CoroutineScope = TestScope(), private val userDisplayName: String? = A_USER_NAME, private val userAvatarUrl: String? = AN_AVATAR_URL, diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt index 0d1b790da6..add458ae00 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt @@ -8,6 +8,7 @@ package io.element.android.libraries.matrix.test import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails +import io.element.android.libraries.matrix.api.core.DeviceId import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomAlias import io.element.android.libraries.matrix.api.core.RoomId @@ -47,6 +48,7 @@ val AN_EVENT_ID = EventId("\$anEventId") val AN_EVENT_ID_2 = EventId("\$anEventId2") val A_ROOM_ALIAS = RoomAlias("#alias1:domain") val A_TRANSACTION_ID = TransactionId("aTransactionId") +val A_DEVICE_ID = DeviceId("ILAKNDNASDLK") val A_UNIQUE_ID = UniqueId("aUniqueId") val A_UNIQUE_ID_2 = UniqueId("aUniqueId2") diff --git a/plugins/src/main/kotlin/Versions.kt b/plugins/src/main/kotlin/Versions.kt index 397b9127c8..59048156b3 100644 --- a/plugins/src/main/kotlin/Versions.kt +++ b/plugins/src/main/kotlin/Versions.kt @@ -42,12 +42,12 @@ import org.gradle.jvm.toolchain.JavaLanguageVersion // Note: 2 digits max for each value private const val versionMajor = 0 -private const val versionMinor = 5 +private const val versionMinor = 6 // Note: even values are reserved for regular release, odd values for hotfix release. // When creating a hotfix, you should decrease the value, since the current value // is the value for the next regular release. -private const val versionPatch = 3 +private const val versionPatch = 0 object Versions { val versionCode = 4_000_000 + versionMajor * 1_00_00 + versionMinor * 1_00 + versionPatch diff --git a/tools/release/release.sh b/tools/release/release.sh index 4838d9d00e..56c6926da1 100755 --- a/tools/release/release.sh +++ b/tools/release/release.sh @@ -177,9 +177,8 @@ printf "Committing...\n" git commit -a -m 'version++' printf "\n================================================================================\n" -printf "Wait for the GitHub action https://github.com/element-hq/element-x-android/actions/workflows/release.yml?query=branch%%3Amain to build the 'main' branch.\n" -printf "Please enter the url of the github action (!!! WARNING: NOT THE URL OF THE ARTIFACT ANYMORE !!!)\n" -read -p "For instance https://github.com/element-hq/element-x-android/actions/runs/9065756777: " runUrl +printf "The GitHub action https://github.com/element-hq/element-x-android/actions/workflows/release.yml?query=branch%%3Amain should have start a new run.\n" +read -p "Please enter the url of the run, no need to wait for it to complete (example: https://github.com/element-hq/element-x-android/actions/runs/9065756777): " runUrl targetPath="./tmp/Element/${version}" @@ -270,7 +269,7 @@ printf "File app-fdroid-x86_64-release-signed.apk:\n" "${buildToolsPath}"/aapt dump badging "${fdroidTargetPath}"/app-fdroid-x86_64-release-signed.apk | grep package printf "\n" -read -p "Does it look correct? Press enter when it's done." +read -p "Does it look correct? Press enter when it's done. " printf "\n================================================================================\n" printf "The APKs in ${fdroidTargetPath} have been signed!\n" @@ -363,7 +362,7 @@ read -p ". Press enter to continue. " printf "\n================================================================================\n" printf "Update the project release notes:\n\n" -read -p "Copy the content of the release note generated by GitHub to the file CHANGES.md and press enter to commit the change. \n" +read -p "Copy the content of the release note generated by GitHub to the file CHANGES.md and press enter to commit the change. " printf "\n================================================================================\n" printf "Committing...\n"