Merge branch 'release/0.6.0' into main
This commit is contained in:
commit
b2a586fa79
36 changed files with 297 additions and 103 deletions
2
.github/workflows/sync-localazy.yml
vendored
2
.github/workflows/sync-localazy.yml
vendored
|
|
@ -36,7 +36,7 @@ jobs:
|
||||||
./tools/localazy/importSupportedLocalesFromLocalazy.py
|
./tools/localazy/importSupportedLocalesFromLocalazy.py
|
||||||
./tools/test/generateAllScreenshots.py
|
./tools/test/generateAllScreenshots.py
|
||||||
- name: Create Pull Request for Strings
|
- name: Create Pull Request for Strings
|
||||||
uses: peter-evans/create-pull-request@v6
|
uses: peter-evans/create-pull-request@v7
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.DANGER_GITHUB_API_TOKEN }}
|
token: ${{ secrets.DANGER_GITHUB_API_TOKEN }}
|
||||||
commit-message: Sync Strings from Localazy
|
commit-message: Sync Strings from Localazy
|
||||||
|
|
|
||||||
2
.github/workflows/sync-sas-strings.yml
vendored
2
.github/workflows/sync-sas-strings.yml
vendored
|
|
@ -23,7 +23,7 @@ jobs:
|
||||||
- name: Run SAS String script
|
- name: Run SAS String script
|
||||||
run: ./tools/sas/import_sas_strings.py
|
run: ./tools/sas/import_sas_strings.py
|
||||||
- name: Create Pull Request for SAS Strings
|
- name: Create Pull Request for SAS Strings
|
||||||
uses: peter-evans/create-pull-request@v6
|
uses: peter-evans/create-pull-request@v7
|
||||||
with:
|
with:
|
||||||
commit-message: Sync SAS Strings
|
commit-message: Sync SAS Strings
|
||||||
title: Sync SAS Strings
|
title: Sync SAS Strings
|
||||||
|
|
|
||||||
2
.idea/dictionaries/shared.xml
generated
2
.idea/dictionaries/shared.xml
generated
|
|
@ -1,6 +1,7 @@
|
||||||
<component name="ProjectDictionaryState">
|
<component name="ProjectDictionaryState">
|
||||||
<dictionary name="shared">
|
<dictionary name="shared">
|
||||||
<words>
|
<words>
|
||||||
|
<w>agpl</w>
|
||||||
<w>backstack</w>
|
<w>backstack</w>
|
||||||
<w>blurhash</w>
|
<w>blurhash</w>
|
||||||
<w>fdroid</w>
|
<w>fdroid</w>
|
||||||
|
|
@ -17,6 +18,7 @@
|
||||||
<w>securebackup</w>
|
<w>securebackup</w>
|
||||||
<w>showkase</w>
|
<w>showkase</w>
|
||||||
<w>snackbar</w>
|
<w>snackbar</w>
|
||||||
|
<w>spdx</w>
|
||||||
<w>swipeable</w>
|
<w>swipeable</w>
|
||||||
<w>textfields</w>
|
<w>textfields</w>
|
||||||
<w>tombstoned</w>
|
<w>tombstoned</w>
|
||||||
|
|
|
||||||
32
CHANGES.md
32
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)
|
Changes in Element X v0.5.2 (2024-09-05)
|
||||||
=========================================
|
=========================================
|
||||||
|
|
||||||
|
|
|
||||||
2
fastlane/metadata/android/en-US/changelogs/40006000.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/40006000.txt
Normal file
|
|
@ -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
|
||||||
|
|
@ -1,23 +1,35 @@
|
||||||
Element X is the future Element.
|
Element X brings you both sovereign & seamless collaboration built on Matrix.
|
||||||
|
|
||||||
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.
|
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
|
||||||
|
|
||||||
A complete new build, Element X transforms performance. It’s not just the fastest Matrix client, it’s also fresher and more reliable.
|
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
|
||||||
|
|
||||||
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.
|
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
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
Element X delivers speed, usability and reliability on the decentralized Matrix open standard.
|
|
||||||
|
|
||||||
<b>Own your data</b>
|
<b>Own your data</b>
|
||||||
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.
|
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.
|
||||||
|
|
||||||
<b>Interoperate natively</b>
|
<b>Interoperate natively</b>
|
||||||
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.
|
||||||
|
|
||||||
<b>Encrypt your data</b>
|
<b>Encrypt your data</b>
|
||||||
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.
|
||||||
|
|
||||||
<b>Chat across multiple devices</b>
|
<b>Chat across multiple devices</b>
|
||||||
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
|
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
|
||||||
BIN
fastlane/metadata/android/en-US/images/phoneScreenshots/5.png
Normal file
BIN
fastlane/metadata/android/en-US/images/phoneScreenshots/5.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.4 MiB |
|
|
@ -1 +1 @@
|
||||||
Fastest ever Matrix client
|
Sovereign. Seamless. On Matrix
|
||||||
|
|
@ -1 +1 @@
|
||||||
Element X - Secure messenger
|
Element X - Secure Chat & Call
|
||||||
|
|
@ -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.Timeline
|
||||||
import io.element.android.libraries.matrix.api.timeline.TimelineProvider
|
import io.element.android.libraries.matrix.api.timeline.TimelineProvider
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.coroutineScope
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onCompletion
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
@ -32,7 +34,8 @@ class PinnedEventsTimelineProvider @Inject constructor(
|
||||||
private val networkMonitor: NetworkMonitor,
|
private val networkMonitor: NetworkMonitor,
|
||||||
private val featureFlagService: FeatureFlagService,
|
private val featureFlagService: FeatureFlagService,
|
||||||
) : TimelineProvider {
|
) : TimelineProvider {
|
||||||
private val _timelineStateFlow: MutableStateFlow<AsyncData<Timeline>> = MutableStateFlow(AsyncData.Uninitialized)
|
private val _timelineStateFlow: MutableStateFlow<AsyncData<Timeline>> =
|
||||||
|
MutableStateFlow(AsyncData.Uninitialized)
|
||||||
|
|
||||||
override fun activeTimelineFlow(): StateFlow<Timeline?> {
|
override fun activeTimelineFlow(): StateFlow<Timeline?> {
|
||||||
return _timelineStateFlow
|
return _timelineStateFlow
|
||||||
|
|
@ -44,25 +47,46 @@ class PinnedEventsTimelineProvider @Inject constructor(
|
||||||
val timelineStateFlow = _timelineStateFlow
|
val timelineStateFlow = _timelineStateFlow
|
||||||
|
|
||||||
fun launchIn(scope: CoroutineScope) {
|
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(
|
combine(
|
||||||
featureFlagService.isFeatureEnabledFlow(FeatureFlags.PinnedEvents),
|
featureFlagService.isFeatureEnabledFlow(FeatureFlags.PinnedEvents),
|
||||||
networkMonitor.connectivity
|
networkMonitor.connectivity
|
||||||
) {
|
) { isEnabled, _ ->
|
||||||
// do not use connectivity here as data can be loaded from cache, it's just to trigger retry if needed
|
// do not use connectivity here as data can be loaded from cache, it's just to trigger retry if needed
|
||||||
isEnabled, _ ->
|
|
||||||
isEnabled
|
isEnabled
|
||||||
}
|
}
|
||||||
.onEach { isFeatureEnabled ->
|
.onEach { isFeatureEnabled ->
|
||||||
if (isFeatureEnabled) {
|
if (isFeatureEnabled) {
|
||||||
loadTimelineIfNeeded()
|
loadTimelineIfNeeded()
|
||||||
} else {
|
} else {
|
||||||
_timelineStateFlow.value = AsyncData.Uninitialized
|
resetTimeline()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onCompletion {
|
.launchIn(this)
|
||||||
invokeOnTimeline { close() }
|
}
|
||||||
}
|
|
||||||
.launchIn(scope)
|
private suspend fun onInactive() {
|
||||||
|
resetTimeline()
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun resetTimeline() {
|
||||||
|
invokeOnTimeline {
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
_timelineStateFlow.emit(AsyncData.Uninitialized)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun invokeOnTimeline(action: suspend Timeline.() -> Unit) {
|
suspend fun invokeOnTimeline(action: suspend Timeline.() -> Unit) {
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.produceState
|
import androidx.compose.runtime.produceState
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.runtime.rememberUpdatedState
|
import androidx.compose.runtime.rememberUpdatedState
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import dagger.assisted.Assisted
|
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.pinned.PinnedEventsTimelineProvider
|
||||||
import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
|
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.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.features.messages.impl.timeline.model.TimelineItem
|
||||||
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
|
||||||
|
|
@ -55,16 +55,23 @@ import kotlin.time.Duration.Companion.milliseconds
|
||||||
class PinnedMessagesListPresenter @AssistedInject constructor(
|
class PinnedMessagesListPresenter @AssistedInject constructor(
|
||||||
@Assisted private val navigator: PinnedMessagesListNavigator,
|
@Assisted private val navigator: PinnedMessagesListNavigator,
|
||||||
private val room: MatrixRoom,
|
private val room: MatrixRoom,
|
||||||
private val timelineItemsFactory: TimelineItemsFactory,
|
timelineItemsFactoryCreator: TimelineItemsFactory.Creator,
|
||||||
private val timelineProvider: PinnedEventsTimelineProvider,
|
private val timelineProvider: PinnedEventsTimelineProvider,
|
||||||
private val snackbarDispatcher: SnackbarDispatcher,
|
private val snackbarDispatcher: SnackbarDispatcher,
|
||||||
actionListPresenterFactory: ActionListPresenter.Factory,
|
actionListPresenterFactory: ActionListPresenter.Factory,
|
||||||
|
private val appCoroutineScope: CoroutineScope,
|
||||||
) : Presenter<PinnedMessagesListState> {
|
) : Presenter<PinnedMessagesListState> {
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
interface Factory {
|
interface Factory {
|
||||||
fun create(navigator: PinnedMessagesListNavigator): PinnedMessagesListPresenter
|
fun create(navigator: PinnedMessagesListNavigator): PinnedMessagesListPresenter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val timelineItemsFactory: TimelineItemsFactory = timelineItemsFactoryCreator.create(
|
||||||
|
config = TimelineItemsFactoryConfig(
|
||||||
|
computeReadReceipts = false,
|
||||||
|
computeReactions = false,
|
||||||
|
)
|
||||||
|
)
|
||||||
private val actionListPresenter = actionListPresenterFactory.create(PinnedMessagesListTimelineActionPostProcessor())
|
private val actionListPresenter = actionListPresenterFactory.create(PinnedMessagesListTimelineActionPostProcessor())
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -93,10 +100,9 @@ class PinnedMessagesListPresenter @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
val coroutineScope = rememberCoroutineScope()
|
|
||||||
fun handleEvents(event: PinnedMessagesListEvents) {
|
fun handleEvents(event: PinnedMessagesListEvents) {
|
||||||
when (event) {
|
when (event) {
|
||||||
is PinnedMessagesListEvents.HandleAction -> coroutineScope.handleTimelineAction(event.action, event.event)
|
is PinnedMessagesListEvents.HandleAction -> appCoroutineScope.handleTimelineAction(event.action, event.event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import dagger.assisted.AssistedFactory
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import io.element.android.features.messages.impl.MessagesNavigator
|
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.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.NewEventState
|
||||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||||
import io.element.android.features.messages.impl.voicemessages.timeline.RedactedVoiceMessageManager
|
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
|
const val FOCUS_ON_PINNED_EVENT_DEBOUNCE_DURATION_IN_MILLIS = 200L
|
||||||
|
|
||||||
class TimelinePresenter @AssistedInject constructor(
|
class TimelinePresenter @AssistedInject constructor(
|
||||||
private val timelineItemsFactory: TimelineItemsFactory,
|
timelineItemsFactoryCreator: TimelineItemsFactory.Creator,
|
||||||
private val timelineItemIndexer: TimelineItemIndexer,
|
private val timelineItemIndexer: TimelineItemIndexer,
|
||||||
private val room: MatrixRoom,
|
private val room: MatrixRoom,
|
||||||
private val dispatchers: CoroutineDispatchers,
|
private val dispatchers: CoroutineDispatchers,
|
||||||
|
|
@ -71,6 +72,13 @@ class TimelinePresenter @AssistedInject constructor(
|
||||||
fun create(navigator: MessagesNavigator): TimelinePresenter
|
fun create(navigator: MessagesNavigator): TimelinePresenter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val timelineItemsFactory: TimelineItemsFactory = timelineItemsFactoryCreator.create(
|
||||||
|
config = TimelineItemsFactoryConfig(
|
||||||
|
computeReadReceipts = true,
|
||||||
|
computeReactions = true,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): TimelineState {
|
override fun present(): TimelineState {
|
||||||
val localScope = rememberCoroutineScope()
|
val localScope = rememberCoroutineScope()
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,9 @@
|
||||||
|
|
||||||
package io.element.android.features.messages.impl.timeline.factories
|
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.TimelineItemIndexer
|
||||||
import io.element.android.features.messages.impl.timeline.diff.TimelineItemsCacheInvalidator
|
import io.element.android.features.messages.impl.timeline.diff.TimelineItemsCacheInvalidator
|
||||||
import io.element.android.features.messages.impl.timeline.factories.event.TimelineItemEventFactory
|
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.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import kotlinx.coroutines.withContext
|
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 dispatchers: CoroutineDispatchers,
|
||||||
private val eventItemFactory: TimelineItemEventFactory,
|
|
||||||
private val virtualItemFactory: TimelineItemVirtualFactory,
|
private val virtualItemFactory: TimelineItemVirtualFactory,
|
||||||
private val timelineItemGrouper: TimelineItemGrouper,
|
private val timelineItemGrouper: TimelineItemGrouper,
|
||||||
private val timelineItemIndexer: TimelineItemIndexer,
|
private val timelineItemIndexer: TimelineItemIndexer,
|
||||||
) {
|
) {
|
||||||
|
@AssistedFactory
|
||||||
|
interface Creator {
|
||||||
|
fun create(config: TimelineItemsFactoryConfig): TimelineItemsFactory
|
||||||
|
}
|
||||||
|
|
||||||
|
private val eventItemFactory = eventItemFactoryCreator.create(config)
|
||||||
private val _timelineItems = MutableSharedFlow<ImmutableList<TimelineItem>>(replay = 1)
|
private val _timelineItems = MutableSharedFlow<ImmutableList<TimelineItem>>(replay = 1)
|
||||||
private val lock = Mutex()
|
private val lock = Mutex()
|
||||||
private val diffCache = MutableListDiffCache<TimelineItem>()
|
private val diffCache = MutableListDiffCache<TimelineItem>()
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
)
|
||||||
|
|
@ -7,6 +7,10 @@
|
||||||
|
|
||||||
package io.element.android.features.messages.impl.timeline.factories.event
|
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.groups.canBeDisplayedInBubbleBlock
|
||||||
import io.element.android.features.messages.impl.timeline.model.AggregatedReaction
|
import io.element.android.features.messages.impl.timeline.model.AggregatedReaction
|
||||||
import io.element.android.features.messages.impl.timeline.model.AggregatedReactionSender
|
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.getAvatarUrl
|
||||||
import io.element.android.libraries.matrix.api.timeline.item.event.getDisambiguatedDisplayName
|
import io.element.android.libraries.matrix.api.timeline.item.event.getDisambiguatedDisplayName
|
||||||
import io.element.android.libraries.matrix.ui.messages.reply.map
|
import io.element.android.libraries.matrix.ui.messages.reply.map
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import java.text.DateFormat
|
import java.text.DateFormat
|
||||||
import java.util.Date
|
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 contentFactory: TimelineItemContentFactory,
|
||||||
private val matrixClient: MatrixClient,
|
private val matrixClient: MatrixClient,
|
||||||
private val lastMessageTimestampFormatter: LastMessageTimestampFormatter,
|
private val lastMessageTimestampFormatter: LastMessageTimestampFormatter,
|
||||||
private val permalinkParser: PermalinkParser,
|
private val permalinkParser: PermalinkParser,
|
||||||
) {
|
) {
|
||||||
|
@AssistedFactory
|
||||||
|
interface Creator {
|
||||||
|
fun create(config: TimelineItemsFactoryConfig): TimelineItemEventFactory
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun create(
|
suspend fun create(
|
||||||
currentTimelineItem: MatrixTimelineItem.Event,
|
currentTimelineItem: MatrixTimelineItem.Event,
|
||||||
index: Int,
|
index: Int,
|
||||||
|
|
@ -92,8 +102,11 @@ class TimelineItemEventFactory @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun MatrixTimelineItem.Event.computeReactionsState(): TimelineItemReactions {
|
private fun MatrixTimelineItem.Event.computeReactionsState(): TimelineItemReactions {
|
||||||
|
if (!config.computeReactions) {
|
||||||
|
return TimelineItemReactions(reactions = persistentListOf())
|
||||||
|
}
|
||||||
val timeFormatter = DateFormat.getTimeInstance(DateFormat.SHORT)
|
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.
|
// Sort reactions within an aggregation by timestamp descending.
|
||||||
// This puts the most recent at the top, useful in cases like the
|
// This puts the most recent at the top, useful in cases like the
|
||||||
// reaction summary view or getting the most recent reaction.
|
// reaction summary view or getting the most recent reaction.
|
||||||
|
|
@ -129,6 +142,9 @@ class TimelineItemEventFactory @Inject constructor(
|
||||||
private fun MatrixTimelineItem.Event.computeReadReceiptState(
|
private fun MatrixTimelineItem.Event.computeReadReceiptState(
|
||||||
roomMembers: List<RoomMember>,
|
roomMembers: List<RoomMember>,
|
||||||
): TimelineItemReadReceipts {
|
): TimelineItemReadReceipts {
|
||||||
|
if (!config.computeReadReceipts) {
|
||||||
|
return TimelineItemReadReceipts(receipts = persistentListOf())
|
||||||
|
}
|
||||||
return TimelineItemReadReceipts(
|
return TimelineItemReadReceipts(
|
||||||
receipts = event.receipts
|
receipts = event.receipts
|
||||||
.map { receipt ->
|
.map { receipt ->
|
||||||
|
|
|
||||||
|
|
@ -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.actionlist.model.TimelineItemAction
|
||||||
import io.element.android.features.messages.impl.draft.FakeComposerDraftService
|
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.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.DefaultMessageComposerContext
|
||||||
import io.element.android.features.messages.impl.messagecomposer.FakeRoomAliasSuggestionsDataSource
|
import io.element.android.features.messages.impl.messagecomposer.FakeRoomAliasSuggestionsDataSource
|
||||||
import io.element.android.features.messages.impl.messagecomposer.MessageComposerPresenter
|
import io.element.android.features.messages.impl.messagecomposer.MessageComposerPresenter
|
||||||
|
|
@ -1024,7 +1024,7 @@ class MessagesPresenterTest {
|
||||||
permissionsPresenterFactory,
|
permissionsPresenterFactory,
|
||||||
)
|
)
|
||||||
val timelinePresenter = TimelinePresenter(
|
val timelinePresenter = TimelinePresenter(
|
||||||
timelineItemsFactory = aTimelineItemsFactory(),
|
timelineItemsFactoryCreator = aTimelineItemsFactoryCreator(),
|
||||||
room = matrixRoom,
|
room = matrixRoom,
|
||||||
dispatchers = coroutineDispatchers,
|
dispatchers = coroutineDispatchers,
|
||||||
appScope = this,
|
appScope = this,
|
||||||
|
|
|
||||||
|
|
@ -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.TimelineItemIndexer
|
||||||
import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactory
|
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.TimelineItemContentFactory
|
||||||
import io.element.android.features.messages.impl.timeline.factories.event.TimelineItemContentFailedToParseMessageFactory
|
import io.element.android.features.messages.impl.timeline.factories.event.TimelineItemContentFailedToParseMessageFactory
|
||||||
import io.element.android.features.messages.impl.timeline.factories.event.TimelineItemContentFailedToParseStateFactory
|
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 io.element.android.tests.testutils.testCoroutineDispatchers
|
||||||
import kotlinx.coroutines.test.TestScope
|
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(
|
internal fun TestScope.aTimelineItemsFactory(
|
||||||
timelineItemIndexer: TimelineItemIndexer = TimelineItemIndexer()
|
config: TimelineItemsFactoryConfig,
|
||||||
|
timelineItemIndexer: TimelineItemIndexer = TimelineItemIndexer(),
|
||||||
): TimelineItemsFactory {
|
): TimelineItemsFactory {
|
||||||
val timelineEventFormatter = aTimelineEventFormatter()
|
val timelineEventFormatter = aTimelineEventFormatter()
|
||||||
val matrixClient = FakeMatrixClient()
|
val matrixClient = FakeMatrixClient()
|
||||||
return TimelineItemsFactory(
|
return TimelineItemsFactory(
|
||||||
dispatchers = testCoroutineDispatchers(),
|
dispatchers = testCoroutineDispatchers(),
|
||||||
eventItemFactory = TimelineItemEventFactory(
|
eventItemFactoryCreator = object : TimelineItemEventFactory.Creator {
|
||||||
contentFactory = TimelineItemContentFactory(
|
override fun create(config: TimelineItemsFactoryConfig): TimelineItemEventFactory {
|
||||||
messageFactory = TimelineItemContentMessageFactory(
|
return TimelineItemEventFactory(
|
||||||
fileSizeFormatter = FakeFileSizeFormatter(),
|
contentFactory = TimelineItemContentFactory(
|
||||||
fileExtensionExtractor = FileExtensionExtractorWithoutValidation(),
|
messageFactory = TimelineItemContentMessageFactory(
|
||||||
featureFlagService = FakeFeatureFlagService(),
|
fileSizeFormatter = FakeFileSizeFormatter(),
|
||||||
htmlConverterProvider = FakeHtmlConverterProvider(),
|
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(),
|
permalinkParser = FakePermalinkParser(),
|
||||||
textPillificationHelper = FakeTextPillificationHelper(),
|
config = config
|
||||||
),
|
)
|
||||||
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(),
|
|
||||||
),
|
|
||||||
virtualItemFactory = TimelineItemVirtualFactory(
|
virtualItemFactory = TimelineItemVirtualFactory(
|
||||||
daySeparatorFactory = TimelineItemDaySeparatorFactory(
|
daySeparatorFactory = TimelineItemDaySeparatorFactory(
|
||||||
FakeDaySeparatorFormatter()
|
FakeDaySeparatorFormatter()
|
||||||
|
|
@ -80,6 +97,7 @@ internal fun TestScope.aTimelineItemsFactory(
|
||||||
),
|
),
|
||||||
timelineItemGrouper = TimelineItemGrouper(),
|
timelineItemGrouper = TimelineItemGrouper(),
|
||||||
timelineItemIndexer = timelineItemIndexer,
|
timelineItemIndexer = timelineItemIndexer,
|
||||||
|
config = config
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ package io.element.android.features.messages.impl.pinned.list
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.messages.impl.actionlist.FakeActionListPresenter
|
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.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.pinned.PinnedEventsTimelineProvider
|
||||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||||
import io.element.android.features.networkmonitor.api.NetworkMonitor
|
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.lambdaRecorder
|
||||||
import io.element.android.tests.testutils.lambda.value
|
import io.element.android.tests.testutils.lambda.value
|
||||||
import io.element.android.tests.testutils.test
|
import io.element.android.tests.testutils.test
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.flowOf
|
import kotlinx.coroutines.flow.flowOf
|
||||||
import kotlinx.coroutines.test.TestScope
|
import kotlinx.coroutines.test.TestScope
|
||||||
|
import kotlinx.coroutines.test.advanceUntilIdle
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
class PinnedMessagesListPresenterTest {
|
class PinnedMessagesListPresenterTest {
|
||||||
@Test
|
@Test
|
||||||
fun `present - initial state feature disabled`() = runTest {
|
fun `present - initial state feature disabled`() = runTest {
|
||||||
|
|
@ -155,6 +158,7 @@ class PinnedMessagesListPresenterTest {
|
||||||
val filledState = awaitItem() as PinnedMessagesListState.Filled
|
val filledState = awaitItem() as PinnedMessagesListState.Filled
|
||||||
val eventItem = filledState.timelineItems.first() as TimelineItem.Event
|
val eventItem = filledState.timelineItems.first() as TimelineItem.Event
|
||||||
filledState.eventSink(PinnedMessagesListEvents.HandleAction(TimelineItemAction.Redact, eventItem))
|
filledState.eventSink(PinnedMessagesListEvents.HandleAction(TimelineItemAction.Redact, eventItem))
|
||||||
|
advanceUntilIdle()
|
||||||
cancelAndIgnoreRemainingEvents()
|
cancelAndIgnoreRemainingEvents()
|
||||||
assert(redactEventLambda)
|
assert(redactEventLambda)
|
||||||
.isCalledOnce()
|
.isCalledOnce()
|
||||||
|
|
@ -184,9 +188,11 @@ class PinnedMessagesListPresenterTest {
|
||||||
|
|
||||||
pinnedEventsTimeline.unpinEventLambda = successUnpinEventLambda
|
pinnedEventsTimeline.unpinEventLambda = successUnpinEventLambda
|
||||||
filledState.eventSink(PinnedMessagesListEvents.HandleAction(TimelineItemAction.Unpin, eventItem))
|
filledState.eventSink(PinnedMessagesListEvents.HandleAction(TimelineItemAction.Unpin, eventItem))
|
||||||
|
advanceUntilIdle()
|
||||||
|
|
||||||
pinnedEventsTimeline.unpinEventLambda = failureUnpinEventLambda
|
pinnedEventsTimeline.unpinEventLambda = failureUnpinEventLambda
|
||||||
filledState.eventSink(PinnedMessagesListEvents.HandleAction(TimelineItemAction.Unpin, eventItem))
|
filledState.eventSink(PinnedMessagesListEvents.HandleAction(TimelineItemAction.Unpin, eventItem))
|
||||||
|
advanceUntilIdle()
|
||||||
|
|
||||||
cancelAndIgnoreRemainingEvents()
|
cancelAndIgnoreRemainingEvents()
|
||||||
|
|
||||||
|
|
@ -221,6 +227,7 @@ class PinnedMessagesListPresenterTest {
|
||||||
val filledState = awaitItem() as PinnedMessagesListState.Filled
|
val filledState = awaitItem() as PinnedMessagesListState.Filled
|
||||||
val eventItem = filledState.timelineItems.first() as TimelineItem.Event
|
val eventItem = filledState.timelineItems.first() as TimelineItem.Event
|
||||||
filledState.eventSink(PinnedMessagesListEvents.HandleAction(TimelineItemAction.ViewInTimeline, eventItem))
|
filledState.eventSink(PinnedMessagesListEvents.HandleAction(TimelineItemAction.ViewInTimeline, eventItem))
|
||||||
|
advanceUntilIdle()
|
||||||
cancelAndIgnoreRemainingEvents()
|
cancelAndIgnoreRemainingEvents()
|
||||||
assert(onViewInTimelineClickLambda)
|
assert(onViewInTimelineClickLambda)
|
||||||
.isCalledOnce()
|
.isCalledOnce()
|
||||||
|
|
@ -249,6 +256,7 @@ class PinnedMessagesListPresenterTest {
|
||||||
val filledState = awaitItem() as PinnedMessagesListState.Filled
|
val filledState = awaitItem() as PinnedMessagesListState.Filled
|
||||||
val eventItem = filledState.timelineItems.first() as TimelineItem.Event
|
val eventItem = filledState.timelineItems.first() as TimelineItem.Event
|
||||||
filledState.eventSink(PinnedMessagesListEvents.HandleAction(TimelineItemAction.ViewSource, eventItem))
|
filledState.eventSink(PinnedMessagesListEvents.HandleAction(TimelineItemAction.ViewSource, eventItem))
|
||||||
|
advanceUntilIdle()
|
||||||
cancelAndIgnoreRemainingEvents()
|
cancelAndIgnoreRemainingEvents()
|
||||||
assert(onShowEventDebugInfoClickLambda)
|
assert(onShowEventDebugInfoClickLambda)
|
||||||
.isCalledOnce()
|
.isCalledOnce()
|
||||||
|
|
@ -277,6 +285,7 @@ class PinnedMessagesListPresenterTest {
|
||||||
val filledState = awaitItem() as PinnedMessagesListState.Filled
|
val filledState = awaitItem() as PinnedMessagesListState.Filled
|
||||||
val eventItem = filledState.timelineItems.first() as TimelineItem.Event
|
val eventItem = filledState.timelineItems.first() as TimelineItem.Event
|
||||||
filledState.eventSink(PinnedMessagesListEvents.HandleAction(TimelineItemAction.Forward, eventItem))
|
filledState.eventSink(PinnedMessagesListEvents.HandleAction(TimelineItemAction.Forward, eventItem))
|
||||||
|
advanceUntilIdle()
|
||||||
cancelAndIgnoreRemainingEvents()
|
cancelAndIgnoreRemainingEvents()
|
||||||
assert(onForwardEventClickLambda)
|
assert(onForwardEventClickLambda)
|
||||||
.isCalledOnce()
|
.isCalledOnce()
|
||||||
|
|
@ -318,10 +327,11 @@ class PinnedMessagesListPresenterTest {
|
||||||
return PinnedMessagesListPresenter(
|
return PinnedMessagesListPresenter(
|
||||||
navigator = navigator,
|
navigator = navigator,
|
||||||
room = room,
|
room = room,
|
||||||
timelineItemsFactory = aTimelineItemsFactory(),
|
timelineItemsFactoryCreator = aTimelineItemsFactoryCreator(),
|
||||||
timelineProvider = timelineProvider,
|
timelineProvider = timelineProvider,
|
||||||
snackbarDispatcher = SnackbarDispatcher(),
|
snackbarDispatcher = SnackbarDispatcher(),
|
||||||
actionListPresenterFactory = FakeActionListPresenter.Factory,
|
actionListPresenterFactory = FakeActionListPresenter.Factory,
|
||||||
|
appCoroutineScope = this,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,8 @@ import app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.messages.impl.FakeMessagesNavigator
|
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.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.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.NewEventState
|
||||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||||
import io.element.android.features.messages.impl.voicemessages.timeline.FakeRedactedVoiceMessageManager
|
import io.element.android.features.messages.impl.voicemessages.timeline.FakeRedactedVoiceMessageManager
|
||||||
|
|
@ -662,7 +661,6 @@ import kotlin.time.Duration.Companion.seconds
|
||||||
liveTimeline = timeline,
|
liveTimeline = timeline,
|
||||||
canUserSendMessageResult = { _, _ -> Result.success(true) }
|
canUserSendMessageResult = { _, _ -> Result.success(true) }
|
||||||
),
|
),
|
||||||
timelineItemsFactory: TimelineItemsFactory = aTimelineItemsFactory(),
|
|
||||||
redactedVoiceMessageManager: RedactedVoiceMessageManager = FakeRedactedVoiceMessageManager(),
|
redactedVoiceMessageManager: RedactedVoiceMessageManager = FakeRedactedVoiceMessageManager(),
|
||||||
messagesNavigator: FakeMessagesNavigator = FakeMessagesNavigator(),
|
messagesNavigator: FakeMessagesNavigator = FakeMessagesNavigator(),
|
||||||
endPollAction: EndPollAction = FakeEndPollAction(),
|
endPollAction: EndPollAction = FakeEndPollAction(),
|
||||||
|
|
@ -671,7 +669,7 @@ import kotlin.time.Duration.Companion.seconds
|
||||||
timelineItemIndexer: TimelineItemIndexer = TimelineItemIndexer(),
|
timelineItemIndexer: TimelineItemIndexer = TimelineItemIndexer(),
|
||||||
): TimelinePresenter {
|
): TimelinePresenter {
|
||||||
return TimelinePresenter(
|
return TimelinePresenter(
|
||||||
timelineItemsFactory = timelineItemsFactory,
|
timelineItemsFactoryCreator = aTimelineItemsFactoryCreator(),
|
||||||
room = room,
|
room = room,
|
||||||
dispatchers = testCoroutineDispatchers(),
|
dispatchers = testCoroutineDispatchers(),
|
||||||
appScope = this,
|
appScope = this,
|
||||||
|
|
|
||||||
|
|
@ -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.features.logout.api.direct.DirectLogoutState
|
||||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
|
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.matrix.api.user.MatrixUser
|
||||||
|
|
||||||
data class PreferencesRootState(
|
data class PreferencesRootState(
|
||||||
val myUser: MatrixUser,
|
val myUser: MatrixUser,
|
||||||
val version: String,
|
val version: String,
|
||||||
val deviceId: String?,
|
val deviceId: DeviceId?,
|
||||||
val showSecureBackup: Boolean,
|
val showSecureBackup: Boolean,
|
||||||
val showSecureBackupBadge: Boolean,
|
val showSecureBackupBadge: Boolean,
|
||||||
val accountManagementUrl: String?,
|
val accountManagementUrl: String?,
|
||||||
|
|
|
||||||
|
|
@ -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.features.logout.api.direct.aDirectLogoutState
|
||||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
|
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.matrix.api.user.MatrixUser
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
||||||
|
|
@ -18,7 +19,7 @@ fun aPreferencesRootState(
|
||||||
) = PreferencesRootState(
|
) = PreferencesRootState(
|
||||||
myUser = myUser,
|
myUser = myUser,
|
||||||
version = "Version 1.1 (1)",
|
version = "Version 1.1 (1)",
|
||||||
deviceId = "ILAKNDNASDLK",
|
deviceId = DeviceId("ILAKNDNASDLK"),
|
||||||
showSecureBackup = true,
|
showSecureBackup = true,
|
||||||
showSecureBackupBadge = true,
|
showSecureBackupBadge = true,
|
||||||
accountManagementUrl = "aUrl",
|
accountManagementUrl = "aUrl",
|
||||||
|
|
|
||||||
|
|
@ -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.CommonDrawables
|
||||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarHost
|
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarHost
|
||||||
import io.element.android.libraries.designsystem.utils.snackbar.rememberSnackbarHostState
|
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.api.user.MatrixUser
|
||||||
import io.element.android.libraries.matrix.ui.components.MatrixUserProvider
|
import io.element.android.libraries.matrix.ui.components.MatrixUserProvider
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
@ -229,7 +230,7 @@ private fun ColumnScope.GeneralSection(
|
||||||
@Composable
|
@Composable
|
||||||
private fun ColumnScope.Footer(
|
private fun ColumnScope.Footer(
|
||||||
version: String,
|
version: String,
|
||||||
deviceId: String?,
|
deviceId: DeviceId?,
|
||||||
onClick: (() -> Unit)?,
|
onClick: (() -> Unit)?,
|
||||||
) {
|
) {
|
||||||
val text = remember(version, deviceId) {
|
val text = remember(version, deviceId) {
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ datastore = "1.0.0"
|
||||||
constraintlayout = "2.1.4"
|
constraintlayout = "2.1.4"
|
||||||
constraintlayout_compose = "1.0.1"
|
constraintlayout_compose = "1.0.1"
|
||||||
lifecycle = "2.8.4"
|
lifecycle = "2.8.4"
|
||||||
activity = "1.9.1"
|
activity = "1.9.2"
|
||||||
media3 = "1.4.1"
|
media3 = "1.4.1"
|
||||||
camera = "1.3.4"
|
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" }
|
appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" }
|
||||||
molecule-runtime = "app.cash.molecule:molecule-runtime:2.0.0"
|
molecule-runtime = "app.cash.molecule:molecule-runtime:2.0.0"
|
||||||
timber = "com.jakewharton.timber:timber:5.0.1"
|
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 = { module = "io.element.android:wysiwyg", version.ref = "wysiwyg" }
|
||||||
matrix_richtexteditor_compose = { module = "io.element.android:wysiwyg-compose", 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" }
|
sqldelight-driver-android = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" }
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,7 @@ enum class FeatureFlags(
|
||||||
key = "feature.pinnedEvents",
|
key = "feature.pinnedEvents",
|
||||||
title = "Pinned Events",
|
title = "Pinned Events",
|
||||||
description = "Allow user to pin events in a room",
|
description = "Allow user to pin events in a room",
|
||||||
defaultValue = { false },
|
defaultValue = { true },
|
||||||
isFinished = false,
|
isFinished = false,
|
||||||
),
|
),
|
||||||
SyncOnPush(
|
SyncOnPush(
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
package io.element.android.libraries.matrix.api
|
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.ProgressCallback
|
||||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
|
@ -41,7 +42,7 @@ import java.util.Optional
|
||||||
|
|
||||||
interface MatrixClient : Closeable {
|
interface MatrixClient : Closeable {
|
||||||
val sessionId: SessionId
|
val sessionId: SessionId
|
||||||
val deviceId: String
|
val deviceId: DeviceId
|
||||||
val userProfile: StateFlow<MatrixUser>
|
val userProfile: StateFlow<MatrixUser>
|
||||||
val roomListService: RoomListService
|
val roomListService: RoomListService
|
||||||
val mediaLoader: MatrixMediaLoader
|
val mediaLoader: MatrixMediaLoader
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -7,9 +7,11 @@
|
||||||
|
|
||||||
package io.element.android.libraries.matrix.api.oidc
|
package io.element.android.libraries.matrix.api.oidc
|
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.api.core.DeviceId
|
||||||
|
|
||||||
sealed interface AccountManagementAction {
|
sealed interface AccountManagementAction {
|
||||||
data object Profile : AccountManagementAction
|
data object Profile : AccountManagementAction
|
||||||
data object SessionsList : AccountManagementAction
|
data object SessionsList : AccountManagementAction
|
||||||
data class SessionView(val deviceId: String) : AccountManagementAction
|
data class SessionView(val deviceId: DeviceId) : AccountManagementAction
|
||||||
data class SessionEnd(val deviceId: String) : AccountManagementAction
|
data class SessionEnd(val deviceId: DeviceId) : AccountManagementAction
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.CoroutineDispatchers
|
||||||
import io.element.android.libraries.core.coroutine.childScope
|
import io.element.android.libraries.core.coroutine.childScope
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
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.ProgressCallback
|
||||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
|
@ -120,7 +121,7 @@ class RustMatrixClient(
|
||||||
sessionDelegate: RustClientSessionDelegate,
|
sessionDelegate: RustClientSessionDelegate,
|
||||||
) : MatrixClient {
|
) : MatrixClient {
|
||||||
override val sessionId: UserId = UserId(client.userId())
|
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")
|
override val sessionCoroutineScope = appCoroutineScope.childScope(dispatchers.main, "Session-$sessionId")
|
||||||
|
|
||||||
private val innerRoomListService = syncService.roomListService()
|
private val innerRoomListService = syncService.roomListService()
|
||||||
|
|
@ -173,13 +174,13 @@ class RustMatrixClient(
|
||||||
roomListService = roomListService,
|
roomListService = roomListService,
|
||||||
innerRoomListService = innerRoomListService,
|
innerRoomListService = innerRoomListService,
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
|
deviceId = deviceId,
|
||||||
notificationSettingsService = notificationSettingsService,
|
notificationSettingsService = notificationSettingsService,
|
||||||
sessionCoroutineScope = sessionCoroutineScope,
|
sessionCoroutineScope = sessionCoroutineScope,
|
||||||
dispatchers = dispatchers,
|
dispatchers = dispatchers,
|
||||||
systemClock = clock,
|
systemClock = clock,
|
||||||
roomContentForwarder = RoomContentForwarder(innerRoomListService),
|
roomContentForwarder = RoomContentForwarder(innerRoomListService),
|
||||||
roomSyncSubscriber = roomSyncSubscriber,
|
roomSyncSubscriber = roomSyncSubscriber,
|
||||||
getSessionData = { sessionStore.getSession(sessionId.value)!! },
|
|
||||||
)
|
)
|
||||||
|
|
||||||
override val mediaLoader: MatrixMediaLoader = RustMediaLoader(
|
override val mediaLoader: MatrixMediaLoader = RustMediaLoader(
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,8 @@ import org.matrix.rustcomponents.sdk.AccountManagementAction as RustAccountManag
|
||||||
fun AccountManagementAction.toRustAction(): RustAccountManagementAction {
|
fun AccountManagementAction.toRustAction(): RustAccountManagementAction {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
AccountManagementAction.Profile -> RustAccountManagementAction.Profile
|
AccountManagementAction.Profile -> RustAccountManagementAction.Profile
|
||||||
is AccountManagementAction.SessionEnd -> RustAccountManagementAction.SessionEnd(deviceId)
|
is AccountManagementAction.SessionEnd -> RustAccountManagementAction.SessionEnd(deviceId.value)
|
||||||
is AccountManagementAction.SessionView -> RustAccountManagementAction.SessionView(deviceId)
|
is AccountManagementAction.SessionView -> RustAccountManagementAction.SessionView(deviceId.value)
|
||||||
AccountManagementAction.SessionsList -> RustAccountManagementAction.SessionsList
|
AccountManagementAction.SessionsList -> RustAccountManagementAction.SessionsList
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.CoroutineDispatchers
|
||||||
import io.element.android.libraries.core.coroutine.childScope
|
import io.element.android.libraries.core.coroutine.childScope
|
||||||
import io.element.android.libraries.core.extensions.mapFailure
|
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.EventId
|
||||||
import io.element.android.libraries.matrix.api.core.ProgressCallback
|
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.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.util.mxCallbackFlow
|
||||||
import io.element.android.libraries.matrix.impl.widget.RustWidgetDriver
|
import io.element.android.libraries.matrix.impl.widget.RustWidgetDriver
|
||||||
import io.element.android.libraries.matrix.impl.widget.generateWidgetWebViewUrl
|
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 io.element.android.services.toolbox.api.systemclock.SystemClock
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
|
@ -89,6 +89,7 @@ import org.matrix.rustcomponents.sdk.Timeline as InnerTimeline
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
class RustMatrixRoom(
|
class RustMatrixRoom(
|
||||||
override val sessionId: SessionId,
|
override val sessionId: SessionId,
|
||||||
|
private val deviceId: DeviceId,
|
||||||
private val roomListItem: RoomListItem,
|
private val roomListItem: RoomListItem,
|
||||||
private val innerRoom: InnerRoom,
|
private val innerRoom: InnerRoom,
|
||||||
innerTimeline: InnerTimeline,
|
innerTimeline: InnerTimeline,
|
||||||
|
|
@ -97,7 +98,6 @@ class RustMatrixRoom(
|
||||||
private val coroutineDispatchers: CoroutineDispatchers,
|
private val coroutineDispatchers: CoroutineDispatchers,
|
||||||
private val systemClock: SystemClock,
|
private val systemClock: SystemClock,
|
||||||
private val roomContentForwarder: RoomContentForwarder,
|
private val roomContentForwarder: RoomContentForwarder,
|
||||||
private val sessionData: SessionData,
|
|
||||||
private val roomSyncSubscriber: RoomSyncSubscriber,
|
private val roomSyncSubscriber: RoomSyncSubscriber,
|
||||||
private val matrixRoomInfoMapper: MatrixRoomInfoMapper,
|
private val matrixRoomInfoMapper: MatrixRoomInfoMapper,
|
||||||
) : MatrixRoom {
|
) : MatrixRoom {
|
||||||
|
|
@ -124,7 +124,7 @@ class RustMatrixRoom(
|
||||||
override fun call(typingUserIds: List<String>) {
|
override fun call(typingUserIds: List<String>) {
|
||||||
channel.trySend(
|
channel.trySend(
|
||||||
typingUserIds
|
typingUserIds
|
||||||
.filter { it != sessionData.userId }
|
.filter { it != sessionId.value }
|
||||||
.map(::UserId)
|
.map(::UserId)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -188,6 +188,7 @@ class RustMatrixRoom(
|
||||||
innerRoom.pinnedEventsTimeline(
|
innerRoom.pinnedEventsTimeline(
|
||||||
internalIdPrefix = "pinned_events",
|
internalIdPrefix = "pinned_events",
|
||||||
maxEventsToLoad = 100u,
|
maxEventsToLoad = 100u,
|
||||||
|
maxConcurrentRequests = 10u,
|
||||||
).let { inner ->
|
).let { inner ->
|
||||||
createTimeline(inner, mode = Timeline.Mode.PINNED_EVENTS)
|
createTimeline(inner, mode = Timeline.Mode.PINNED_EVENTS)
|
||||||
}
|
}
|
||||||
|
|
@ -606,7 +607,7 @@ class RustMatrixRoom(
|
||||||
room = innerRoom,
|
room = innerRoom,
|
||||||
widgetCapabilitiesProvider = object : WidgetCapabilitiesProvider {
|
widgetCapabilitiesProvider = object : WidgetCapabilitiesProvider {
|
||||||
override fun acquireCapabilities(capabilities: WidgetCapabilities): WidgetCapabilities {
|
override fun acquireCapabilities(capabilities: WidgetCapabilities): WidgetCapabilities {
|
||||||
return getElementCallRequiredPermissions(sessionId.value, sessionData.deviceId)
|
return getElementCallRequiredPermissions(sessionId.value, deviceId.value)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ package io.element.android.libraries.matrix.impl.room
|
||||||
import androidx.collection.lruCache
|
import androidx.collection.lruCache
|
||||||
import io.element.android.appconfig.TimelineConfig
|
import io.element.android.appconfig.TimelineConfig
|
||||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
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.RoomId
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
|
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.api.roomlist.awaitLoaded
|
||||||
import io.element.android.libraries.matrix.impl.roomlist.fullRoomWithTimeline
|
import io.element.android.libraries.matrix.impl.roomlist.fullRoomWithTimeline
|
||||||
import io.element.android.libraries.matrix.impl.roomlist.roomOrNull
|
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 io.element.android.services.toolbox.api.systemclock.SystemClock
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
|
@ -38,6 +38,7 @@ private const val CACHE_SIZE = 16
|
||||||
|
|
||||||
class RustRoomFactory(
|
class RustRoomFactory(
|
||||||
private val sessionId: SessionId,
|
private val sessionId: SessionId,
|
||||||
|
private val deviceId: DeviceId,
|
||||||
private val notificationSettingsService: NotificationSettingsService,
|
private val notificationSettingsService: NotificationSettingsService,
|
||||||
private val sessionCoroutineScope: CoroutineScope,
|
private val sessionCoroutineScope: CoroutineScope,
|
||||||
private val dispatchers: CoroutineDispatchers,
|
private val dispatchers: CoroutineDispatchers,
|
||||||
|
|
@ -46,7 +47,6 @@ class RustRoomFactory(
|
||||||
private val roomListService: RoomListService,
|
private val roomListService: RoomListService,
|
||||||
private val innerRoomListService: InnerRoomListService,
|
private val innerRoomListService: InnerRoomListService,
|
||||||
private val roomSyncSubscriber: RoomSyncSubscriber,
|
private val roomSyncSubscriber: RoomSyncSubscriber,
|
||||||
private val getSessionData: suspend () -> SessionData,
|
|
||||||
) {
|
) {
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
private val dispatcher = dispatchers.io.limitedParallelism(1)
|
private val dispatcher = dispatchers.io.limitedParallelism(1)
|
||||||
|
|
@ -108,6 +108,7 @@ class RustRoomFactory(
|
||||||
val liveTimeline = roomReferences.fullRoom.timeline()
|
val liveTimeline = roomReferences.fullRoom.timeline()
|
||||||
RustMatrixRoom(
|
RustMatrixRoom(
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
|
deviceId = deviceId,
|
||||||
roomListItem = roomReferences.roomListItem,
|
roomListItem = roomReferences.roomListItem,
|
||||||
innerRoom = roomReferences.fullRoom,
|
innerRoom = roomReferences.fullRoom,
|
||||||
innerTimeline = liveTimeline,
|
innerTimeline = liveTimeline,
|
||||||
|
|
@ -116,7 +117,6 @@ class RustRoomFactory(
|
||||||
coroutineDispatchers = dispatchers,
|
coroutineDispatchers = dispatchers,
|
||||||
systemClock = systemClock,
|
systemClock = systemClock,
|
||||||
roomContentForwarder = roomContentForwarder,
|
roomContentForwarder = roomContentForwarder,
|
||||||
sessionData = getSessionData(),
|
|
||||||
roomSyncSubscriber = roomSyncSubscriber,
|
roomSyncSubscriber = roomSyncSubscriber,
|
||||||
matrixRoomInfoMapper = matrixRoomInfoMapper,
|
matrixRoomInfoMapper = matrixRoomInfoMapper,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -61,9 +61,11 @@ import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.flow.onStart
|
import kotlinx.coroutines.flow.onStart
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.matrix.rustcomponents.sdk.EditedContent
|
||||||
import org.matrix.rustcomponents.sdk.EventTimelineItem
|
import org.matrix.rustcomponents.sdk.EventTimelineItem
|
||||||
import org.matrix.rustcomponents.sdk.FormattedBody
|
import org.matrix.rustcomponents.sdk.FormattedBody
|
||||||
import org.matrix.rustcomponents.sdk.MessageFormat
|
import org.matrix.rustcomponents.sdk.MessageFormat
|
||||||
|
import org.matrix.rustcomponents.sdk.PollData
|
||||||
import org.matrix.rustcomponents.sdk.SendAttachmentJoinHandle
|
import org.matrix.rustcomponents.sdk.SendAttachmentJoinHandle
|
||||||
import org.matrix.rustcomponents.sdk.use
|
import org.matrix.rustcomponents.sdk.use
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
@ -274,8 +276,15 @@ class RustTimeline(
|
||||||
withContext(dispatcher) {
|
withContext(dispatcher) {
|
||||||
runCatching<Unit> {
|
runCatching<Unit> {
|
||||||
getEventTimelineItem(originalEventId, transactionId).use { item ->
|
getEventTimelineItem(originalEventId, transactionId).use { item ->
|
||||||
|
val editedContent = EditedContent.RoomMessage(
|
||||||
|
content = MessageEventContent.from(
|
||||||
|
body = body,
|
||||||
|
htmlBody = htmlBody,
|
||||||
|
intentionalMentions = intentionalMentions
|
||||||
|
),
|
||||||
|
)
|
||||||
inner.edit(
|
inner.edit(
|
||||||
newContent = MessageEventContent.from(body, htmlBody, intentionalMentions),
|
newContent = editedContent,
|
||||||
item = item,
|
item = item,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -434,16 +443,21 @@ class RustTimeline(
|
||||||
inner.getEventTimelineItemByEventId(
|
inner.getEventTimelineItemByEventId(
|
||||||
eventId = pollStartId.value
|
eventId = pollStartId.value
|
||||||
)
|
)
|
||||||
pollStartEvent.use {
|
val editedContent = EditedContent.PollStart(
|
||||||
inner.editPoll(
|
pollData = PollData(
|
||||||
question = question,
|
question = question,
|
||||||
answers = answers,
|
answers = answers,
|
||||||
maxSelections = maxSelections.toUByte(),
|
maxSelections = maxSelections.toUByte(),
|
||||||
pollKind = pollKind.toInner(),
|
pollKind = pollKind.toInner(),
|
||||||
editItem = pollStartEvent,
|
),
|
||||||
|
)
|
||||||
|
pollStartEvent.use {
|
||||||
|
inner.edit(
|
||||||
|
newContent = editedContent,
|
||||||
|
item = it,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}.map { }
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun sendPollResponse(
|
override suspend fun sendPollResponse(
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
package io.element.android.libraries.matrix.test
|
package io.element.android.libraries.matrix.test
|
||||||
|
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
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.ProgressCallback
|
||||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
|
@ -58,7 +59,7 @@ import java.util.Optional
|
||||||
|
|
||||||
class FakeMatrixClient(
|
class FakeMatrixClient(
|
||||||
override val sessionId: SessionId = A_SESSION_ID,
|
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(),
|
override val sessionCoroutineScope: CoroutineScope = TestScope(),
|
||||||
private val userDisplayName: String? = A_USER_NAME,
|
private val userDisplayName: String? = A_USER_NAME,
|
||||||
private val userAvatarUrl: String? = AN_AVATAR_URL,
|
private val userAvatarUrl: String? = AN_AVATAR_URL,
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
package io.element.android.libraries.matrix.test
|
package io.element.android.libraries.matrix.test
|
||||||
|
|
||||||
import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails
|
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.EventId
|
||||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
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 AN_EVENT_ID_2 = EventId("\$anEventId2")
|
||||||
val A_ROOM_ALIAS = RoomAlias("#alias1:domain")
|
val A_ROOM_ALIAS = RoomAlias("#alias1:domain")
|
||||||
val A_TRANSACTION_ID = TransactionId("aTransactionId")
|
val A_TRANSACTION_ID = TransactionId("aTransactionId")
|
||||||
|
val A_DEVICE_ID = DeviceId("ILAKNDNASDLK")
|
||||||
|
|
||||||
val A_UNIQUE_ID = UniqueId("aUniqueId")
|
val A_UNIQUE_ID = UniqueId("aUniqueId")
|
||||||
val A_UNIQUE_ID_2 = UniqueId("aUniqueId2")
|
val A_UNIQUE_ID_2 = UniqueId("aUniqueId2")
|
||||||
|
|
|
||||||
|
|
@ -42,12 +42,12 @@ import org.gradle.jvm.toolchain.JavaLanguageVersion
|
||||||
|
|
||||||
// Note: 2 digits max for each value
|
// Note: 2 digits max for each value
|
||||||
private const val versionMajor = 0
|
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.
|
// 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
|
// When creating a hotfix, you should decrease the value, since the current value
|
||||||
// is the value for the next regular release.
|
// is the value for the next regular release.
|
||||||
private const val versionPatch = 3
|
private const val versionPatch = 0
|
||||||
|
|
||||||
object Versions {
|
object Versions {
|
||||||
val versionCode = 4_000_000 + versionMajor * 1_00_00 + versionMinor * 1_00 + versionPatch
|
val versionCode = 4_000_000 + versionMajor * 1_00_00 + versionMinor * 1_00 + versionPatch
|
||||||
|
|
|
||||||
|
|
@ -177,9 +177,8 @@ printf "Committing...\n"
|
||||||
git commit -a -m 'version++'
|
git commit -a -m 'version++'
|
||||||
|
|
||||||
printf "\n================================================================================\n"
|
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 "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"
|
||||||
printf "Please enter the url of the github action (!!! WARNING: NOT THE URL OF THE ARTIFACT ANYMORE !!!)\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
|
||||||
read -p "For instance https://github.com/element-hq/element-x-android/actions/runs/9065756777: " runUrl
|
|
||||||
|
|
||||||
targetPath="./tmp/Element/${version}"
|
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
|
"${buildToolsPath}"/aapt dump badging "${fdroidTargetPath}"/app-fdroid-x86_64-release-signed.apk | grep package
|
||||||
|
|
||||||
printf "\n"
|
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 "\n================================================================================\n"
|
||||||
printf "The APKs in ${fdroidTargetPath} have been signed!\n"
|
printf "The APKs in ${fdroidTargetPath} have been signed!\n"
|
||||||
|
|
@ -363,7 +362,7 @@ read -p ". Press enter to continue. "
|
||||||
printf "\n================================================================================\n"
|
printf "\n================================================================================\n"
|
||||||
printf "Update the project release notes:\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 "\n================================================================================\n"
|
||||||
printf "Committing...\n"
|
printf "Committing...\n"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue