mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-28 22:18:46 +03:00
Merge branch 'develop' into feature/bca/rust_flavor
This commit is contained in:
commit
3146f5209b
90 changed files with 856 additions and 292 deletions
51
CHANGES.md
51
CHANGES.md
|
@ -1,3 +1,54 @@
|
||||||
|
Changes in Element v1.5.12 (2022-12-15)
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
Features ✨
|
||||||
|
----------
|
||||||
|
- [Threads] - Threads Labs Flag is enabled by default and forced to be enabled for existing users, but sill can be disabled manually ([#5503](https://github.com/vector-im/element-android/issues/5503))
|
||||||
|
- [Session manager] Add action to signout all the other session ([#7693](https://github.com/vector-im/element-android/issues/7693))
|
||||||
|
- Remind unverified sessions with a banner once a week ([#7694](https://github.com/vector-im/element-android/issues/7694))
|
||||||
|
- [Session manager] Add actions to rename and signout current session ([#7697](https://github.com/vector-im/element-android/issues/7697))
|
||||||
|
- Voice Broadcast - Update last message in the room list ([#7719](https://github.com/vector-im/element-android/issues/7719))
|
||||||
|
- Delete unused client information from account data ([#7754](https://github.com/vector-im/element-android/issues/7754))
|
||||||
|
|
||||||
|
Bugfixes 🐛
|
||||||
|
----------
|
||||||
|
- Fix bad pills color background. For light and dark theme the color is now 61708B (iso EleWeb) ([#7274](https://github.com/vector-im/element-android/issues/7274))
|
||||||
|
- [Notifications] Fixed a bug when push notification was automatically dismissed while app is on background ([#7643](https://github.com/vector-im/element-android/issues/7643))
|
||||||
|
- ANR when asking to select the notification method ([#7653](https://github.com/vector-im/element-android/issues/7653))
|
||||||
|
- [Rich text editor] Fix design and spacing of rich text editor ([#7658](https://github.com/vector-im/element-android/issues/7658))
|
||||||
|
- [Rich text editor] Fix keyboard closing after collapsing editor ([#7659](https://github.com/vector-im/element-android/issues/7659))
|
||||||
|
- Rich Text Editor: fix several issues related to insets:
|
||||||
|
* Empty space displayed at the bottom when you don't have permissions to send messages into a room.
|
||||||
|
* Wrong insets being kept when you exit the room screen and the keyboard is displayed, then come back to it. ([#7680](https://github.com/vector-im/element-android/issues/7680))
|
||||||
|
- Fix crash in message composer when room is missing ([#7683](https://github.com/vector-im/element-android/issues/7683))
|
||||||
|
- Fix crash when invalid homeserver url is entered. ([#7684](https://github.com/vector-im/element-android/issues/7684))
|
||||||
|
- Rich Text Editor: improve performance when entering reply/edit/quote mode. ([#7691](https://github.com/vector-im/element-android/issues/7691))
|
||||||
|
- [Rich text editor] Add error tracking for rich text editor ([#7695](https://github.com/vector-im/element-android/issues/7695))
|
||||||
|
- Fix E2EE set up failure whilst signing in using QR code ([#7699](https://github.com/vector-im/element-android/issues/7699))
|
||||||
|
- Fix usage of unknown shield in room summary ([#7710](https://github.com/vector-im/element-android/issues/7710))
|
||||||
|
- Fix crash when the network is not available. ([#7725](https://github.com/vector-im/element-android/issues/7725))
|
||||||
|
- [Session manager] Sessions without encryption support should not prompt to verify ([#7733](https://github.com/vector-im/element-android/issues/7733))
|
||||||
|
- Fix issue of Scan QR code button sometimes not showing when it should be available ([#7737](https://github.com/vector-im/element-android/issues/7737))
|
||||||
|
- Verification request is not showing when verify session popup is displayed ([#7743](https://github.com/vector-im/element-android/issues/7743))
|
||||||
|
- Fix crash when inviting by email. ([#7744](https://github.com/vector-im/element-android/issues/7744))
|
||||||
|
- Revert usage of stable fields in live location sharing and polls ([#7751](https://github.com/vector-im/element-android/issues/7751))
|
||||||
|
- [Poll] Poll end event is not recognized ([#7753](https://github.com/vector-im/element-android/issues/7753))
|
||||||
|
- [Push Notifications] When push notification for threaded message is clicked, thread timeline will be opened instead of room's main timeline ([#7770](https://github.com/vector-im/element-android/issues/7770))
|
||||||
|
|
||||||
|
Other changes
|
||||||
|
-------------
|
||||||
|
- [Threads] - added API to fetch threads list from the server instead of building it locally from events ([#5819](https://github.com/vector-im/element-android/issues/5819))
|
||||||
|
- Add Z-Labs label for rich text editor and migrate to new label naming. ([#7477](https://github.com/vector-im/element-android/issues/7477))
|
||||||
|
- Crypto database migration tests ([#7645](https://github.com/vector-im/element-android/issues/7645))
|
||||||
|
- Add tracing Id for to device messages ([#7708](https://github.com/vector-im/element-android/issues/7708))
|
||||||
|
- Disable nightly popup and add an entry point in the advanced settings instead. ([#7723](https://github.com/vector-im/element-android/issues/7723))
|
||||||
|
- Save m.local_notification_settings.<device-id> event in account_data ([#7596](https://github.com/vector-im/element-android/issues/7596))
|
||||||
|
- Update notifications setting when m.local_notification_settings.<device-id> event changes for current device ([#7632](https://github.com/vector-im/element-android/issues/7632))
|
||||||
|
|
||||||
|
SDK API changes ⚠️
|
||||||
|
------------------
|
||||||
|
- Handle account data removal ([#7740](https://github.com/vector-im/element-android/issues/7740))
|
||||||
|
|
||||||
Changes in Element 1.5.11 (2022-12-07)
|
Changes in Element 1.5.11 (2022-12-07)
|
||||||
======================================
|
======================================
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@ buildscript {
|
||||||
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.5.0.2730'
|
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.5.0.2730'
|
||||||
classpath 'com.google.android.gms:oss-licenses-plugin:0.10.5'
|
classpath 'com.google.android.gms:oss-licenses-plugin:0.10.5'
|
||||||
classpath "com.likethesalad.android:stem-plugin:2.2.3"
|
classpath "com.likethesalad.android:stem-plugin:2.2.3"
|
||||||
classpath 'org.owasp:dependency-check-gradle:7.3.0'
|
classpath 'org.owasp:dependency-check-gradle:7.4.1'
|
||||||
classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.7.20"
|
classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.7.20"
|
||||||
classpath "org.jetbrains.kotlinx:kotlinx-knit:0.4.0"
|
classpath "org.jetbrains.kotlinx:kotlinx-knit:0.4.0"
|
||||||
classpath 'com.jakewharton:butterknife-gradle-plugin:10.2.3'
|
classpath 'com.jakewharton:butterknife-gradle-plugin:10.2.3'
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
Fix bad pills color background. For light and dark theme the color is now 61708B (iso EleWeb)
|
|
|
@ -1 +0,0 @@
|
||||||
Add Z-Labs label for rich text editor and migrate to new label naming.
|
|
|
@ -1 +0,0 @@
|
||||||
Save m.local_notification_settings.<device-id> event in account_data
|
|
|
@ -1 +0,0 @@
|
||||||
Update notifications setting when m.local_notification_settings.<device-id> event changes for current device
|
|
|
@ -1 +0,0 @@
|
||||||
[Notifications] Fixed a bug when push notification was automatically dismissed while app is on background
|
|
|
@ -1 +0,0 @@
|
||||||
Crypto database migration tests
|
|
|
@ -1 +0,0 @@
|
||||||
ANR when asking to select the notification method
|
|
|
@ -1 +0,0 @@
|
||||||
[Rich text editor] Fix design and spacing of rich text editor
|
|
|
@ -1 +0,0 @@
|
||||||
[Rich text editor] Fix keyboard closing after collapsing editor
|
|
|
@ -1,3 +0,0 @@
|
||||||
Rich Text Editor: fix several issues related to insets:
|
|
||||||
* Empty space displayed at the bottom when you don't have permissions to send messages into a room.
|
|
||||||
* Wrong insets being kept when you exit the room screen and the keyboard is displayed, then come back to it.
|
|
|
@ -1,2 +0,0 @@
|
||||||
Fix crash in message composer when room is missing
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
Fix crash when invalid homeserver url is entered.
|
|
|
@ -1 +0,0 @@
|
||||||
Rich Text Editor: improve performance when entering reply/edit/quote mode.
|
|
|
@ -1 +0,0 @@
|
||||||
[Session manager] Add action to signout all the other session
|
|
|
@ -1 +0,0 @@
|
||||||
Remind unverified sessions with a banner once a week
|
|
|
@ -1 +0,0 @@
|
||||||
[Rich text editor] Add error tracking for rich text editor
|
|
|
@ -1 +0,0 @@
|
||||||
[Session manager] Add actions to rename and signout current session
|
|
|
@ -1 +0,0 @@
|
||||||
Fix E2EE set up failure whilst signing in using QR code
|
|
|
@ -1 +0,0 @@
|
||||||
Add tracing Id for to device messages
|
|
|
@ -1 +0,0 @@
|
||||||
Fix usage of unknown shield in room summary
|
|
|
@ -1 +0,0 @@
|
||||||
Disable nightly popup and add an entry point in the advanced settings instead.
|
|
|
@ -1 +0,0 @@
|
||||||
Fix crash when the network is not available.
|
|
|
@ -1 +0,0 @@
|
||||||
[Session manager] Sessions without encryption support should not prompt to verify
|
|
|
@ -1 +0,0 @@
|
||||||
Fix issue of Scan QR code button sometimes not showing when it should be available
|
|
|
@ -1 +0,0 @@
|
||||||
Handle account data removal
|
|
|
@ -1 +0,0 @@
|
||||||
Verification request is not showing when verify session popup is displayed
|
|
|
@ -1 +0,0 @@
|
||||||
Fix crash when inviting by email.
|
|
|
@ -1 +0,0 @@
|
||||||
Revert usage of stable fields in live location sharing and polls
|
|
|
@ -1 +0,0 @@
|
||||||
Delete unused client information from account data
|
|
|
@ -1 +0,0 @@
|
||||||
[Push Notifications] When push notification for threaded message is clicked, thread timeline will be opened instead of room's main timeline
|
|
|
@ -26,7 +26,7 @@ def jjwt = "0.11.5"
|
||||||
// Temporary version to unblock #6929. Once 0.16.0 is released we should use it, and revert
|
// Temporary version to unblock #6929. Once 0.16.0 is released we should use it, and revert
|
||||||
// the whole commit which set version 0.16.0-SNAPSHOT
|
// the whole commit which set version 0.16.0-SNAPSHOT
|
||||||
def vanniktechEmoji = "0.16.0-SNAPSHOT"
|
def vanniktechEmoji = "0.16.0-SNAPSHOT"
|
||||||
def sentry = "6.9.0"
|
def sentry = "6.9.2"
|
||||||
def fragment = "1.5.5"
|
def fragment = "1.5.5"
|
||||||
// Testing
|
// Testing
|
||||||
def mockk = "1.12.3" // We need to use 1.12.3 to have mocking in androidTest until a new version is released: https://github.com/mockk/mockk/issues/819
|
def mockk = "1.12.3" // We need to use 1.12.3 to have mocking in androidTest until a new version is released: https://github.com/mockk/mockk/issues/819
|
||||||
|
|
2
fastlane/metadata/android/en-US/changelogs/40105120.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/40105120.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Main changes in this version: Thread are now enabled by default.
|
||||||
|
Full changelog: https://github.com/vector-im/element-android/releases
|
|
@ -134,6 +134,9 @@
|
||||||
<string name="notice_crypto_unable_to_decrypt">** Unable to decrypt: %s **</string>
|
<string name="notice_crypto_unable_to_decrypt">** Unable to decrypt: %s **</string>
|
||||||
<string name="notice_crypto_error_unknown_inbound_session_id">The sender\'s device has not sent us the keys for this message.</string>
|
<string name="notice_crypto_error_unknown_inbound_session_id">The sender\'s device has not sent us the keys for this message.</string>
|
||||||
|
|
||||||
|
<string name="notice_voice_broadcast_ended">%1$s ended a voice broadcast.</string>
|
||||||
|
<string name="notice_voice_broadcast_ended_by_you">You ended a voice broadcast.</string>
|
||||||
|
|
||||||
<!-- Messages -->
|
<!-- Messages -->
|
||||||
|
|
||||||
<!-- Room Screen -->
|
<!-- Room Screen -->
|
||||||
|
@ -3039,7 +3042,7 @@
|
||||||
|
|
||||||
<string name="labs_auto_report_uisi">Auto Report Decryption Errors.</string>
|
<string name="labs_auto_report_uisi">Auto Report Decryption Errors.</string>
|
||||||
<string name="labs_auto_report_uisi_desc">Your system will automatically send logs when an unable to decrypt error occurs</string>
|
<string name="labs_auto_report_uisi_desc">Your system will automatically send logs when an unable to decrypt error occurs</string>
|
||||||
<string name="labs_enable_thread_messages">Enable Thread Messages</string>
|
<string name="labs_enable_thread_messages">Enable threaded messages</string>
|
||||||
<string name="labs_enable_thread_messages_desc">Note: app will be restarted</string>
|
<string name="labs_enable_thread_messages_desc">Note: app will be restarted</string>
|
||||||
<string name="settings_show_latest_profile">Show latest user info</string>
|
<string name="settings_show_latest_profile">Show latest user info</string>
|
||||||
<string name="settings_show_latest_profile_description">Show the latest profile info (avatar and display name) for all the messages.</string>
|
<string name="settings_show_latest_profile_description">Show the latest profile info (avatar and display name) for all the messages.</string>
|
||||||
|
@ -3108,6 +3111,7 @@
|
||||||
<string name="audio_message_file_size">(%1$s)</string>
|
<string name="audio_message_file_size">(%1$s)</string>
|
||||||
|
|
||||||
<string name="voice_broadcast_live">Live</string>
|
<string name="voice_broadcast_live">Live</string>
|
||||||
|
<string name="voice_broadcast_live_broadcast">Live broadcast</string>
|
||||||
<!-- TODO Rename id to voice_broadcast_buffering -->
|
<!-- TODO Rename id to voice_broadcast_buffering -->
|
||||||
<string name="a11y_voice_broadcast_buffering">Buffering…</string>
|
<string name="a11y_voice_broadcast_buffering">Buffering…</string>
|
||||||
<string name="a11y_resume_voice_broadcast_record">Resume voice broadcast record</string>
|
<string name="a11y_resume_voice_broadcast_record">Resume voice broadcast record</string>
|
||||||
|
|
|
@ -31,7 +31,6 @@ import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState
|
import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState
|
||||||
import org.matrix.android.sdk.api.session.room.send.UserDraft
|
import org.matrix.android.sdk.api.session.room.send.UserDraft
|
||||||
import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary
|
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
import org.matrix.android.sdk.api.util.Optional
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
import org.matrix.android.sdk.api.util.toOptional
|
import org.matrix.android.sdk.api.util.toOptional
|
||||||
|
@ -119,13 +118,6 @@ class FlowRoom(private val room: Room) {
|
||||||
return room.roomPushRuleService().getLiveRoomNotificationState().asFlow()
|
return room.roomPushRuleService().getLiveRoomNotificationState().asFlow()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun liveThreadSummaries(): Flow<List<ThreadSummary>> {
|
|
||||||
return room.threadsService().getAllThreadSummariesLive().asFlow()
|
|
||||||
.startWith(room.coroutineDispatchers.io) {
|
|
||||||
room.threadsService().getAllThreadSummaries()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun liveThreadList(): Flow<List<ThreadRootEvent>> {
|
fun liveThreadList(): Flow<List<ThreadRootEvent>> {
|
||||||
return room.threadsLocalService().getAllThreadsLive().asFlow()
|
return room.threadsLocalService().getAllThreadsLive().asFlow()
|
||||||
.startWith(room.coroutineDispatchers.io) {
|
.startWith(room.coroutineDispatchers.io) {
|
||||||
|
|
|
@ -63,7 +63,7 @@ android {
|
||||||
// that the app's state is completely cleared between tests.
|
// that the app's state is completely cleared between tests.
|
||||||
testInstrumentationRunnerArguments clearPackageData: 'true'
|
testInstrumentationRunnerArguments clearPackageData: 'true'
|
||||||
|
|
||||||
buildConfigField "String", "SDK_VERSION", "\"1.5.12\""
|
buildConfigField "String", "SDK_VERSION", "\"1.5.14\""
|
||||||
|
|
||||||
buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\""
|
buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\""
|
||||||
buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\""
|
buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\""
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.common
|
package org.matrix.android.sdk.common
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.RoomDisplayNameFallbackProvider
|
import org.matrix.android.sdk.api.provider.RoomDisplayNameFallbackProvider
|
||||||
|
|
||||||
class TestRoomDisplayNameFallbackProvider : RoomDisplayNameFallbackProvider {
|
class TestRoomDisplayNameFallbackProvider : RoomDisplayNameFallbackProvider {
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,9 @@ import okhttp3.Interceptor
|
||||||
import org.matrix.android.sdk.api.crypto.MXCryptoConfig
|
import org.matrix.android.sdk.api.crypto.MXCryptoConfig
|
||||||
import org.matrix.android.sdk.api.metrics.CryptoMetricPlugin
|
import org.matrix.android.sdk.api.metrics.CryptoMetricPlugin
|
||||||
import org.matrix.android.sdk.api.metrics.MetricPlugin
|
import org.matrix.android.sdk.api.metrics.MetricPlugin
|
||||||
|
import org.matrix.android.sdk.api.provider.CustomEventTypesProvider
|
||||||
|
import org.matrix.android.sdk.api.provider.MatrixItemDisplayNameFallbackProvider
|
||||||
|
import org.matrix.android.sdk.api.provider.RoomDisplayNameFallbackProvider
|
||||||
import java.net.Proxy
|
import java.net.Proxy
|
||||||
|
|
||||||
data class MatrixConfiguration(
|
data class MatrixConfiguration(
|
||||||
|
@ -68,7 +71,7 @@ data class MatrixConfiguration(
|
||||||
/**
|
/**
|
||||||
* Thread messages default enable/disabled value.
|
* Thread messages default enable/disabled value.
|
||||||
*/
|
*/
|
||||||
val threadMessagesEnabledDefault: Boolean = false,
|
val threadMessagesEnabledDefault: Boolean = true,
|
||||||
/**
|
/**
|
||||||
* List of network interceptors, they will be added when building an OkHttp client.
|
* List of network interceptors, they will be added when building an OkHttp client.
|
||||||
*/
|
*/
|
||||||
|
@ -77,11 +80,14 @@ data class MatrixConfiguration(
|
||||||
* Sync configuration.
|
* Sync configuration.
|
||||||
*/
|
*/
|
||||||
val syncConfig: SyncConfig = SyncConfig(),
|
val syncConfig: SyncConfig = SyncConfig(),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Metrics plugin that can be used to capture metrics from matrix-sdk-android.
|
* Metrics plugin that can be used to capture metrics from matrix-sdk-android.
|
||||||
*/
|
*/
|
||||||
val metricPlugins: List<MetricPlugin> = emptyList(),
|
val metricPlugins: List<MetricPlugin> = emptyList(),
|
||||||
|
|
||||||
val cryptoAnalyticsPlugin: CryptoMetricPlugin? = null
|
val cryptoAnalyticsPlugin: CryptoMetricPlugin? = null,
|
||||||
|
/**
|
||||||
|
* CustomEventTypesProvider to provide custom event types to the sdk which should be processed with internal events.
|
||||||
|
*/
|
||||||
|
val customEventTypesProvider: CustomEventTypesProvider? = null,
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.api.provider
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide custom event types which should be processed with the internal event types.
|
||||||
|
*/
|
||||||
|
interface CustomEventTypesProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom event types to include when computing [RoomSummary.latestPreviewableEvent].
|
||||||
|
*/
|
||||||
|
val customPreviewableEventTypes: List<String>
|
||||||
|
}
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.matrix.android.sdk.api
|
package org.matrix.android.sdk.api.provider
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.util.MatrixItem
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.matrix.android.sdk.api
|
package org.matrix.android.sdk.api.provider
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This interface exists to let the implementation provide localized room display name fallback.
|
* This interface exists to let the implementation provide localized room display name fallback.
|
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.api.session.room.threads
|
||||||
|
|
||||||
|
sealed class FetchThreadsResult {
|
||||||
|
data class ShouldFetchMore(val nextBatch: String) : FetchThreadsResult()
|
||||||
|
object ReachedEnd : FetchThreadsResult()
|
||||||
|
object Failed : FetchThreadsResult()
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.api.session.room.threads
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = false)
|
||||||
|
enum class ThreadFilter {
|
||||||
|
@Json(name = "all") ALL,
|
||||||
|
@Json(name = "participated") PARTICIPATED,
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.api.session.room.threads
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.paging.PagedList
|
||||||
|
import org.matrix.android.sdk.api.session.room.ResultBoundaries
|
||||||
|
import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary
|
||||||
|
|
||||||
|
data class ThreadLivePageResult(
|
||||||
|
val livePagedList: LiveData<PagedList<ThreadSummary>>,
|
||||||
|
val liveBoundaries: LiveData<ResultBoundaries>
|
||||||
|
)
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.api.session.room.threads
|
package org.matrix.android.sdk.api.session.room.threads
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.paging.PagedList
|
||||||
import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary
|
import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -27,15 +27,14 @@ import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary
|
||||||
*/
|
*/
|
||||||
interface ThreadsService {
|
interface ThreadsService {
|
||||||
|
|
||||||
/**
|
suspend fun getPagedThreadsList(userParticipating: Boolean, pagedListConfig: PagedList.Config): ThreadLivePageResult
|
||||||
* Returns a [LiveData] list of all the [ThreadSummary] that exists at the room level.
|
|
||||||
*/
|
suspend fun fetchThreadList(nextBatchId: String?, limit: Int, filter: ThreadFilter = ThreadFilter.ALL): FetchThreadsResult
|
||||||
fun getAllThreadSummariesLive(): LiveData<List<ThreadSummary>>
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a list of all the [ThreadSummary] that exists at the room level.
|
* Returns a list of all the [ThreadSummary] that exists at the room level.
|
||||||
*/
|
*/
|
||||||
fun getAllThreadSummaries(): List<ThreadSummary>
|
suspend fun getAllThreadSummaries(): List<ThreadSummary>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enhance the provided ThreadSummary[List] by adding the latest
|
* Enhance the provided ThreadSummary[List] by adding the latest
|
||||||
|
@ -51,9 +50,4 @@ interface ThreadsService {
|
||||||
* @param limit defines the number of max results the api will respond with
|
* @param limit defines the number of max results the api will respond with
|
||||||
*/
|
*/
|
||||||
suspend fun fetchThreadTimeline(rootThreadEventId: String, from: String, limit: Int)
|
suspend fun fetchThreadTimeline(rootThreadEventId: String, from: String, limit: Int)
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch all thread summaries for the current room using the enhanced /messages api.
|
|
||||||
*/
|
|
||||||
suspend fun fetchThreadSummaries()
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,6 +62,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo042
|
||||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo043
|
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo043
|
||||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo044
|
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo044
|
||||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo045
|
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo045
|
||||||
|
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo046
|
||||||
import org.matrix.android.sdk.internal.util.Normalizer
|
import org.matrix.android.sdk.internal.util.Normalizer
|
||||||
import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration
|
import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -70,7 +71,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
|
||||||
private val normalizer: Normalizer
|
private val normalizer: Normalizer
|
||||||
) : MatrixRealmMigration(
|
) : MatrixRealmMigration(
|
||||||
dbName = "Session",
|
dbName = "Session",
|
||||||
schemaVersion = 45L,
|
schemaVersion = 46L,
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* Forces all RealmSessionStoreMigration instances to be equal.
|
* Forces all RealmSessionStoreMigration instances to be equal.
|
||||||
|
@ -125,5 +126,6 @@ internal class RealmSessionStoreMigration @Inject constructor(
|
||||||
if (oldVersion < 43) MigrateSessionTo043(realm).perform()
|
if (oldVersion < 43) MigrateSessionTo043(realm).perform()
|
||||||
if (oldVersion < 44) MigrateSessionTo044(realm).perform()
|
if (oldVersion < 44) MigrateSessionTo044(realm).perform()
|
||||||
if (oldVersion < 45) MigrateSessionTo045(realm).perform()
|
if (oldVersion < 45) MigrateSessionTo045(realm).perform()
|
||||||
|
if (oldVersion < 46) MigrateSessionTo046(realm).perform()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,9 +37,11 @@ import org.matrix.android.sdk.internal.database.model.EventEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.EventInsertType
|
import org.matrix.android.sdk.internal.database.model.EventInsertType
|
||||||
import org.matrix.android.sdk.internal.database.model.RoomEntity
|
import org.matrix.android.sdk.internal.database.model.RoomEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
|
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
|
||||||
|
import org.matrix.android.sdk.internal.database.model.threads.ThreadListPageEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntity
|
import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntityFields
|
import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntityFields
|
||||||
import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore
|
import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore
|
||||||
|
import org.matrix.android.sdk.internal.database.query.get
|
||||||
import org.matrix.android.sdk.internal.database.query.getOrCreate
|
import org.matrix.android.sdk.internal.database.query.getOrCreate
|
||||||
import org.matrix.android.sdk.internal.database.query.getOrNull
|
import org.matrix.android.sdk.internal.database.query.getOrNull
|
||||||
import org.matrix.android.sdk.internal.database.query.where
|
import org.matrix.android.sdk.internal.database.query.where
|
||||||
|
@ -113,16 +115,16 @@ internal fun ThreadSummaryEntity.Companion.createOrUpdate(
|
||||||
userId: String,
|
userId: String,
|
||||||
cryptoService: CryptoService? = null,
|
cryptoService: CryptoService? = null,
|
||||||
currentTimeMillis: Long,
|
currentTimeMillis: Long,
|
||||||
) {
|
): ThreadSummaryEntity? {
|
||||||
when (threadSummaryType) {
|
when (threadSummaryType) {
|
||||||
ThreadSummaryUpdateType.REPLACE -> {
|
ThreadSummaryUpdateType.REPLACE -> {
|
||||||
rootThreadEvent?.eventId ?: return
|
rootThreadEvent?.eventId ?: return null
|
||||||
rootThreadEvent.senderId ?: return
|
rootThreadEvent.senderId ?: return null
|
||||||
|
|
||||||
val numberOfThreads = rootThreadEvent.unsignedData?.relations?.latestThread?.count ?: return
|
val numberOfThreads = rootThreadEvent.unsignedData?.relations?.latestThread?.count ?: return null
|
||||||
|
|
||||||
// Something is wrong with the server return
|
// Something is wrong with the server return
|
||||||
if (numberOfThreads <= 0) return
|
if (numberOfThreads <= 0) return null
|
||||||
|
|
||||||
val threadSummary = ThreadSummaryEntity.getOrCreate(realm, roomId, rootThreadEvent.eventId).also {
|
val threadSummary = ThreadSummaryEntity.getOrCreate(realm, roomId, rootThreadEvent.eventId).also {
|
||||||
Timber.i("###THREADS ThreadSummaryHelper REPLACE eventId:${it.rootThreadEventId} ")
|
Timber.i("###THREADS ThreadSummaryHelper REPLACE eventId:${it.rootThreadEventId} ")
|
||||||
|
@ -153,12 +155,13 @@ internal fun ThreadSummaryEntity.Companion.createOrUpdate(
|
||||||
)
|
)
|
||||||
|
|
||||||
roomEntity.addIfNecessary(threadSummary)
|
roomEntity.addIfNecessary(threadSummary)
|
||||||
|
return threadSummary
|
||||||
}
|
}
|
||||||
ThreadSummaryUpdateType.ADD -> {
|
ThreadSummaryUpdateType.ADD -> {
|
||||||
val rootThreadEventId = threadEventEntity?.rootThreadEventId ?: return
|
val rootThreadEventId = threadEventEntity?.rootThreadEventId ?: return null
|
||||||
Timber.i("###THREADS ThreadSummaryHelper ADD for root eventId:$rootThreadEventId")
|
Timber.i("###THREADS ThreadSummaryHelper ADD for root eventId:$rootThreadEventId")
|
||||||
|
|
||||||
val threadSummary = ThreadSummaryEntity.getOrNull(realm, roomId, rootThreadEventId)
|
var threadSummary = ThreadSummaryEntity.getOrNull(realm, roomId, rootThreadEventId)
|
||||||
if (threadSummary != null) {
|
if (threadSummary != null) {
|
||||||
// ThreadSummary exists so lets add the latest event
|
// ThreadSummary exists so lets add the latest event
|
||||||
Timber.i("###THREADS ThreadSummaryHelper ADD root eventId:$rootThreadEventId exists, lets update latest thread event.")
|
Timber.i("###THREADS ThreadSummaryHelper ADD root eventId:$rootThreadEventId exists, lets update latest thread event.")
|
||||||
|
@ -172,7 +175,7 @@ internal fun ThreadSummaryEntity.Companion.createOrUpdate(
|
||||||
Timber.i("###THREADS ThreadSummaryHelper ADD root eventId:$rootThreadEventId do not exists, lets try to create one")
|
Timber.i("###THREADS ThreadSummaryHelper ADD root eventId:$rootThreadEventId do not exists, lets try to create one")
|
||||||
threadEventEntity.findRootThreadEvent()?.let { rootThreadEventEntity ->
|
threadEventEntity.findRootThreadEvent()?.let { rootThreadEventEntity ->
|
||||||
// Root thread event entity exists so lets create a new record
|
// Root thread event entity exists so lets create a new record
|
||||||
ThreadSummaryEntity.getOrCreate(realm, roomId, rootThreadEventEntity.eventId).let {
|
threadSummary = ThreadSummaryEntity.getOrCreate(realm, roomId, rootThreadEventEntity.eventId).also {
|
||||||
it.updateThreadSummary(
|
it.updateThreadSummary(
|
||||||
rootThreadEventEntity = rootThreadEventEntity,
|
rootThreadEventEntity = rootThreadEventEntity,
|
||||||
numberOfThreads = 1,
|
numberOfThreads = 1,
|
||||||
|
@ -183,8 +186,13 @@ internal fun ThreadSummaryEntity.Companion.createOrUpdate(
|
||||||
roomEntity.addIfNecessary(it)
|
roomEntity.addIfNecessary(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
threadSummary?.let {
|
||||||
|
ThreadListPageEntity.get(realm, roomId)?.threadSummaries?.add(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return threadSummary
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.internal.database.migration
|
||||||
|
|
||||||
|
import io.realm.DynamicRealm
|
||||||
|
import org.matrix.android.sdk.internal.database.model.threads.ThreadListPageEntityFields
|
||||||
|
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||||
|
|
||||||
|
internal class MigrateSessionTo046(realm: DynamicRealm) : RealmMigrator(realm, 46) {
|
||||||
|
|
||||||
|
override fun doMigrate(realm: DynamicRealm) {
|
||||||
|
realm.schema.create("ThreadListPageEntity")
|
||||||
|
.addField(ThreadListPageEntityFields.ROOM_ID, String::class.java)
|
||||||
|
.addPrimaryKey(ThreadListPageEntityFields.ROOM_ID)
|
||||||
|
.setRequired(ThreadListPageEntityFields.ROOM_ID, true)
|
||||||
|
.addRealmListField(ThreadListPageEntityFields.THREAD_SUMMARIES.`$`, realm.schema.get("ThreadSummaryEntity")!!)
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.database.model
|
||||||
import io.realm.annotations.RealmModule
|
import io.realm.annotations.RealmModule
|
||||||
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
|
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity
|
import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity
|
||||||
|
import org.matrix.android.sdk.internal.database.model.threads.ThreadListPageEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntity
|
import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntity
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -72,6 +73,7 @@ import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntit
|
||||||
UserPresenceEntity::class,
|
UserPresenceEntity::class,
|
||||||
ThreadSummaryEntity::class,
|
ThreadSummaryEntity::class,
|
||||||
SyncFilterParamsEntity::class,
|
SyncFilterParamsEntity::class,
|
||||||
|
ThreadListPageEntity::class
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
internal class SessionRealmModule
|
internal class SessionRealmModule
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.internal.database.model.threads
|
||||||
|
|
||||||
|
import io.realm.RealmList
|
||||||
|
import io.realm.RealmObject
|
||||||
|
import io.realm.annotations.PrimaryKey
|
||||||
|
|
||||||
|
internal open class ThreadListPageEntity(
|
||||||
|
@PrimaryKey var roomId: String = "",
|
||||||
|
var threadSummaries: RealmList<ThreadSummaryEntity> = RealmList()
|
||||||
|
) : RealmObject() {
|
||||||
|
companion object
|
||||||
|
}
|
|
@ -40,5 +40,8 @@ internal open class ThreadSummaryEntity(
|
||||||
@LinkingObjects("threadSummaries")
|
@LinkingObjects("threadSummaries")
|
||||||
val room: RealmResults<RoomEntity>? = null
|
val room: RealmResults<RoomEntity>? = null
|
||||||
|
|
||||||
|
@LinkingObjects("threadSummaries")
|
||||||
|
val page: RealmResults<ThreadListPageEntity>? = null
|
||||||
|
|
||||||
companion object
|
companion object
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.internal.database.query
|
||||||
|
|
||||||
|
import io.realm.Realm
|
||||||
|
import io.realm.kotlin.createObject
|
||||||
|
import io.realm.kotlin.where
|
||||||
|
import org.matrix.android.sdk.internal.database.model.threads.ThreadListPageEntity
|
||||||
|
import org.matrix.android.sdk.internal.database.model.threads.ThreadListPageEntityFields
|
||||||
|
|
||||||
|
internal fun ThreadListPageEntity.Companion.get(realm: Realm, roomId: String): ThreadListPageEntity? {
|
||||||
|
return realm.where<ThreadListPageEntity>().equalTo(ThreadListPageEntityFields.ROOM_ID, roomId).findFirst()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun ThreadListPageEntity.Companion.getOrCreate(realm: Realm, roomId: String): ThreadListPageEntity {
|
||||||
|
return get(realm, roomId) ?: realm.createObject(roomId)
|
||||||
|
}
|
|
@ -22,6 +22,7 @@ import io.realm.RealmQuery
|
||||||
import io.realm.RealmResults
|
import io.realm.RealmResults
|
||||||
import io.realm.Sort
|
import io.realm.Sort
|
||||||
import io.realm.kotlin.where
|
import io.realm.kotlin.where
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEventFilters
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEventFilters
|
||||||
import org.matrix.android.sdk.internal.database.model.ChunkEntity
|
import org.matrix.android.sdk.internal.database.model.ChunkEntity
|
||||||
|
@ -94,6 +95,18 @@ internal fun RealmQuery<TimelineEventEntity>.filterEvents(filters: TimelineEvent
|
||||||
if (filters.filterTypes && filters.allowedTypes.isNotEmpty()) {
|
if (filters.filterTypes && filters.allowedTypes.isNotEmpty()) {
|
||||||
beginGroup()
|
beginGroup()
|
||||||
filters.allowedTypes.forEachIndexed { index, filter ->
|
filters.allowedTypes.forEachIndexed { index, filter ->
|
||||||
|
if (filter.eventType == EventType.ENCRYPTED) {
|
||||||
|
val otherTypes = filters.allowedTypes.minus(filter).map { it.eventType }
|
||||||
|
if (filter.stateKey == null) {
|
||||||
|
filterEncryptedTypes(otherTypes)
|
||||||
|
} else {
|
||||||
|
beginGroup()
|
||||||
|
filterEncryptedTypes(otherTypes)
|
||||||
|
and()
|
||||||
|
equalTo(TimelineEventEntityFields.ROOT.STATE_KEY, filter.stateKey)
|
||||||
|
endGroup()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
if (filter.stateKey == null) {
|
if (filter.stateKey == null) {
|
||||||
equalTo(TimelineEventEntityFields.ROOT.TYPE, filter.eventType)
|
equalTo(TimelineEventEntityFields.ROOT.TYPE, filter.eventType)
|
||||||
} else {
|
} else {
|
||||||
|
@ -103,6 +116,7 @@ internal fun RealmQuery<TimelineEventEntity>.filterEvents(filters: TimelineEvent
|
||||||
equalTo(TimelineEventEntityFields.ROOT.STATE_KEY, filter.stateKey)
|
equalTo(TimelineEventEntityFields.ROOT.STATE_KEY, filter.stateKey)
|
||||||
endGroup()
|
endGroup()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (index != filters.allowedTypes.size - 1) {
|
if (index != filters.allowedTypes.size - 1) {
|
||||||
or()
|
or()
|
||||||
}
|
}
|
||||||
|
@ -115,7 +129,6 @@ internal fun RealmQuery<TimelineEventEntity>.filterEvents(filters: TimelineEvent
|
||||||
if (filters.filterEdits) {
|
if (filters.filterEdits) {
|
||||||
not().like(TimelineEventEntityFields.ROOT.CONTENT, TimelineEventFilter.Content.EDIT)
|
not().like(TimelineEventEntityFields.ROOT.CONTENT, TimelineEventFilter.Content.EDIT)
|
||||||
not().like(TimelineEventEntityFields.ROOT.CONTENT, TimelineEventFilter.Content.RESPONSE)
|
not().like(TimelineEventEntityFields.ROOT.CONTENT, TimelineEventFilter.Content.RESPONSE)
|
||||||
not().like(TimelineEventEntityFields.ROOT.CONTENT, TimelineEventFilter.Content.REFERENCE)
|
|
||||||
}
|
}
|
||||||
if (filters.filterRedacted) {
|
if (filters.filterRedacted) {
|
||||||
not().like(TimelineEventEntityFields.ROOT.UNSIGNED_DATA, TimelineEventFilter.Unsigned.REDACTED)
|
not().like(TimelineEventEntityFields.ROOT.UNSIGNED_DATA, TimelineEventFilter.Unsigned.REDACTED)
|
||||||
|
@ -124,6 +137,21 @@ internal fun RealmQuery<TimelineEventEntity>.filterEvents(filters: TimelineEvent
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun RealmQuery<TimelineEventEntity>.filterEncryptedTypes(allowedTypes: List<String>): RealmQuery<TimelineEventEntity> {
|
||||||
|
beginGroup()
|
||||||
|
equalTo(TimelineEventEntityFields.ROOT.TYPE, EventType.ENCRYPTED)
|
||||||
|
and()
|
||||||
|
beginGroup()
|
||||||
|
isNull(TimelineEventEntityFields.ROOT.DECRYPTION_RESULT_JSON)
|
||||||
|
allowedTypes.forEach { eventType ->
|
||||||
|
or()
|
||||||
|
like(TimelineEventEntityFields.ROOT.DECRYPTION_RESULT_JSON, TimelineEventFilter.DecryptedContent.type(eventType))
|
||||||
|
}
|
||||||
|
endGroup()
|
||||||
|
endGroup()
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
internal fun RealmQuery<TimelineEventEntity>.filterTypes(filterTypes: List<String>): RealmQuery<TimelineEventEntity> {
|
internal fun RealmQuery<TimelineEventEntity>.filterTypes(filterTypes: List<String>): RealmQuery<TimelineEventEntity> {
|
||||||
return if (filterTypes.isEmpty()) {
|
return if (filterTypes.isEmpty()) {
|
||||||
this
|
this
|
||||||
|
|
|
@ -34,6 +34,7 @@ internal object TimelineEventFilter {
|
||||||
*/
|
*/
|
||||||
internal object DecryptedContent {
|
internal object DecryptedContent {
|
||||||
internal const val URL = """{*"file":*"url":*}"""
|
internal const val URL = """{*"file":*"url":*}"""
|
||||||
|
fun type(type: String) = """{*"type":*"$type"*}"""
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -35,6 +35,7 @@ import org.matrix.android.sdk.internal.session.room.membership.joining.InviteBod
|
||||||
import org.matrix.android.sdk.internal.session.room.membership.threepid.ThreePidInviteBody
|
import org.matrix.android.sdk.internal.session.room.membership.threepid.ThreePidInviteBody
|
||||||
import org.matrix.android.sdk.internal.session.room.read.ReadBody
|
import org.matrix.android.sdk.internal.session.room.read.ReadBody
|
||||||
import org.matrix.android.sdk.internal.session.room.relation.RelationsResponse
|
import org.matrix.android.sdk.internal.session.room.relation.RelationsResponse
|
||||||
|
import org.matrix.android.sdk.internal.session.room.relation.threads.ThreadSummariesResponse
|
||||||
import org.matrix.android.sdk.internal.session.room.reporting.ReportContentBody
|
import org.matrix.android.sdk.internal.session.room.reporting.ReportContentBody
|
||||||
import org.matrix.android.sdk.internal.session.room.send.SendResponse
|
import org.matrix.android.sdk.internal.session.room.send.SendResponse
|
||||||
import org.matrix.android.sdk.internal.session.room.tags.TagBody
|
import org.matrix.android.sdk.internal.session.room.tags.TagBody
|
||||||
|
@ -464,4 +465,12 @@ internal interface RoomAPI {
|
||||||
@Path("roomIdOrAlias") roomidOrAlias: String,
|
@Path("roomIdOrAlias") roomidOrAlias: String,
|
||||||
@Query("via") viaServers: List<String>?
|
@Query("via") viaServers: List<String>?
|
||||||
): RoomStrippedState
|
): RoomStrippedState
|
||||||
|
|
||||||
|
@GET(NetworkConstants.URI_API_PREFIX_PATH_V1 + "rooms/{roomId}/threads")
|
||||||
|
suspend fun getThreadsList(
|
||||||
|
@Path("roomId") roomId: String,
|
||||||
|
@Query("include") include: String? = "all",
|
||||||
|
@Query("from") from: String? = null,
|
||||||
|
@Query("limit") limit: Int? = null
|
||||||
|
): ThreadSummariesResponse
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,6 @@ import org.matrix.android.sdk.api.session.room.getTimelineEvent
|
||||||
import org.matrix.android.sdk.api.session.room.model.PollSummaryContent
|
import org.matrix.android.sdk.api.session.room.model.PollSummaryContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.VoteInfo
|
import org.matrix.android.sdk.api.session.room.model.VoteInfo
|
||||||
import org.matrix.android.sdk.api.session.room.model.VoteSummary
|
import org.matrix.android.sdk.api.session.room.model.VoteSummary
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageEndPollContent
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessagePollResponseContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessagePollResponseContent
|
||||||
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
|
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
|
||||||
|
@ -78,7 +77,7 @@ class DefaultPollAggregationProcessor @Inject constructor() : PollAggregationPro
|
||||||
val content = event.getClearContent()?.toModel<MessagePollResponseContent>() ?: return false
|
val content = event.getClearContent()?.toModel<MessagePollResponseContent>() ?: return false
|
||||||
val roomId = event.roomId ?: return false
|
val roomId = event.roomId ?: return false
|
||||||
val senderId = event.senderId ?: return false
|
val senderId = event.senderId ?: return false
|
||||||
val targetEventId = (event.getRelationContent() ?: content.relatesTo)?.eventId ?: return false
|
val targetEventId = event.getRelationContent()?.eventId ?: return false
|
||||||
val targetPollContent = getPollContent(session, roomId, targetEventId) ?: return false
|
val targetPollContent = getPollContent(session, roomId, targetEventId) ?: return false
|
||||||
|
|
||||||
val annotationsSummaryEntity = getAnnotationsSummaryEntity(realm, roomId, targetEventId)
|
val annotationsSummaryEntity = getAnnotationsSummaryEntity(realm, roomId, targetEventId)
|
||||||
|
@ -154,9 +153,8 @@ class DefaultPollAggregationProcessor @Inject constructor() : PollAggregationPro
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handlePollEndEvent(session: Session, powerLevelsHelper: PowerLevelsHelper, realm: Realm, event: Event): Boolean {
|
override fun handlePollEndEvent(session: Session, powerLevelsHelper: PowerLevelsHelper, realm: Realm, event: Event): Boolean {
|
||||||
val content = event.getClearContent()?.toModel<MessageEndPollContent>() ?: return false
|
|
||||||
val roomId = event.roomId ?: return false
|
val roomId = event.roomId ?: return false
|
||||||
val pollEventId = content.relatesTo?.eventId ?: return false
|
val pollEventId = event.getRelationContent()?.eventId ?: return false
|
||||||
val pollOwnerId = getPollEvent(session, roomId, pollEventId)?.root?.senderId
|
val pollOwnerId = getPollEvent(session, roomId, pollEventId)?.root?.senderId
|
||||||
val isPollOwner = pollOwnerId == event.senderId
|
val isPollOwner = pollOwnerId == event.senderId
|
||||||
|
|
||||||
|
|
|
@ -16,37 +16,38 @@
|
||||||
package org.matrix.android.sdk.internal.session.room.relation.threads
|
package org.matrix.android.sdk.internal.session.room.relation.threads
|
||||||
|
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
|
import io.realm.RealmList
|
||||||
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
|
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.threads.FetchThreadsResult
|
||||||
|
import org.matrix.android.sdk.api.session.room.threads.ThreadFilter
|
||||||
import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummaryUpdateType
|
import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummaryUpdateType
|
||||||
import org.matrix.android.sdk.internal.database.helper.createOrUpdate
|
import org.matrix.android.sdk.internal.database.helper.createOrUpdate
|
||||||
import org.matrix.android.sdk.internal.database.model.RoomEntity
|
import org.matrix.android.sdk.internal.database.model.RoomEntity
|
||||||
|
import org.matrix.android.sdk.internal.database.model.threads.ThreadListPageEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntity
|
import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntity
|
||||||
|
import org.matrix.android.sdk.internal.database.query.getOrCreate
|
||||||
import org.matrix.android.sdk.internal.database.query.where
|
import org.matrix.android.sdk.internal.database.query.where
|
||||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
import org.matrix.android.sdk.internal.di.UserId
|
import org.matrix.android.sdk.internal.di.UserId
|
||||||
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
||||||
import org.matrix.android.sdk.internal.network.executeRequest
|
import org.matrix.android.sdk.internal.network.executeRequest
|
||||||
import org.matrix.android.sdk.internal.session.filter.FilterFactory
|
|
||||||
import org.matrix.android.sdk.internal.session.room.RoomAPI
|
import org.matrix.android.sdk.internal.session.room.RoomAPI
|
||||||
import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection
|
|
||||||
import org.matrix.android.sdk.internal.session.room.timeline.PaginationResponse
|
|
||||||
import org.matrix.android.sdk.internal.task.Task
|
import org.matrix.android.sdk.internal.task.Task
|
||||||
import org.matrix.android.sdk.internal.util.awaitTransaction
|
import org.matrix.android.sdk.internal.util.awaitTransaction
|
||||||
import org.matrix.android.sdk.internal.util.time.Clock
|
import org.matrix.android.sdk.internal.util.time.Clock
|
||||||
import timber.log.Timber
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
/***
|
/***
|
||||||
* This class is responsible to Fetch all the thread in the current room,
|
* This class is responsible to Fetch all the thread in the current room,
|
||||||
* To fetch all threads in a room, the /messages API is used with newly added filtering options.
|
* To fetch all threads in a room, the /messages API is used with newly added filtering options.
|
||||||
*/
|
*/
|
||||||
internal interface FetchThreadSummariesTask : Task<FetchThreadSummariesTask.Params, DefaultFetchThreadSummariesTask.Result> {
|
internal interface FetchThreadSummariesTask : Task<FetchThreadSummariesTask.Params, FetchThreadsResult> {
|
||||||
data class Params(
|
data class Params(
|
||||||
val roomId: String,
|
val roomId: String,
|
||||||
val from: String = "",
|
val from: String? = null,
|
||||||
val limit: Int = 500,
|
val limit: Int = 5,
|
||||||
val isUserParticipating: Boolean = true
|
val filter: ThreadFilter? = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,39 +60,43 @@ internal class DefaultFetchThreadSummariesTask @Inject constructor(
|
||||||
private val clock: Clock,
|
private val clock: Clock,
|
||||||
) : FetchThreadSummariesTask {
|
) : FetchThreadSummariesTask {
|
||||||
|
|
||||||
override suspend fun execute(params: FetchThreadSummariesTask.Params): Result {
|
override suspend fun execute(params: FetchThreadSummariesTask.Params): FetchThreadsResult {
|
||||||
val filter = FilterFactory.createThreadsFilter(
|
val response = executeRequest(globalErrorReceiver) {
|
||||||
numberOfEvents = params.limit,
|
roomAPI.getThreadsList(
|
||||||
userId = if (params.isUserParticipating) userId else null
|
roomId = params.roomId,
|
||||||
).toJSONString()
|
include = params.filter?.toString()?.lowercase(),
|
||||||
|
from = params.from,
|
||||||
val response = executeRequest(
|
limit = params.limit
|
||||||
globalErrorReceiver,
|
)
|
||||||
canRetry = true
|
|
||||||
) {
|
|
||||||
roomAPI.getRoomMessagesFrom(params.roomId, params.from, PaginationDirection.BACKWARDS.value, params.limit, filter)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Timber.i("###THREADS DefaultFetchThreadSummariesTask Fetched size:${response.events.size} nextBatch:${response.end} ")
|
handleResponse(response, params)
|
||||||
|
|
||||||
return handleResponse(response, params)
|
return when {
|
||||||
|
response.nextBatch != null -> FetchThreadsResult.ShouldFetchMore(response.nextBatch)
|
||||||
|
else -> FetchThreadsResult.ReachedEnd
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun handleResponse(
|
private suspend fun handleResponse(
|
||||||
response: PaginationResponse,
|
response: ThreadSummariesResponse,
|
||||||
params: FetchThreadSummariesTask.Params
|
params: FetchThreadSummariesTask.Params
|
||||||
): Result {
|
) {
|
||||||
val rootThreadList = response.events
|
val rootThreadList = response.chunk
|
||||||
|
|
||||||
|
val threadSummaries = RealmList<ThreadSummaryEntity>()
|
||||||
|
|
||||||
monarchy.awaitTransaction { realm ->
|
monarchy.awaitTransaction { realm ->
|
||||||
val roomEntity = RoomEntity.where(realm, roomId = params.roomId).findFirst() ?: return@awaitTransaction
|
val roomEntity = RoomEntity.where(realm, roomId = params.roomId).findFirst() ?: return@awaitTransaction
|
||||||
|
|
||||||
val roomMemberContentsByUser = HashMap<String, RoomMemberContent?>()
|
val roomMemberContentsByUser = HashMap<String, RoomMemberContent?>()
|
||||||
|
|
||||||
for (rootThreadEvent in rootThreadList) {
|
for (rootThreadEvent in rootThreadList) {
|
||||||
if (rootThreadEvent.eventId == null || rootThreadEvent.senderId == null || rootThreadEvent.type == null) {
|
if (rootThreadEvent.eventId == null || rootThreadEvent.senderId == null || rootThreadEvent.type == null) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
ThreadSummaryEntity.createOrUpdate(
|
val threadSummary = ThreadSummaryEntity.createOrUpdate(
|
||||||
threadSummaryType = ThreadSummaryUpdateType.REPLACE,
|
threadSummaryType = ThreadSummaryUpdateType.REPLACE,
|
||||||
realm = realm,
|
realm = realm,
|
||||||
roomId = params.roomId,
|
roomId = params.roomId,
|
||||||
|
@ -102,14 +107,16 @@ internal class DefaultFetchThreadSummariesTask @Inject constructor(
|
||||||
cryptoService = cryptoService,
|
cryptoService = cryptoService,
|
||||||
currentTimeMillis = clock.epochMillis(),
|
currentTimeMillis = clock.epochMillis(),
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
threadSummaries.add(threadSummary)
|
||||||
return Result.SUCCESS
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class Result {
|
val page = ThreadListPageEntity.getOrCreate(realm, params.roomId)
|
||||||
SHOULD_FETCH_MORE,
|
threadSummaries.forEach {
|
||||||
REACHED_END,
|
if (!page.threadSummaries.contains(it)) {
|
||||||
SUCCESS
|
page.threadSummaries.add(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.matrix.android.sdk.internal.session.room.relation.threads
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal data class ThreadSummariesResponse(
|
||||||
|
@Json(name = "chunk") val chunk: List<Event>,
|
||||||
|
@Json(name = "next_batch") val nextBatch: String?,
|
||||||
|
@Json(name = "prev_batch") val prevBatch: String?
|
||||||
|
)
|
|
@ -17,17 +17,23 @@
|
||||||
package org.matrix.android.sdk.internal.session.room.summary
|
package org.matrix.android.sdk.internal.session.room.summary
|
||||||
|
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
|
import org.matrix.android.sdk.api.MatrixConfiguration
|
||||||
import org.matrix.android.sdk.api.session.room.summary.RoomSummaryConstants
|
import org.matrix.android.sdk.api.session.room.summary.RoomSummaryConstants
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.EventTypeFilter
|
import org.matrix.android.sdk.api.session.room.timeline.EventTypeFilter
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEventFilters
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEventFilters
|
||||||
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
|
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
|
||||||
import org.matrix.android.sdk.internal.database.query.latestEvent
|
import org.matrix.android.sdk.internal.database.query.latestEvent
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal object RoomSummaryEventsHelper {
|
internal class RoomSummaryEventsHelper @Inject constructor(
|
||||||
|
matrixConfiguration: MatrixConfiguration,
|
||||||
|
) {
|
||||||
|
|
||||||
private val previewFilters = TimelineEventFilters(
|
private val previewFilters = TimelineEventFilters(
|
||||||
filterTypes = true,
|
filterTypes = true,
|
||||||
allowedTypes = RoomSummaryConstants.PREVIEWABLE_TYPES.map { EventTypeFilter(eventType = it, stateKey = null) },
|
allowedTypes = RoomSummaryConstants.PREVIEWABLE_TYPES
|
||||||
|
.plus(matrixConfiguration.customEventTypesProvider?.customPreviewableEventTypes.orEmpty())
|
||||||
|
.map { EventTypeFilter(eventType = it, stateKey = null) },
|
||||||
filterUseless = true,
|
filterUseless = true,
|
||||||
filterRedacted = false,
|
filterRedacted = false,
|
||||||
filterEdits = true
|
filterEdits = true
|
||||||
|
|
|
@ -73,13 +73,14 @@ internal class RoomSummaryUpdater @Inject constructor(
|
||||||
private val roomAvatarResolver: RoomAvatarResolver,
|
private val roomAvatarResolver: RoomAvatarResolver,
|
||||||
private val roomAccountDataDataSource: RoomAccountDataDataSource,
|
private val roomAccountDataDataSource: RoomAccountDataDataSource,
|
||||||
private val homeServerCapabilitiesService: HomeServerCapabilitiesService,
|
private val homeServerCapabilitiesService: HomeServerCapabilitiesService,
|
||||||
private val roomSummaryEventDecryptor: RoomSummaryEventDecryptor
|
private val roomSummaryEventDecryptor: RoomSummaryEventDecryptor,
|
||||||
|
private val roomSummaryEventsHelper: RoomSummaryEventsHelper,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun refreshLatestPreviewContent(realm: Realm, roomId: String) {
|
fun refreshLatestPreviewContent(realm: Realm, roomId: String) {
|
||||||
val roomSummaryEntity = RoomSummaryEntity.getOrNull(realm, roomId)
|
val roomSummaryEntity = RoomSummaryEntity.getOrNull(realm, roomId)
|
||||||
if (roomSummaryEntity != null) {
|
if (roomSummaryEntity != null) {
|
||||||
val latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId)
|
val latestPreviewableEvent = roomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId)
|
||||||
latestPreviewableEvent?.attemptToDecrypt()
|
latestPreviewableEvent?.attemptToDecrypt()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -141,7 +142,7 @@ internal class RoomSummaryUpdater @Inject constructor(
|
||||||
val encryptionEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_ENCRYPTION, stateKey = "")?.root
|
val encryptionEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_ENCRYPTION, stateKey = "")?.root
|
||||||
Timber.d("## CRYPTO: currentEncryptionEvent is $encryptionEvent")
|
Timber.d("## CRYPTO: currentEncryptionEvent is $encryptionEvent")
|
||||||
|
|
||||||
val latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId)
|
val latestPreviewableEvent = roomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId)
|
||||||
|
|
||||||
val lastActivityFromEvent = latestPreviewableEvent?.root?.originServerTs
|
val lastActivityFromEvent = latestPreviewableEvent?.root?.originServerTs
|
||||||
if (lastActivityFromEvent != null) {
|
if (lastActivityFromEvent != null) {
|
||||||
|
@ -224,7 +225,7 @@ internal class RoomSummaryUpdater @Inject constructor(
|
||||||
fun updateSendingInformation(realm: Realm, roomId: String) {
|
fun updateSendingInformation(realm: Realm, roomId: String) {
|
||||||
val roomSummaryEntity = RoomSummaryEntity.getOrCreate(realm, roomId)
|
val roomSummaryEntity = RoomSummaryEntity.getOrCreate(realm, roomId)
|
||||||
roomSummaryEntity.updateHasFailedSending()
|
roomSummaryEntity.updateHasFailedSending()
|
||||||
roomSummaryEntity.latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId)
|
roomSummaryEntity.latestPreviewableEvent = roomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -16,30 +16,39 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.internal.session.room.threads
|
package org.matrix.android.sdk.internal.session.room.threads
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.paging.LivePagedListBuilder
|
||||||
|
import androidx.paging.PagedList
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
|
import io.realm.Sort
|
||||||
|
import io.realm.kotlin.where
|
||||||
|
import org.matrix.android.sdk.api.session.room.ResultBoundaries
|
||||||
|
import org.matrix.android.sdk.api.session.room.threads.FetchThreadsResult
|
||||||
|
import org.matrix.android.sdk.api.session.room.threads.ThreadFilter
|
||||||
|
import org.matrix.android.sdk.api.session.room.threads.ThreadLivePageResult
|
||||||
import org.matrix.android.sdk.api.session.room.threads.ThreadsService
|
import org.matrix.android.sdk.api.session.room.threads.ThreadsService
|
||||||
import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary
|
import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary
|
||||||
import org.matrix.android.sdk.internal.database.helper.enhanceWithEditions
|
import org.matrix.android.sdk.internal.database.helper.enhanceWithEditions
|
||||||
import org.matrix.android.sdk.internal.database.helper.findAllThreadsForRoomId
|
import org.matrix.android.sdk.internal.database.helper.findAllThreadsForRoomId
|
||||||
import org.matrix.android.sdk.internal.database.mapper.ThreadSummaryMapper
|
import org.matrix.android.sdk.internal.database.mapper.ThreadSummaryMapper
|
||||||
|
import org.matrix.android.sdk.internal.database.model.threads.ThreadListPageEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntity
|
import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntity
|
||||||
|
import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntityFields
|
||||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
import org.matrix.android.sdk.internal.di.UserId
|
|
||||||
import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadSummariesTask
|
import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadSummariesTask
|
||||||
import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask
|
import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask
|
||||||
|
import org.matrix.android.sdk.internal.util.awaitTransaction
|
||||||
|
|
||||||
internal class DefaultThreadsService @AssistedInject constructor(
|
internal class DefaultThreadsService @AssistedInject constructor(
|
||||||
@Assisted private val roomId: String,
|
@Assisted private val roomId: String,
|
||||||
@UserId private val userId: String,
|
|
||||||
private val fetchThreadTimelineTask: FetchThreadTimelineTask,
|
private val fetchThreadTimelineTask: FetchThreadTimelineTask,
|
||||||
private val fetchThreadSummariesTask: FetchThreadSummariesTask,
|
|
||||||
@SessionDatabase private val monarchy: Monarchy,
|
@SessionDatabase private val monarchy: Monarchy,
|
||||||
private val threadSummaryMapper: ThreadSummaryMapper
|
private val threadSummaryMapper: ThreadSummaryMapper,
|
||||||
|
private val fetchThreadSummariesTask: FetchThreadSummariesTask,
|
||||||
) : ThreadsService {
|
) : ThreadsService {
|
||||||
|
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
|
@ -47,16 +56,58 @@ internal class DefaultThreadsService @AssistedInject constructor(
|
||||||
fun create(roomId: String): DefaultThreadsService
|
fun create(roomId: String): DefaultThreadsService
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getAllThreadSummariesLive(): LiveData<List<ThreadSummary>> {
|
override suspend fun getPagedThreadsList(userParticipating: Boolean, pagedListConfig: PagedList.Config): ThreadLivePageResult {
|
||||||
return monarchy.findAllMappedWithChanges(
|
monarchy.awaitTransaction { realm ->
|
||||||
{ ThreadSummaryEntity.findAllThreadsForRoomId(it, roomId = roomId) },
|
realm.where<ThreadListPageEntity>().findAll().deleteAllFromRealm()
|
||||||
{
|
}
|
||||||
|
|
||||||
|
val realmDataSourceFactory = monarchy.createDataSourceFactory { realm ->
|
||||||
|
realm
|
||||||
|
.where<ThreadSummaryEntity>().equalTo(ThreadSummaryEntityFields.PAGE.ROOM_ID, roomId)
|
||||||
|
.sort(ThreadSummaryEntityFields.LATEST_THREAD_EVENT_ENTITY.ORIGIN_SERVER_TS, Sort.DESCENDING)
|
||||||
|
}
|
||||||
|
|
||||||
|
val dataSourceFactory = realmDataSourceFactory.map {
|
||||||
threadSummaryMapper.map(it)
|
threadSummaryMapper.map(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val boundaries = MutableLiveData(ResultBoundaries())
|
||||||
|
|
||||||
|
val builder = LivePagedListBuilder(dataSourceFactory, pagedListConfig).also {
|
||||||
|
it.setBoundaryCallback(object : PagedList.BoundaryCallback<ThreadSummary>() {
|
||||||
|
override fun onItemAtEndLoaded(itemAtEnd: ThreadSummary) {
|
||||||
|
boundaries.postValue(boundaries.value?.copy(endLoaded = true))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemAtFrontLoaded(itemAtFront: ThreadSummary) {
|
||||||
|
boundaries.postValue(boundaries.value?.copy(frontLoaded = true))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onZeroItemsLoaded() {
|
||||||
|
boundaries.postValue(boundaries.value?.copy(zeroItemLoaded = true))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
val livePagedList = monarchy.findAllPagedWithChanges(
|
||||||
|
realmDataSourceFactory,
|
||||||
|
builder
|
||||||
|
)
|
||||||
|
return ThreadLivePageResult(livePagedList, boundaries)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun fetchThreadList(nextBatchId: String?, limit: Int, filter: ThreadFilter): FetchThreadsResult {
|
||||||
|
return fetchThreadSummariesTask.execute(
|
||||||
|
FetchThreadSummariesTask.Params(
|
||||||
|
roomId = roomId,
|
||||||
|
from = nextBatchId,
|
||||||
|
limit = limit,
|
||||||
|
filter = filter
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getAllThreadSummaries(): List<ThreadSummary> {
|
override suspend fun getAllThreadSummaries(): List<ThreadSummary> {
|
||||||
return monarchy.fetchAllMappedSync(
|
return monarchy.fetchAllMappedSync(
|
||||||
{ ThreadSummaryEntity.findAllThreadsForRoomId(it, roomId = roomId) },
|
{ ThreadSummaryEntity.findAllThreadsForRoomId(it, roomId = roomId) },
|
||||||
{ threadSummaryMapper.map(it) }
|
{ threadSummaryMapper.map(it) }
|
||||||
|
@ -79,12 +130,4 @@ internal class DefaultThreadsService @AssistedInject constructor(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun fetchThreadSummaries() {
|
|
||||||
fetchThreadSummariesTask.execute(
|
|
||||||
FetchThreadSummariesTask.Params(
|
|
||||||
roomId = roomId
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@ import org.matrix.android.sdk.test.fakes.FakeRealm
|
||||||
import org.matrix.android.sdk.test.fakes.givenEqualTo
|
import org.matrix.android.sdk.test.fakes.givenEqualTo
|
||||||
import org.matrix.android.sdk.test.fakes.givenFindFirst
|
import org.matrix.android.sdk.test.fakes.givenFindFirst
|
||||||
|
|
||||||
class PollAggregationProcessorTest {
|
class DefaultPollAggregationProcessorTest {
|
||||||
|
|
||||||
private val pollAggregationProcessor: PollAggregationProcessor = DefaultPollAggregationProcessor()
|
private val pollAggregationProcessor: PollAggregationProcessor = DefaultPollAggregationProcessor()
|
||||||
private val realm = FakeRealm()
|
private val realm = FakeRealm()
|
|
@ -37,7 +37,7 @@ ext.versionMinor = 5
|
||||||
// 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.
|
||||||
ext.versionPatch = 12
|
ext.versionPatch = 14
|
||||||
|
|
||||||
static def getGitTimestamp() {
|
static def getGitTimestamp() {
|
||||||
def cmd = 'git show -s --format=%ct'
|
def cmd = 'git show -s --format=%ct'
|
||||||
|
|
|
@ -49,6 +49,7 @@ import im.vector.app.features.analytics.VectorAnalytics
|
||||||
import im.vector.app.features.analytics.errors.ErrorTracker
|
import im.vector.app.features.analytics.errors.ErrorTracker
|
||||||
import im.vector.app.features.analytics.impl.DefaultVectorAnalytics
|
import im.vector.app.features.analytics.impl.DefaultVectorAnalytics
|
||||||
import im.vector.app.features.analytics.metrics.VectorPlugins
|
import im.vector.app.features.analytics.metrics.VectorPlugins
|
||||||
|
import im.vector.app.features.configuration.VectorCustomEventTypesProvider
|
||||||
import im.vector.app.features.invite.AutoAcceptInvites
|
import im.vector.app.features.invite.AutoAcceptInvites
|
||||||
import im.vector.app.features.invite.CompileTimeAutoAcceptInvites
|
import im.vector.app.features.invite.CompileTimeAutoAcceptInvites
|
||||||
import im.vector.app.features.navigation.DefaultNavigator
|
import im.vector.app.features.navigation.DefaultNavigator
|
||||||
|
@ -145,6 +146,7 @@ import javax.inject.Singleton
|
||||||
vectorRoomDisplayNameFallbackProvider: VectorRoomDisplayNameFallbackProvider,
|
vectorRoomDisplayNameFallbackProvider: VectorRoomDisplayNameFallbackProvider,
|
||||||
flipperProxy: FlipperProxy,
|
flipperProxy: FlipperProxy,
|
||||||
vectorPlugins: VectorPlugins,
|
vectorPlugins: VectorPlugins,
|
||||||
|
vectorCustomEventTypesProvider: VectorCustomEventTypesProvider,
|
||||||
): MatrixConfiguration {
|
): MatrixConfiguration {
|
||||||
return MatrixConfiguration(
|
return MatrixConfiguration(
|
||||||
applicationFlavor = BuildConfig.FLAVOR_DESCRIPTION,
|
applicationFlavor = BuildConfig.FLAVOR_DESCRIPTION,
|
||||||
|
@ -156,6 +158,7 @@ import javax.inject.Singleton
|
||||||
),
|
),
|
||||||
metricPlugins = vectorPlugins.plugins(),
|
metricPlugins = vectorPlugins.plugins(),
|
||||||
cryptoAnalyticsPlugin = vectorPlugins.cryptoMetricPlugin,
|
cryptoAnalyticsPlugin = vectorPlugins.cryptoMetricPlugin,
|
||||||
|
customEventTypesProvider = vectorCustomEventTypesProvider,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@
|
||||||
<!-- Level 1: Labs -->
|
<!-- Level 1: Labs -->
|
||||||
<bool name="settings_labs_deferred_dm_visible">true</bool>
|
<bool name="settings_labs_deferred_dm_visible">true</bool>
|
||||||
<bool name="settings_labs_deferred_dm_default">true</bool>
|
<bool name="settings_labs_deferred_dm_default">true</bool>
|
||||||
<bool name="settings_labs_thread_messages_default">false</bool>
|
<bool name="settings_labs_thread_messages_default">true</bool>
|
||||||
<bool name="settings_labs_new_app_layout_default">true</bool>
|
<bool name="settings_labs_new_app_layout_default">true</bool>
|
||||||
<bool name="settings_labs_new_session_manager_default">false</bool>
|
<bool name="settings_labs_new_session_manager_default">false</bool>
|
||||||
<bool name="settings_labs_new_session_manager_visible">true</bool>
|
<bool name="settings_labs_new_session_manager_visible">true</bool>
|
||||||
|
|
|
@ -27,7 +27,7 @@ import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayer
|
||||||
import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayerImpl
|
import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayerImpl
|
||||||
import im.vector.app.features.voicebroadcast.recording.VoiceBroadcastRecorder
|
import im.vector.app.features.voicebroadcast.recording.VoiceBroadcastRecorder
|
||||||
import im.vector.app.features.voicebroadcast.recording.VoiceBroadcastRecorderQ
|
import im.vector.app.features.voicebroadcast.recording.VoiceBroadcastRecorderQ
|
||||||
import im.vector.app.features.voicebroadcast.usecase.GetMostRecentVoiceBroadcastStateEventUseCase
|
import im.vector.app.features.voicebroadcast.usecase.GetVoiceBroadcastStateEventLiveUseCase
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@InstallIn(SingletonComponent::class)
|
@InstallIn(SingletonComponent::class)
|
||||||
|
@ -40,13 +40,13 @@ abstract class VoiceModule {
|
||||||
fun providesVoiceBroadcastRecorder(
|
fun providesVoiceBroadcastRecorder(
|
||||||
context: Context,
|
context: Context,
|
||||||
sessionHolder: ActiveSessionHolder,
|
sessionHolder: ActiveSessionHolder,
|
||||||
getMostRecentVoiceBroadcastStateEventUseCase: GetMostRecentVoiceBroadcastStateEventUseCase,
|
getVoiceBroadcastStateEventLiveUseCase: GetVoiceBroadcastStateEventLiveUseCase,
|
||||||
): VoiceBroadcastRecorder? {
|
): VoiceBroadcastRecorder? {
|
||||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
VoiceBroadcastRecorderQ(
|
VoiceBroadcastRecorderQ(
|
||||||
context = context,
|
context = context,
|
||||||
sessionHolder = sessionHolder,
|
sessionHolder = sessionHolder,
|
||||||
getVoiceBroadcastEventUseCase = getMostRecentVoiceBroadcastStateEventUseCase
|
getVoiceBroadcastEventUseCase = getVoiceBroadcastStateEventLiveUseCase
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.configuration
|
||||||
|
|
||||||
|
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
|
||||||
|
import org.matrix.android.sdk.api.provider.CustomEventTypesProvider
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class VectorCustomEventTypesProvider @Inject constructor() : CustomEventTypesProvider {
|
||||||
|
|
||||||
|
override val customPreviewableEventTypes = listOf(
|
||||||
|
VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO
|
||||||
|
)
|
||||||
|
}
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
package im.vector.app.features.displayname
|
package im.vector.app.features.displayname
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.MatrixItemDisplayNameFallbackProvider
|
import org.matrix.android.sdk.api.provider.MatrixItemDisplayNameFallbackProvider
|
||||||
import org.matrix.android.sdk.api.util.MatrixItem
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
|
|
||||||
// Used to provide the fallback to the MatrixSDK, in the MatrixConfiguration
|
// Used to provide the fallback to the MatrixSDK, in the MatrixConfiguration
|
||||||
|
|
|
@ -251,6 +251,12 @@ class HomeActivityViewModel @AssistedInject constructor(
|
||||||
// }
|
// }
|
||||||
|
|
||||||
when {
|
when {
|
||||||
|
!vectorPreferences.areThreadMessagesEnabled() && !vectorPreferences.wasThreadFlagChangedManually() -> {
|
||||||
|
vectorPreferences.setThreadMessagesEnabled()
|
||||||
|
lightweightSettingsStorage.setThreadMessagesEnabled(vectorPreferences.areThreadMessagesEnabled())
|
||||||
|
// Clear Cache
|
||||||
|
_viewEvents.post(HomeActivityViewEvents.MigrateThreads(checkSession = false))
|
||||||
|
}
|
||||||
// Notify users
|
// Notify users
|
||||||
vectorPreferences.shouldNotifyUserAboutThreads() && vectorPreferences.areThreadMessagesEnabled() -> {
|
vectorPreferences.shouldNotifyUserAboutThreads() && vectorPreferences.areThreadMessagesEnabled() -> {
|
||||||
Timber.i("----> Notify users about threads")
|
Timber.i("----> Notify users about threads")
|
||||||
|
|
|
@ -20,9 +20,15 @@ import dagger.Lazy
|
||||||
import im.vector.app.EmojiSpanify
|
import im.vector.app.EmojiSpanify
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.extensions.getVectorLastMessageContent
|
import im.vector.app.core.extensions.getVectorLastMessageContent
|
||||||
|
import im.vector.app.core.extensions.orEmpty
|
||||||
import im.vector.app.core.resources.ColorProvider
|
import im.vector.app.core.resources.ColorProvider
|
||||||
|
import im.vector.app.core.resources.DrawableProvider
|
||||||
import im.vector.app.core.resources.StringProvider
|
import im.vector.app.core.resources.StringProvider
|
||||||
import im.vector.app.features.html.EventHtmlRenderer
|
import im.vector.app.features.html.EventHtmlRenderer
|
||||||
|
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
|
||||||
|
import im.vector.app.features.voicebroadcast.isLive
|
||||||
|
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
||||||
|
import me.gujun.android.span.image
|
||||||
import me.gujun.android.span.span
|
import me.gujun.android.span.span
|
||||||
import org.commonmark.node.Document
|
import org.commonmark.node.Document
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
@ -41,6 +47,7 @@ import javax.inject.Inject
|
||||||
class DisplayableEventFormatter @Inject constructor(
|
class DisplayableEventFormatter @Inject constructor(
|
||||||
private val stringProvider: StringProvider,
|
private val stringProvider: StringProvider,
|
||||||
private val colorProvider: ColorProvider,
|
private val colorProvider: ColorProvider,
|
||||||
|
private val drawableProvider: DrawableProvider,
|
||||||
private val emojiSpanify: EmojiSpanify,
|
private val emojiSpanify: EmojiSpanify,
|
||||||
private val noticeEventFormatter: NoticeEventFormatter,
|
private val noticeEventFormatter: NoticeEventFormatter,
|
||||||
private val htmlRenderer: Lazy<EventHtmlRenderer>
|
private val htmlRenderer: Lazy<EventHtmlRenderer>
|
||||||
|
@ -135,6 +142,9 @@ class DisplayableEventFormatter @Inject constructor(
|
||||||
in EventType.STATE_ROOM_BEACON_INFO.values -> {
|
in EventType.STATE_ROOM_BEACON_INFO.values -> {
|
||||||
simpleFormat(senderName, stringProvider.getString(R.string.sent_live_location), appendAuthor)
|
simpleFormat(senderName, stringProvider.getString(R.string.sent_live_location), appendAuthor)
|
||||||
}
|
}
|
||||||
|
VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO -> {
|
||||||
|
formatVoiceBroadcastEvent(timelineEvent.root, isDm, senderName)
|
||||||
|
}
|
||||||
else -> {
|
else -> {
|
||||||
span {
|
span {
|
||||||
text = noticeEventFormatter.format(timelineEvent, isDm) ?: ""
|
text = noticeEventFormatter.format(timelineEvent, isDm) ?: ""
|
||||||
|
@ -252,4 +262,20 @@ class DisplayableEventFormatter @Inject constructor(
|
||||||
body
|
body
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun formatVoiceBroadcastEvent(event: Event, isDm: Boolean, senderName: String): CharSequence {
|
||||||
|
return if (event.asVoiceBroadcastEvent()?.isLive == true) {
|
||||||
|
span {
|
||||||
|
drawableProvider.getDrawable(R.drawable.ic_voice_broadcast, colorProvider.getColor(R.color.palette_vermilion))?.let {
|
||||||
|
image(it)
|
||||||
|
+" "
|
||||||
|
}
|
||||||
|
span(stringProvider.getString(R.string.voice_broadcast_live_broadcast)) {
|
||||||
|
textColor = colorProvider.getColor(R.color.palette_vermilion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
noticeEventFormatter.format(event, senderName, isDm).orEmpty()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,8 @@ import im.vector.app.core.resources.StringProvider
|
||||||
import im.vector.app.features.roomprofile.permissions.RoleFormatter
|
import im.vector.app.features.roomprofile.permissions.RoleFormatter
|
||||||
import im.vector.app.features.settings.VectorPreferences
|
import im.vector.app.features.settings.VectorPreferences
|
||||||
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
|
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
|
||||||
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
||||||
|
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
||||||
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||||
import org.matrix.android.sdk.api.extensions.appendNl
|
import org.matrix.android.sdk.api.extensions.appendNl
|
||||||
import org.matrix.android.sdk.api.extensions.orFalse
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
|
@ -67,30 +69,32 @@ class NoticeEventFormatter @Inject constructor(
|
||||||
private fun Event.isSentByCurrentUser() = senderId != null && senderId == currentUserId
|
private fun Event.isSentByCurrentUser() = senderId != null && senderId == currentUserId
|
||||||
|
|
||||||
fun format(timelineEvent: TimelineEvent, isDm: Boolean): CharSequence? {
|
fun format(timelineEvent: TimelineEvent, isDm: Boolean): CharSequence? {
|
||||||
return when (val type = timelineEvent.root.getClearType()) {
|
val event = timelineEvent.root
|
||||||
EventType.STATE_ROOM_JOIN_RULES -> formatJoinRulesEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName, isDm)
|
val senderName = timelineEvent.senderInfo.disambiguatedDisplayName
|
||||||
EventType.STATE_ROOM_CREATE -> formatRoomCreateEvent(timelineEvent.root, isDm)
|
return when (val type = event.getClearType()) {
|
||||||
EventType.STATE_ROOM_NAME -> formatRoomNameEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
|
EventType.STATE_ROOM_JOIN_RULES -> formatJoinRulesEvent(event, senderName, isDm)
|
||||||
EventType.STATE_ROOM_TOPIC -> formatRoomTopicEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
|
EventType.STATE_ROOM_CREATE -> formatRoomCreateEvent(event, isDm)
|
||||||
EventType.STATE_ROOM_AVATAR -> formatRoomAvatarEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
|
EventType.STATE_ROOM_NAME -> formatRoomNameEvent(event, senderName)
|
||||||
EventType.STATE_ROOM_MEMBER -> formatRoomMemberEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName, isDm)
|
EventType.STATE_ROOM_TOPIC -> formatRoomTopicEvent(event, senderName)
|
||||||
EventType.STATE_ROOM_THIRD_PARTY_INVITE -> formatRoomThirdPartyInvite(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName, isDm)
|
EventType.STATE_ROOM_AVATAR -> formatRoomAvatarEvent(event, senderName)
|
||||||
EventType.STATE_ROOM_ALIASES -> formatRoomAliasesEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
|
EventType.STATE_ROOM_MEMBER -> formatRoomMemberEvent(event, senderName, isDm)
|
||||||
EventType.STATE_ROOM_CANONICAL_ALIAS -> formatRoomCanonicalAliasEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
|
EventType.STATE_ROOM_THIRD_PARTY_INVITE -> formatRoomThirdPartyInvite(event, senderName, isDm)
|
||||||
EventType.STATE_ROOM_HISTORY_VISIBILITY ->
|
EventType.STATE_ROOM_ALIASES -> formatRoomAliasesEvent(event, senderName)
|
||||||
formatRoomHistoryVisibilityEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName, isDm)
|
EventType.STATE_ROOM_CANONICAL_ALIAS -> formatRoomCanonicalAliasEvent(event, senderName)
|
||||||
EventType.STATE_ROOM_SERVER_ACL -> formatRoomServerAclEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
|
EventType.STATE_ROOM_HISTORY_VISIBILITY -> formatRoomHistoryVisibilityEvent(event, senderName, isDm)
|
||||||
EventType.STATE_ROOM_GUEST_ACCESS -> formatRoomGuestAccessEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName, isDm)
|
EventType.STATE_ROOM_SERVER_ACL -> formatRoomServerAclEvent(event, senderName)
|
||||||
EventType.STATE_ROOM_ENCRYPTION -> formatRoomEncryptionEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
|
EventType.STATE_ROOM_GUEST_ACCESS -> formatRoomGuestAccessEvent(event, senderName, isDm)
|
||||||
|
EventType.STATE_ROOM_ENCRYPTION -> formatRoomEncryptionEvent(event, senderName)
|
||||||
EventType.STATE_ROOM_WIDGET,
|
EventType.STATE_ROOM_WIDGET,
|
||||||
EventType.STATE_ROOM_WIDGET_LEGACY -> formatWidgetEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
|
EventType.STATE_ROOM_WIDGET_LEGACY -> formatWidgetEvent(event, senderName)
|
||||||
EventType.STATE_ROOM_TOMBSTONE -> formatRoomTombstoneEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName, isDm)
|
EventType.STATE_ROOM_TOMBSTONE -> formatRoomTombstoneEvent(event, senderName, isDm)
|
||||||
EventType.STATE_ROOM_POWER_LEVELS -> formatRoomPowerLevels(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
|
EventType.STATE_ROOM_POWER_LEVELS -> formatRoomPowerLevels(event, senderName)
|
||||||
EventType.CALL_INVITE,
|
EventType.CALL_INVITE,
|
||||||
EventType.CALL_CANDIDATES,
|
EventType.CALL_CANDIDATES,
|
||||||
EventType.CALL_HANGUP,
|
EventType.CALL_HANGUP,
|
||||||
EventType.CALL_REJECT,
|
EventType.CALL_REJECT,
|
||||||
EventType.CALL_ANSWER -> formatCallEvent(type, timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
|
EventType.CALL_ANSWER -> formatCallEvent(type, event, senderName)
|
||||||
|
VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO -> formatVoiceBroadcastEvent(event, senderName)
|
||||||
EventType.CALL_NEGOTIATE,
|
EventType.CALL_NEGOTIATE,
|
||||||
EventType.CALL_SELECT_ANSWER,
|
EventType.CALL_SELECT_ANSWER,
|
||||||
EventType.CALL_REPLACES,
|
EventType.CALL_REPLACES,
|
||||||
|
@ -109,8 +113,7 @@ class NoticeEventFormatter @Inject constructor(
|
||||||
EventType.STICKER,
|
EventType.STICKER,
|
||||||
in EventType.POLL_RESPONSE.values,
|
in EventType.POLL_RESPONSE.values,
|
||||||
in EventType.POLL_END.values,
|
in EventType.POLL_END.values,
|
||||||
in EventType.BEACON_LOCATION_DATA.values,
|
in EventType.BEACON_LOCATION_DATA.values -> formatDebug(event)
|
||||||
VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO -> formatDebug(timelineEvent.root)
|
|
||||||
else -> {
|
else -> {
|
||||||
Timber.v("Type $type not handled by this formatter")
|
Timber.v("Type $type not handled by this formatter")
|
||||||
null
|
null
|
||||||
|
@ -191,6 +194,7 @@ class NoticeEventFormatter @Inject constructor(
|
||||||
EventType.CALL_REJECT,
|
EventType.CALL_REJECT,
|
||||||
EventType.CALL_ANSWER -> formatCallEvent(type, event, senderName)
|
EventType.CALL_ANSWER -> formatCallEvent(type, event, senderName)
|
||||||
EventType.STATE_ROOM_TOMBSTONE -> formatRoomTombstoneEvent(event, senderName, isDm)
|
EventType.STATE_ROOM_TOMBSTONE -> formatRoomTombstoneEvent(event, senderName, isDm)
|
||||||
|
VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO -> formatVoiceBroadcastEvent(event, senderName)
|
||||||
else -> {
|
else -> {
|
||||||
Timber.v("Type $type not handled by this formatter")
|
Timber.v("Type $type not handled by this formatter")
|
||||||
null
|
null
|
||||||
|
@ -894,4 +898,16 @@ class NoticeEventFormatter @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun formatVoiceBroadcastEvent(event: Event, senderName: String?): CharSequence {
|
||||||
|
return if (event.asVoiceBroadcastEvent()?.content?.voiceBroadcastState == VoiceBroadcastState.STOPPED) {
|
||||||
|
if (event.isSentByCurrentUser()) {
|
||||||
|
sp.getString(R.string.notice_voice_broadcast_ended_by_you)
|
||||||
|
} else {
|
||||||
|
sp.getString(R.string.notice_voice_broadcast_ended, senderName)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
formatDebug(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -252,7 +252,7 @@ class TimelineEventVisibilityHelper @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root.getClearType() == VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO &&
|
if (root.getClearType() == VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO &&
|
||||||
root.asVoiceBroadcastEvent()?.content?.voiceBroadcastState != VoiceBroadcastState.STARTED) {
|
root.asVoiceBroadcastEvent()?.content?.voiceBroadcastState !in arrayOf(VoiceBroadcastState.STARTED, VoiceBroadcastState.STOPPED)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ import com.airbnb.mvrx.Loading
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.date.DateFormatKind
|
import im.vector.app.core.date.DateFormatKind
|
||||||
import im.vector.app.core.date.VectorDateFormatter
|
import im.vector.app.core.date.VectorDateFormatter
|
||||||
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
import im.vector.app.core.epoxy.VectorEpoxyModel
|
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||||
import im.vector.app.core.error.ErrorFormatter
|
import im.vector.app.core.error.ErrorFormatter
|
||||||
import im.vector.app.core.resources.StringProvider
|
import im.vector.app.core.resources.StringProvider
|
||||||
|
@ -29,21 +30,33 @@ import im.vector.app.features.home.AvatarRenderer
|
||||||
import im.vector.app.features.home.RoomListDisplayMode
|
import im.vector.app.features.home.RoomListDisplayMode
|
||||||
import im.vector.app.features.home.room.detail.timeline.format.DisplayableEventFormatter
|
import im.vector.app.features.home.room.detail.timeline.format.DisplayableEventFormatter
|
||||||
import im.vector.app.features.home.room.typing.TypingHelper
|
import im.vector.app.features.home.room.typing.TypingHelper
|
||||||
|
import im.vector.app.features.voicebroadcast.isLive
|
||||||
|
import im.vector.app.features.voicebroadcast.isVoiceBroadcast
|
||||||
|
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
||||||
|
import im.vector.app.features.voicebroadcast.usecase.GetRoomLiveVoiceBroadcastsUseCase
|
||||||
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
|
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
|
||||||
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
|
import org.matrix.android.sdk.api.session.getRoom
|
||||||
|
import org.matrix.android.sdk.api.session.room.getTimelineEvent
|
||||||
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
||||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
|
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.asMessageAudioEvent
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class RoomSummaryItemFactory @Inject constructor(
|
class RoomSummaryItemFactory @Inject constructor(
|
||||||
|
private val sessionHolder: ActiveSessionHolder,
|
||||||
private val displayableEventFormatter: DisplayableEventFormatter,
|
private val displayableEventFormatter: DisplayableEventFormatter,
|
||||||
private val dateFormatter: VectorDateFormatter,
|
private val dateFormatter: VectorDateFormatter,
|
||||||
private val stringProvider: StringProvider,
|
private val stringProvider: StringProvider,
|
||||||
private val typingHelper: TypingHelper,
|
private val typingHelper: TypingHelper,
|
||||||
private val avatarRenderer: AvatarRenderer,
|
private val avatarRenderer: AvatarRenderer,
|
||||||
private val errorFormatter: ErrorFormatter
|
private val errorFormatter: ErrorFormatter,
|
||||||
|
private val getRoomLiveVoiceBroadcastsUseCase: GetRoomLiveVoiceBroadcastsUseCase,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun create(
|
fun create(
|
||||||
|
@ -129,13 +142,15 @@ class RoomSummaryItemFactory @Inject constructor(
|
||||||
val showSelected = selectedRoomIds.contains(roomSummary.roomId)
|
val showSelected = selectedRoomIds.contains(roomSummary.roomId)
|
||||||
var latestFormattedEvent: CharSequence = ""
|
var latestFormattedEvent: CharSequence = ""
|
||||||
var latestEventTime = ""
|
var latestEventTime = ""
|
||||||
val latestEvent = roomSummary.latestPreviewableEvent
|
val latestEvent = roomSummary.getVectorLatestPreviewableEvent()
|
||||||
if (latestEvent != null) {
|
if (latestEvent != null) {
|
||||||
latestFormattedEvent = displayableEventFormatter.format(latestEvent, roomSummary.isDirect, roomSummary.isDirect.not())
|
latestFormattedEvent = displayableEventFormatter.format(latestEvent, roomSummary.isDirect, roomSummary.isDirect.not())
|
||||||
latestEventTime = dateFormatter.format(latestEvent.root.originServerTs, DateFormatKind.ROOM_LIST)
|
latestEventTime = dateFormatter.format(latestEvent.root.originServerTs, DateFormatKind.ROOM_LIST)
|
||||||
}
|
}
|
||||||
|
|
||||||
val typingMessage = typingHelper.getTypingMessage(roomSummary.typingUsers)
|
val typingMessage = typingHelper.getTypingMessage(roomSummary.typingUsers)
|
||||||
|
// Skip typing while there is a live voice broadcast
|
||||||
|
.takeUnless { latestEvent?.root?.asVoiceBroadcastEvent()?.isLive.orFalse() }.orEmpty()
|
||||||
|
|
||||||
return if (subtitle.isBlank() && displayMode == RoomListDisplayMode.FILTERED) {
|
return if (subtitle.isBlank() && displayMode == RoomListDisplayMode.FILTERED) {
|
||||||
createCenteredRoomSummaryItem(roomSummary, displayMode, showSelected, unreadCount, onClick, onLongClick)
|
createCenteredRoomSummaryItem(roomSummary, displayMode, showSelected, unreadCount, onClick, onLongClick)
|
||||||
|
@ -225,4 +240,14 @@ class RoomSummaryItemFactory @Inject constructor(
|
||||||
else -> stringProvider.getQuantityString(R.plurals.search_space_multiple_parents, size - 1, directParentNames[0], size - 1)
|
else -> stringProvider.getQuantityString(R.plurals.search_space_multiple_parents, size - 1, directParentNames[0], size - 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun RoomSummary.getVectorLatestPreviewableEvent(): TimelineEvent? {
|
||||||
|
val room = sessionHolder.getSafeActiveSession()?.getRoom(roomId) ?: return latestPreviewableEvent
|
||||||
|
val liveVoiceBroadcastTimelineEvent = getRoomLiveVoiceBroadcastsUseCase.execute(roomId).lastOrNull()
|
||||||
|
?.root?.eventId?.let { room.getTimelineEvent(it) }
|
||||||
|
return latestPreviewableEvent?.takeIf { it.root.getClearType() == EventType.CALL_INVITE }
|
||||||
|
?: liveVoiceBroadcastTimelineEvent
|
||||||
|
?: latestPreviewableEvent
|
||||||
|
?.takeUnless { it.root.asMessageAudioEvent()?.isVoiceBroadcast().orFalse() } // Skip voice messages related to voice broadcast
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,24 +19,18 @@ package im.vector.app.features.home.room.threads.list.viewmodel
|
||||||
import com.airbnb.epoxy.EpoxyController
|
import com.airbnb.epoxy.EpoxyController
|
||||||
import im.vector.app.core.date.DateFormatKind
|
import im.vector.app.core.date.DateFormatKind
|
||||||
import im.vector.app.core.date.VectorDateFormatter
|
import im.vector.app.core.date.VectorDateFormatter
|
||||||
import im.vector.app.core.resources.StringProvider
|
|
||||||
import im.vector.app.features.home.AvatarRenderer
|
import im.vector.app.features.home.AvatarRenderer
|
||||||
import im.vector.app.features.home.room.detail.timeline.format.DisplayableEventFormatter
|
import im.vector.app.features.home.room.detail.timeline.format.DisplayableEventFormatter
|
||||||
import im.vector.app.features.home.room.threads.list.model.threadListItem
|
import im.vector.app.features.home.room.threads.list.model.threadListItem
|
||||||
import org.matrix.android.sdk.api.session.Session
|
|
||||||
import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary
|
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
import org.matrix.android.sdk.api.session.threads.ThreadNotificationState
|
import org.matrix.android.sdk.api.session.threads.ThreadNotificationState
|
||||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||||
import org.matrix.android.sdk.api.util.toMatrixItemOrNull
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class ThreadListController @Inject constructor(
|
class ThreadListController @Inject constructor(
|
||||||
private val avatarRenderer: AvatarRenderer,
|
private val avatarRenderer: AvatarRenderer,
|
||||||
private val stringProvider: StringProvider,
|
|
||||||
private val dateFormatter: VectorDateFormatter,
|
private val dateFormatter: VectorDateFormatter,
|
||||||
private val displayableEventFormatter: DisplayableEventFormatter,
|
private val displayableEventFormatter: DisplayableEventFormatter,
|
||||||
private val session: Session
|
|
||||||
) : EpoxyController() {
|
) : EpoxyController() {
|
||||||
|
|
||||||
var listener: Listener? = null
|
var listener: Listener? = null
|
||||||
|
@ -48,64 +42,7 @@ class ThreadListController @Inject constructor(
|
||||||
requestModelBuild()
|
requestModelBuild()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun buildModels() =
|
override fun buildModels() {
|
||||||
when (session.homeServerCapabilitiesService().getHomeServerCapabilities().canUseThreading) {
|
|
||||||
true -> buildThreadSummaries()
|
|
||||||
false -> buildThreadList()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Building thread summaries when homeserver supports threading.
|
|
||||||
*/
|
|
||||||
private fun buildThreadSummaries() {
|
|
||||||
val safeViewState = viewState ?: return
|
|
||||||
val host = this
|
|
||||||
safeViewState.threadSummaryList.invoke()
|
|
||||||
?.filter {
|
|
||||||
if (safeViewState.shouldFilterThreads) {
|
|
||||||
it.isUserParticipating
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
?.forEach { threadSummary ->
|
|
||||||
val date = dateFormatter.format(threadSummary.latestEvent?.originServerTs, DateFormatKind.ROOM_LIST)
|
|
||||||
val lastMessageFormatted = threadSummary.let {
|
|
||||||
displayableEventFormatter.formatThreadSummary(
|
|
||||||
event = it.latestEvent,
|
|
||||||
latestEdition = it.threadEditions.latestThreadEdition
|
|
||||||
).toString()
|
|
||||||
}
|
|
||||||
val rootMessageFormatted = threadSummary.let {
|
|
||||||
displayableEventFormatter.formatThreadSummary(
|
|
||||||
event = it.rootEvent,
|
|
||||||
latestEdition = it.threadEditions.rootThreadEdition
|
|
||||||
).toString()
|
|
||||||
}
|
|
||||||
threadListItem {
|
|
||||||
id(threadSummary.rootEvent?.eventId)
|
|
||||||
avatarRenderer(host.avatarRenderer)
|
|
||||||
matrixItem(threadSummary.rootThreadSenderInfo.toMatrixItem())
|
|
||||||
title(threadSummary.rootThreadSenderInfo.displayName.orEmpty())
|
|
||||||
date(date)
|
|
||||||
rootMessageDeleted(threadSummary.rootEvent?.isRedacted() ?: false)
|
|
||||||
// TODO refactor notifications that with the new thread summary
|
|
||||||
threadNotificationState(threadSummary.rootEvent?.threadDetails?.threadNotificationState ?: ThreadNotificationState.NO_NEW_MESSAGE)
|
|
||||||
rootMessage(rootMessageFormatted)
|
|
||||||
lastMessage(lastMessageFormatted)
|
|
||||||
lastMessageCounter(threadSummary.numberOfThreads.toString())
|
|
||||||
lastMessageMatrixItem(threadSummary.latestThreadSenderInfo.toMatrixItemOrNull())
|
|
||||||
itemClickListener {
|
|
||||||
host.listener?.onThreadSummaryClicked(threadSummary)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Building local thread list when homeserver do not support threading.
|
|
||||||
*/
|
|
||||||
private fun buildThreadList() {
|
|
||||||
val safeViewState = viewState ?: return
|
val safeViewState = viewState ?: return
|
||||||
val host = this
|
val host = this
|
||||||
safeViewState.rootThreadEventList.invoke()
|
safeViewState.rootThreadEventList.invoke()
|
||||||
|
@ -152,7 +89,6 @@ class ThreadListController @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Listener {
|
interface Listener {
|
||||||
fun onThreadSummaryClicked(threadSummary: ThreadSummary)
|
|
||||||
fun onThreadListClicked(timelineEvent: TimelineEvent)
|
fun onThreadListClicked(timelineEvent: TimelineEvent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.app.features.home.room.threads.list.viewmodel
|
||||||
|
|
||||||
|
import com.airbnb.epoxy.EpoxyModel
|
||||||
|
import com.airbnb.epoxy.paging.PagedListEpoxyController
|
||||||
|
import im.vector.app.core.date.DateFormatKind
|
||||||
|
import im.vector.app.core.date.VectorDateFormatter
|
||||||
|
import im.vector.app.core.utils.createUIHandler
|
||||||
|
import im.vector.app.features.home.AvatarRenderer
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.format.DisplayableEventFormatter
|
||||||
|
import im.vector.app.features.home.room.threads.list.model.ThreadListItem_
|
||||||
|
import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary
|
||||||
|
import org.matrix.android.sdk.api.session.threads.ThreadNotificationState
|
||||||
|
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||||
|
import org.matrix.android.sdk.api.util.toMatrixItemOrNull
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class ThreadListPagedController @Inject constructor(
|
||||||
|
private val avatarRenderer: AvatarRenderer,
|
||||||
|
private val dateFormatter: VectorDateFormatter,
|
||||||
|
private val displayableEventFormatter: DisplayableEventFormatter,
|
||||||
|
) : PagedListEpoxyController<ThreadSummary>(
|
||||||
|
// Important it must match the PageList builder notify Looper
|
||||||
|
modelBuildingHandler = createUIHandler()
|
||||||
|
) {
|
||||||
|
|
||||||
|
var listener: Listener? = null
|
||||||
|
|
||||||
|
override fun buildItemModel(currentPosition: Int, item: ThreadSummary?): EpoxyModel<*> {
|
||||||
|
if (item == null) {
|
||||||
|
throw java.lang.NullPointerException()
|
||||||
|
}
|
||||||
|
val host = this
|
||||||
|
val date = dateFormatter.format(item.latestEvent?.originServerTs, DateFormatKind.ROOM_LIST)
|
||||||
|
val lastMessageFormatted = item.let {
|
||||||
|
displayableEventFormatter.formatThreadSummary(
|
||||||
|
event = it.latestEvent,
|
||||||
|
latestEdition = it.threadEditions.latestThreadEdition
|
||||||
|
).toString()
|
||||||
|
}
|
||||||
|
val rootMessageFormatted = item.let {
|
||||||
|
displayableEventFormatter.formatThreadSummary(
|
||||||
|
event = it.rootEvent,
|
||||||
|
latestEdition = it.threadEditions.rootThreadEdition
|
||||||
|
).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
return ThreadListItem_()
|
||||||
|
.id(item.rootEvent?.eventId)
|
||||||
|
.avatarRenderer(host.avatarRenderer)
|
||||||
|
.matrixItem(item.rootThreadSenderInfo.toMatrixItem())
|
||||||
|
.title(item.rootThreadSenderInfo.displayName.orEmpty())
|
||||||
|
.date(date)
|
||||||
|
.rootMessageDeleted(item.rootEvent?.isRedacted() ?: false)
|
||||||
|
// TODO refactor notifications that with the new thread summary
|
||||||
|
.threadNotificationState(item.rootEvent?.threadDetails?.threadNotificationState ?: ThreadNotificationState.NO_NEW_MESSAGE)
|
||||||
|
.rootMessage(rootMessageFormatted)
|
||||||
|
.lastMessage(lastMessageFormatted)
|
||||||
|
.lastMessageCounter(item.numberOfThreads.toString())
|
||||||
|
.lastMessageMatrixItem(item.latestThreadSenderInfo.toMatrixItemOrNull())
|
||||||
|
.itemClickListener {
|
||||||
|
host.listener?.onThreadSummaryClicked(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Listener {
|
||||||
|
fun onThreadSummaryClicked(threadSummary: ThreadSummary)
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,6 +16,11 @@
|
||||||
|
|
||||||
package im.vector.app.features.home.room.threads.list.viewmodel
|
package im.vector.app.features.home.room.threads.list.viewmodel
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.Observer
|
||||||
|
import androidx.lifecycle.asFlow
|
||||||
|
import androidx.paging.PagedList
|
||||||
import com.airbnb.mvrx.FragmentViewModelContext
|
import com.airbnb.mvrx.FragmentViewModelContext
|
||||||
import com.airbnb.mvrx.MavericksViewModelFactory
|
import com.airbnb.mvrx.MavericksViewModelFactory
|
||||||
import com.airbnb.mvrx.ViewModelContext
|
import com.airbnb.mvrx.ViewModelContext
|
||||||
|
@ -29,23 +34,47 @@ import im.vector.app.features.analytics.AnalyticsTracker
|
||||||
import im.vector.app.features.analytics.extensions.toAnalyticsInteraction
|
import im.vector.app.features.analytics.extensions.toAnalyticsInteraction
|
||||||
import im.vector.app.features.analytics.plan.Interaction
|
import im.vector.app.features.analytics.plan.Interaction
|
||||||
import im.vector.app.features.home.room.threads.list.views.ThreadListFragment
|
import im.vector.app.features.home.room.threads.list.views.ThreadListFragment
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.getRoom
|
import org.matrix.android.sdk.api.session.getRoom
|
||||||
|
import org.matrix.android.sdk.api.session.room.threads.FetchThreadsResult
|
||||||
|
import org.matrix.android.sdk.api.session.room.threads.ThreadFilter
|
||||||
|
import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary
|
||||||
import org.matrix.android.sdk.api.session.threads.ThreadTimelineEvent
|
import org.matrix.android.sdk.api.session.threads.ThreadTimelineEvent
|
||||||
import org.matrix.android.sdk.flow.flow
|
import org.matrix.android.sdk.flow.flow
|
||||||
|
|
||||||
class ThreadListViewModel @AssistedInject constructor(
|
class ThreadListViewModel @AssistedInject constructor(
|
||||||
@Assisted val initialState: ThreadListViewState,
|
@Assisted val initialState: ThreadListViewState,
|
||||||
private val analyticsTracker: AnalyticsTracker,
|
private val analyticsTracker: AnalyticsTracker,
|
||||||
private val session: Session
|
private val session: Session,
|
||||||
) :
|
) : VectorViewModel<ThreadListViewState, EmptyAction, EmptyViewEvents>(initialState) {
|
||||||
VectorViewModel<ThreadListViewState, EmptyAction, EmptyViewEvents>(initialState) {
|
|
||||||
|
|
||||||
private val room = session.getRoom(initialState.roomId)
|
private val room = session.getRoom(initialState.roomId)
|
||||||
|
|
||||||
|
private val defaultPagedListConfig = PagedList.Config.Builder()
|
||||||
|
.setPageSize(20)
|
||||||
|
.setInitialLoadSizeHint(40)
|
||||||
|
.setEnablePlaceholders(false)
|
||||||
|
.setPrefetchDistance(10)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
private var nextBatchId: String? = null
|
||||||
|
private var hasReachedEnd: Boolean = false
|
||||||
|
private var boundariesJob: Job? = null
|
||||||
|
|
||||||
|
private var livePagedList: LiveData<PagedList<ThreadSummary>>? = null
|
||||||
|
private val _threadsLivePagedList = MutableLiveData<PagedList<ThreadSummary>>()
|
||||||
|
val threadsLivePagedList: LiveData<PagedList<ThreadSummary>> = _threadsLivePagedList
|
||||||
|
private val internalPagedListObserver = Observer<PagedList<ThreadSummary>> {
|
||||||
|
_threadsLivePagedList.postValue(it)
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
interface Factory {
|
interface Factory {
|
||||||
fun create(initialState: ThreadListViewState): ThreadListViewModel
|
fun create(initialState: ThreadListViewState): ThreadListViewModel
|
||||||
|
@ -54,7 +83,7 @@ class ThreadListViewModel @AssistedInject constructor(
|
||||||
companion object : MavericksViewModelFactory<ThreadListViewModel, ThreadListViewState> {
|
companion object : MavericksViewModelFactory<ThreadListViewModel, ThreadListViewState> {
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
override fun create(viewModelContext: ViewModelContext, state: ThreadListViewState): ThreadListViewModel? {
|
override fun create(viewModelContext: ViewModelContext, state: ThreadListViewState): ThreadListViewModel {
|
||||||
val fragment: ThreadListFragment = (viewModelContext as FragmentViewModelContext).fragment()
|
val fragment: ThreadListFragment = (viewModelContext as FragmentViewModelContext).fragment()
|
||||||
return fragment.threadListViewModelFactory.create(state)
|
return fragment.threadListViewModelFactory.create(state)
|
||||||
}
|
}
|
||||||
|
@ -72,7 +101,7 @@ class ThreadListViewModel @AssistedInject constructor(
|
||||||
private fun fetchAndObserveThreads() {
|
private fun fetchAndObserveThreads() {
|
||||||
when (session.homeServerCapabilitiesService().getHomeServerCapabilities().canUseThreading) {
|
when (session.homeServerCapabilitiesService().getHomeServerCapabilities().canUseThreading) {
|
||||||
true -> {
|
true -> {
|
||||||
fetchThreadList()
|
setLoading(true)
|
||||||
observeThreadSummaries()
|
observeThreadSummaries()
|
||||||
}
|
}
|
||||||
false -> observeThreadsList()
|
false -> observeThreadsList()
|
||||||
|
@ -82,13 +111,32 @@ class ThreadListViewModel @AssistedInject constructor(
|
||||||
/**
|
/**
|
||||||
* Observing thread summaries when homeserver support threading.
|
* Observing thread summaries when homeserver support threading.
|
||||||
*/
|
*/
|
||||||
private fun observeThreadSummaries() {
|
private fun observeThreadSummaries() = withState { state ->
|
||||||
room?.flow()
|
viewModelScope.launch {
|
||||||
?.liveThreadSummaries()
|
nextBatchId = null
|
||||||
?.map { room.threadsService().enhanceThreadWithEditions(it) }
|
hasReachedEnd = false
|
||||||
?.flowOn(room.coroutineDispatchers.io)
|
|
||||||
?.execute { asyncThreads ->
|
livePagedList?.removeObserver(internalPagedListObserver)
|
||||||
copy(threadSummaryList = asyncThreads)
|
|
||||||
|
room?.threadsService()
|
||||||
|
?.getPagedThreadsList(state.shouldFilterThreads, defaultPagedListConfig)?.let { result ->
|
||||||
|
livePagedList = result.livePagedList
|
||||||
|
|
||||||
|
livePagedList?.observeForever(internalPagedListObserver)
|
||||||
|
|
||||||
|
boundariesJob = result.liveBoundaries.asFlow()
|
||||||
|
.onEach {
|
||||||
|
if (it.endLoaded) {
|
||||||
|
if (!hasReachedEnd) {
|
||||||
|
fetchNextPage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.launchIn(viewModelScope)
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true)
|
||||||
|
fetchNextPage()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,14 +159,6 @@ class ThreadListViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fetchThreadList() {
|
|
||||||
viewModelScope.launch {
|
|
||||||
setLoading(true)
|
|
||||||
room?.threadsService()?.fetchThreadSummaries()
|
|
||||||
setLoading(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setLoading(isLoading: Boolean) {
|
private fun setLoading(isLoading: Boolean) {
|
||||||
setState {
|
setState {
|
||||||
copy(isLoading = isLoading)
|
copy(isLoading = isLoading)
|
||||||
|
@ -132,5 +172,30 @@ class ThreadListViewModel @AssistedInject constructor(
|
||||||
setState {
|
setState {
|
||||||
copy(shouldFilterThreads = shouldFilterThreads)
|
copy(shouldFilterThreads = shouldFilterThreads)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fetchAndObserveThreads()
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun fetchNextPage() {
|
||||||
|
val filter = when (awaitState().shouldFilterThreads) {
|
||||||
|
true -> ThreadFilter.PARTICIPATED
|
||||||
|
false -> ThreadFilter.ALL
|
||||||
|
}
|
||||||
|
room?.threadsService()?.fetchThreadList(
|
||||||
|
nextBatchId = nextBatchId,
|
||||||
|
limit = defaultPagedListConfig.pageSize,
|
||||||
|
filter = filter,
|
||||||
|
).let { result ->
|
||||||
|
when (result) {
|
||||||
|
is FetchThreadsResult.ReachedEnd -> {
|
||||||
|
hasReachedEnd = true
|
||||||
|
}
|
||||||
|
is FetchThreadsResult.ShouldFetchMore -> {
|
||||||
|
nextBatchId = result.nextBatch
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,11 +20,9 @@ import com.airbnb.mvrx.Async
|
||||||
import com.airbnb.mvrx.MavericksState
|
import com.airbnb.mvrx.MavericksState
|
||||||
import com.airbnb.mvrx.Uninitialized
|
import com.airbnb.mvrx.Uninitialized
|
||||||
import im.vector.app.features.home.room.threads.arguments.ThreadListArgs
|
import im.vector.app.features.home.room.threads.arguments.ThreadListArgs
|
||||||
import org.matrix.android.sdk.api.session.room.threads.model.ThreadSummary
|
|
||||||
import org.matrix.android.sdk.api.session.threads.ThreadTimelineEvent
|
import org.matrix.android.sdk.api.session.threads.ThreadTimelineEvent
|
||||||
|
|
||||||
data class ThreadListViewState(
|
data class ThreadListViewState(
|
||||||
val threadSummaryList: Async<List<ThreadSummary>> = Uninitialized,
|
|
||||||
val rootThreadEventList: Async<List<ThreadTimelineEvent>> = Uninitialized,
|
val rootThreadEventList: Async<List<ThreadTimelineEvent>> = Uninitialized,
|
||||||
val shouldFilterThreads: Boolean = false,
|
val shouldFilterThreads: Boolean = false,
|
||||||
val isLoading: Boolean = false,
|
val isLoading: Boolean = false,
|
||||||
|
|
|
@ -40,6 +40,7 @@ import im.vector.app.features.home.room.threads.ThreadsActivity
|
||||||
import im.vector.app.features.home.room.threads.arguments.ThreadListArgs
|
import im.vector.app.features.home.room.threads.arguments.ThreadListArgs
|
||||||
import im.vector.app.features.home.room.threads.arguments.ThreadTimelineArgs
|
import im.vector.app.features.home.room.threads.arguments.ThreadTimelineArgs
|
||||||
import im.vector.app.features.home.room.threads.list.viewmodel.ThreadListController
|
import im.vector.app.features.home.room.threads.list.viewmodel.ThreadListController
|
||||||
|
import im.vector.app.features.home.room.threads.list.viewmodel.ThreadListPagedController
|
||||||
import im.vector.app.features.home.room.threads.list.viewmodel.ThreadListViewModel
|
import im.vector.app.features.home.room.threads.list.viewmodel.ThreadListViewModel
|
||||||
import im.vector.app.features.home.room.threads.list.viewmodel.ThreadListViewState
|
import im.vector.app.features.home.room.threads.list.viewmodel.ThreadListViewState
|
||||||
import im.vector.app.features.rageshake.BugReporter
|
import im.vector.app.features.rageshake.BugReporter
|
||||||
|
@ -52,12 +53,14 @@ import javax.inject.Inject
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class ThreadListFragment :
|
class ThreadListFragment :
|
||||||
VectorBaseFragment<FragmentThreadListBinding>(),
|
VectorBaseFragment<FragmentThreadListBinding>(),
|
||||||
|
ThreadListPagedController.Listener,
|
||||||
ThreadListController.Listener,
|
ThreadListController.Listener,
|
||||||
VectorMenuProvider {
|
VectorMenuProvider {
|
||||||
|
|
||||||
@Inject lateinit var avatarRenderer: AvatarRenderer
|
@Inject lateinit var avatarRenderer: AvatarRenderer
|
||||||
@Inject lateinit var bugReporter: BugReporter
|
@Inject lateinit var bugReporter: BugReporter
|
||||||
@Inject lateinit var threadListController: ThreadListController
|
@Inject lateinit var threadListController: ThreadListPagedController
|
||||||
|
@Inject lateinit var legacyThreadListController: ThreadListController
|
||||||
@Inject lateinit var threadListViewModelFactory: ThreadListViewModel.Factory
|
@Inject lateinit var threadListViewModelFactory: ThreadListViewModel.Factory
|
||||||
|
|
||||||
private val threadListViewModel: ThreadListViewModel by fragmentViewModel()
|
private val threadListViewModel: ThreadListViewModel by fragmentViewModel()
|
||||||
|
@ -100,7 +103,7 @@ class ThreadListFragment :
|
||||||
val filterBadge = filterIcon.findViewById<View>(R.id.threadListFilterBadge)
|
val filterBadge = filterIcon.findViewById<View>(R.id.threadListFilterBadge)
|
||||||
filterBadge.isVisible = state.shouldFilterThreads
|
filterBadge.isVisible = state.shouldFilterThreads
|
||||||
when (threadListViewModel.canHomeserverUseThreading()) {
|
when (threadListViewModel.canHomeserverUseThreading()) {
|
||||||
true -> menu.findItem(R.id.menu_thread_list_filter).isVisible = !state.threadSummaryList.invoke().isNullOrEmpty()
|
true -> menu.findItem(R.id.menu_thread_list_filter).isVisible = true
|
||||||
false -> menu.findItem(R.id.menu_thread_list_filter).isVisible = !state.rootThreadEventList.invoke().isNullOrEmpty()
|
false -> menu.findItem(R.id.menu_thread_list_filter).isVisible = !state.rootThreadEventList.invoke().isNullOrEmpty()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -111,8 +114,18 @@ class ThreadListFragment :
|
||||||
initToolbar()
|
initToolbar()
|
||||||
initTextConstants()
|
initTextConstants()
|
||||||
initBetaFeedback()
|
initBetaFeedback()
|
||||||
|
|
||||||
|
if (threadListViewModel.canHomeserverUseThreading()) {
|
||||||
views.threadListRecyclerView.configureWith(threadListController, TimelineItemAnimator(), hasFixedSize = false)
|
views.threadListRecyclerView.configureWith(threadListController, TimelineItemAnimator(), hasFixedSize = false)
|
||||||
threadListController.listener = this
|
threadListController.listener = this
|
||||||
|
|
||||||
|
threadListViewModel.threadsLivePagedList.observe(viewLifecycleOwner) { threadsList ->
|
||||||
|
threadListController.submitList(threadsList)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
views.threadListRecyclerView.configureWith(legacyThreadListController, TimelineItemAnimator(), hasFixedSize = false)
|
||||||
|
legacyThreadListController.listener = this
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
|
@ -144,7 +157,9 @@ class ThreadListFragment :
|
||||||
override fun invalidate() = withState(threadListViewModel) { state ->
|
override fun invalidate() = withState(threadListViewModel) { state ->
|
||||||
invalidateOptionsMenu()
|
invalidateOptionsMenu()
|
||||||
renderEmptyStateIfNeeded(state)
|
renderEmptyStateIfNeeded(state)
|
||||||
threadListController.update(state)
|
if (!threadListViewModel.canHomeserverUseThreading()) {
|
||||||
|
legacyThreadListController.update(state)
|
||||||
|
}
|
||||||
renderLoaderIfNeeded(state)
|
renderLoaderIfNeeded(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,7 +200,7 @@ class ThreadListFragment :
|
||||||
|
|
||||||
private fun renderEmptyStateIfNeeded(state: ThreadListViewState) {
|
private fun renderEmptyStateIfNeeded(state: ThreadListViewState) {
|
||||||
when (threadListViewModel.canHomeserverUseThreading()) {
|
when (threadListViewModel.canHomeserverUseThreading()) {
|
||||||
true -> views.threadListEmptyConstraintLayout.isVisible = state.threadSummaryList.invoke().isNullOrEmpty()
|
true -> views.threadListEmptyConstraintLayout.isVisible = false
|
||||||
false -> views.threadListEmptyConstraintLayout.isVisible = state.rootThreadEventList.invoke().isNullOrEmpty()
|
false -> views.threadListEmptyConstraintLayout.isVisible = state.rootThreadEventList.invoke().isNullOrEmpty()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ package im.vector.app.features.room
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import org.matrix.android.sdk.api.RoomDisplayNameFallbackProvider
|
import org.matrix.android.sdk.api.provider.RoomDisplayNameFallbackProvider
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class VectorRoomDisplayNameFallbackProvider @Inject constructor(
|
class VectorRoomDisplayNameFallbackProvider @Inject constructor(
|
||||||
|
|
|
@ -239,6 +239,7 @@ class VectorPreferences @Inject constructor(
|
||||||
|
|
||||||
// This key will be used to identify clients with the new thread support enabled m.thread
|
// This key will be used to identify clients with the new thread support enabled m.thread
|
||||||
const val SETTINGS_LABS_ENABLE_THREAD_MESSAGES = "SETTINGS_LABS_ENABLE_THREAD_MESSAGES_FINAL"
|
const val SETTINGS_LABS_ENABLE_THREAD_MESSAGES = "SETTINGS_LABS_ENABLE_THREAD_MESSAGES_FINAL"
|
||||||
|
const val SETTINGS_LABS_THREAD_MESSAGES_CHANGED_BY_USER = "SETTINGS_LABS_THREAD_MESSAGES_CHANGED_BY_USER"
|
||||||
const val SETTINGS_THREAD_MESSAGES_SYNCED = "SETTINGS_THREAD_MESSAGES_SYNCED"
|
const val SETTINGS_THREAD_MESSAGES_SYNCED = "SETTINGS_THREAD_MESSAGES_SYNCED"
|
||||||
|
|
||||||
// This key will be used to enable user for displaying live user info or not.
|
// This key will be used to enable user for displaying live user info or not.
|
||||||
|
@ -1135,6 +1136,24 @@ class VectorPreferences @Inject constructor(
|
||||||
.apply()
|
.apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether or not user changed threads flag manually. We need this to not force flag to be enabled on app start.
|
||||||
|
* Should be removed when Threads flag will be removed
|
||||||
|
*/
|
||||||
|
fun wasThreadFlagChangedManually(): Boolean {
|
||||||
|
return defaultPrefs.getBoolean(SETTINGS_LABS_THREAD_MESSAGES_CHANGED_BY_USER, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the flag to indicate that user changed threads flag (e.g. disabled them).
|
||||||
|
*/
|
||||||
|
fun setThreadFlagChangedManually() {
|
||||||
|
defaultPrefs
|
||||||
|
.edit()
|
||||||
|
.putBoolean(SETTINGS_LABS_THREAD_MESSAGES_CHANGED_BY_USER, true)
|
||||||
|
.apply()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates whether or not the user will be notified about the new thread support.
|
* Indicates whether or not the user will be notified about the new thread support.
|
||||||
* We should notify the user only if he had old thread support enabled.
|
* We should notify the user only if he had old thread support enabled.
|
||||||
|
|
|
@ -141,6 +141,7 @@ class VectorSettingsLabsFragment :
|
||||||
*/
|
*/
|
||||||
private fun onThreadsPreferenceClicked() {
|
private fun onThreadsPreferenceClicked() {
|
||||||
// We should migrate threads only if threads are disabled
|
// We should migrate threads only if threads are disabled
|
||||||
|
vectorPreferences.setThreadFlagChangedManually()
|
||||||
vectorPreferences.setShouldMigrateThreads(!vectorPreferences.areThreadMessagesEnabled())
|
vectorPreferences.setShouldMigrateThreads(!vectorPreferences.areThreadMessagesEnabled())
|
||||||
lightweightSettingsStorage.setThreadMessagesEnabled(vectorPreferences.areThreadMessagesEnabled())
|
lightweightSettingsStorage.setThreadMessagesEnabled(vectorPreferences.areThreadMessagesEnabled())
|
||||||
displayLoadingView()
|
displayLoadingView()
|
||||||
|
|
|
@ -31,7 +31,7 @@ import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayer.Stat
|
||||||
import im.vector.app.features.voicebroadcast.listening.usecase.GetLiveVoiceBroadcastChunksUseCase
|
import im.vector.app.features.voicebroadcast.listening.usecase.GetLiveVoiceBroadcastChunksUseCase
|
||||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcast
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcast
|
||||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastEvent
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastEvent
|
||||||
import im.vector.app.features.voicebroadcast.usecase.GetMostRecentVoiceBroadcastStateEventUseCase
|
import im.vector.app.features.voicebroadcast.usecase.GetVoiceBroadcastStateEventLiveUseCase
|
||||||
import im.vector.lib.core.utils.timer.CountUpTimer
|
import im.vector.lib.core.utils.timer.CountUpTimer
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
@ -48,7 +48,7 @@ import javax.inject.Singleton
|
||||||
class VoiceBroadcastPlayerImpl @Inject constructor(
|
class VoiceBroadcastPlayerImpl @Inject constructor(
|
||||||
private val sessionHolder: ActiveSessionHolder,
|
private val sessionHolder: ActiveSessionHolder,
|
||||||
private val playbackTracker: AudioMessagePlaybackTracker,
|
private val playbackTracker: AudioMessagePlaybackTracker,
|
||||||
private val getVoiceBroadcastEventUseCase: GetMostRecentVoiceBroadcastStateEventUseCase,
|
private val getVoiceBroadcastEventUseCase: GetVoiceBroadcastStateEventLiveUseCase,
|
||||||
private val getLiveVoiceBroadcastChunksUseCase: GetLiveVoiceBroadcastChunksUseCase
|
private val getLiveVoiceBroadcastChunksUseCase: GetLiveVoiceBroadcastChunksUseCase
|
||||||
) : VoiceBroadcastPlayer {
|
) : VoiceBroadcastPlayer {
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ import im.vector.app.features.voicebroadcast.model.VoiceBroadcastEvent
|
||||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
||||||
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
||||||
import im.vector.app.features.voicebroadcast.sequence
|
import im.vector.app.features.voicebroadcast.sequence
|
||||||
import im.vector.app.features.voicebroadcast.usecase.GetMostRecentVoiceBroadcastStateEventUseCase
|
import im.vector.app.features.voicebroadcast.usecase.GetVoiceBroadcastStateEventLiveUseCase
|
||||||
import im.vector.app.features.voicebroadcast.voiceBroadcastId
|
import im.vector.app.features.voicebroadcast.voiceBroadcastId
|
||||||
import kotlinx.coroutines.channels.awaitClose
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
@ -48,7 +48,7 @@ import javax.inject.Inject
|
||||||
*/
|
*/
|
||||||
class GetLiveVoiceBroadcastChunksUseCase @Inject constructor(
|
class GetLiveVoiceBroadcastChunksUseCase @Inject constructor(
|
||||||
private val activeSessionHolder: ActiveSessionHolder,
|
private val activeSessionHolder: ActiveSessionHolder,
|
||||||
private val getVoiceBroadcastEventUseCase: GetMostRecentVoiceBroadcastStateEventUseCase,
|
private val getVoiceBroadcastEventUseCase: GetVoiceBroadcastStateEventLiveUseCase,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun execute(voiceBroadcast: VoiceBroadcast): Flow<List<MessageAudioEvent>> {
|
fun execute(voiceBroadcast: VoiceBroadcast): Flow<List<MessageAudioEvent>> {
|
||||||
|
|
|
@ -26,7 +26,7 @@ import im.vector.app.features.voice.AbstractVoiceRecorderQ
|
||||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcast
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcast
|
||||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastEvent
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastEvent
|
||||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
||||||
import im.vector.app.features.voicebroadcast.usecase.GetMostRecentVoiceBroadcastStateEventUseCase
|
import im.vector.app.features.voicebroadcast.usecase.GetVoiceBroadcastStateEventLiveUseCase
|
||||||
import im.vector.lib.core.utils.timer.CountUpTimer
|
import im.vector.lib.core.utils.timer.CountUpTimer
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
@ -40,7 +40,7 @@ import java.util.concurrent.TimeUnit
|
||||||
class VoiceBroadcastRecorderQ(
|
class VoiceBroadcastRecorderQ(
|
||||||
context: Context,
|
context: Context,
|
||||||
private val sessionHolder: ActiveSessionHolder,
|
private val sessionHolder: ActiveSessionHolder,
|
||||||
private val getVoiceBroadcastEventUseCase: GetMostRecentVoiceBroadcastStateEventUseCase
|
private val getVoiceBroadcastEventUseCase: GetVoiceBroadcastStateEventLiveUseCase
|
||||||
) : AbstractVoiceRecorderQ(context), VoiceBroadcastRecorder {
|
) : AbstractVoiceRecorderQ(context), VoiceBroadcastRecorder {
|
||||||
|
|
||||||
private val session get() = sessionHolder.getActiveSession()
|
private val session get() = sessionHolder.getActiveSession()
|
||||||
|
|
|
@ -28,7 +28,7 @@ import im.vector.app.features.voicebroadcast.model.VoiceBroadcast
|
||||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastChunk
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastChunk
|
||||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
||||||
import im.vector.app.features.voicebroadcast.recording.VoiceBroadcastRecorder
|
import im.vector.app.features.voicebroadcast.recording.VoiceBroadcastRecorder
|
||||||
import im.vector.app.features.voicebroadcast.usecase.GetOngoingVoiceBroadcastsUseCase
|
import im.vector.app.features.voicebroadcast.usecase.GetRoomLiveVoiceBroadcastsUseCase
|
||||||
import im.vector.lib.multipicker.utils.toMultiPickerAudioType
|
import im.vector.lib.multipicker.utils.toMultiPickerAudioType
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -56,7 +56,7 @@ class StartVoiceBroadcastUseCase @Inject constructor(
|
||||||
private val voiceBroadcastRecorder: VoiceBroadcastRecorder?,
|
private val voiceBroadcastRecorder: VoiceBroadcastRecorder?,
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val buildMeta: BuildMeta,
|
private val buildMeta: BuildMeta,
|
||||||
private val getOngoingVoiceBroadcastsUseCase: GetOngoingVoiceBroadcastsUseCase,
|
private val getRoomLiveVoiceBroadcastsUseCase: GetRoomLiveVoiceBroadcastsUseCase,
|
||||||
private val stopVoiceBroadcastUseCase: StopVoiceBroadcastUseCase,
|
private val stopVoiceBroadcastUseCase: StopVoiceBroadcastUseCase,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@ -152,7 +152,7 @@ class StartVoiceBroadcastUseCase @Inject constructor(
|
||||||
Timber.d("## StartVoiceBroadcastUseCase: Cannot start voice broadcast: another voice broadcast")
|
Timber.d("## StartVoiceBroadcastUseCase: Cannot start voice broadcast: another voice broadcast")
|
||||||
throw VoiceBroadcastFailure.RecordingError.UserAlreadyBroadcasting
|
throw VoiceBroadcastFailure.RecordingError.UserAlreadyBroadcasting
|
||||||
}
|
}
|
||||||
getOngoingVoiceBroadcastsUseCase.execute(room.roomId).isNotEmpty() -> {
|
getRoomLiveVoiceBroadcastsUseCase.execute(room.roomId).isNotEmpty() -> {
|
||||||
Timber.d("## StartVoiceBroadcastUseCase: Cannot start voice broadcast: user already broadcasting")
|
Timber.d("## StartVoiceBroadcastUseCase: Cannot start voice broadcast: user already broadcasting")
|
||||||
throw VoiceBroadcastFailure.RecordingError.BlockedBySomeoneElse
|
throw VoiceBroadcastFailure.RecordingError.BlockedBySomeoneElse
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ package im.vector.app.features.voicebroadcast.recording.usecase
|
||||||
import im.vector.app.core.di.ActiveSessionHolder
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
import im.vector.app.features.voicebroadcast.VoiceBroadcastHelper
|
import im.vector.app.features.voicebroadcast.VoiceBroadcastHelper
|
||||||
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
||||||
import im.vector.app.features.voicebroadcast.usecase.GetOngoingVoiceBroadcastsUseCase
|
import im.vector.app.features.voicebroadcast.usecase.GetRoomLiveVoiceBroadcastsUseCase
|
||||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||||
import org.matrix.android.sdk.api.session.getRoom
|
import org.matrix.android.sdk.api.session.getRoom
|
||||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||||
|
@ -32,7 +32,7 @@ import javax.inject.Inject
|
||||||
*/
|
*/
|
||||||
class StopOngoingVoiceBroadcastUseCase @Inject constructor(
|
class StopOngoingVoiceBroadcastUseCase @Inject constructor(
|
||||||
private val activeSessionHolder: ActiveSessionHolder,
|
private val activeSessionHolder: ActiveSessionHolder,
|
||||||
private val getOngoingVoiceBroadcastsUseCase: GetOngoingVoiceBroadcastsUseCase,
|
private val getRoomLiveVoiceBroadcastsUseCase: GetRoomLiveVoiceBroadcastsUseCase,
|
||||||
private val voiceBroadcastHelper: VoiceBroadcastHelper,
|
private val voiceBroadcastHelper: VoiceBroadcastHelper,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ class StopOngoingVoiceBroadcastUseCase @Inject constructor(
|
||||||
|
|
||||||
recentRooms
|
recentRooms
|
||||||
.forEach { room ->
|
.forEach { room ->
|
||||||
val ongoingVoiceBroadcasts = getOngoingVoiceBroadcastsUseCase.execute(room.roomId)
|
val ongoingVoiceBroadcasts = getRoomLiveVoiceBroadcastsUseCase.execute(room.roomId)
|
||||||
val myOngoingVoiceBroadcastId = ongoingVoiceBroadcasts.find { it.root.stateKey == session.myUserId }?.reference?.eventId
|
val myOngoingVoiceBroadcastId = ongoingVoiceBroadcasts.find { it.root.stateKey == session.myUserId }?.reference?.eventId
|
||||||
val initialEvent = myOngoingVoiceBroadcastId?.let { room.timelineService().getTimelineEvent(it)?.root?.asVoiceBroadcastEvent() }
|
val initialEvent = myOngoingVoiceBroadcastId?.let { room.timelineService().getTimelineEvent(it)?.root?.asVoiceBroadcastEvent() }
|
||||||
if (myOngoingVoiceBroadcastId != null && initialEvent?.content?.deviceId == session.sessionParams.deviceId) {
|
if (myOngoingVoiceBroadcastId != null && initialEvent?.content?.deviceId == session.sessionParams.deviceId) {
|
||||||
|
|
|
@ -18,32 +18,26 @@ package im.vector.app.features.voicebroadcast.usecase
|
||||||
|
|
||||||
import im.vector.app.core.di.ActiveSessionHolder
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
|
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
|
||||||
|
import im.vector.app.features.voicebroadcast.isLive
|
||||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastEvent
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastEvent
|
||||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
|
||||||
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
||||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||||
import org.matrix.android.sdk.api.session.getRoom
|
import org.matrix.android.sdk.api.session.getRoom
|
||||||
import timber.log.Timber
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class GetOngoingVoiceBroadcastsUseCase @Inject constructor(
|
class GetRoomLiveVoiceBroadcastsUseCase @Inject constructor(
|
||||||
private val activeSessionHolder: ActiveSessionHolder,
|
private val activeSessionHolder: ActiveSessionHolder,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun execute(roomId: String): List<VoiceBroadcastEvent> {
|
fun execute(roomId: String): List<VoiceBroadcastEvent> {
|
||||||
val session = activeSessionHolder.getSafeActiveSession() ?: run {
|
val session = activeSessionHolder.getSafeActiveSession() ?: return emptyList()
|
||||||
Timber.d("## GetOngoingVoiceBroadcastsUseCase: no active session")
|
|
||||||
return emptyList()
|
|
||||||
}
|
|
||||||
val room = session.getRoom(roomId) ?: error("Unknown roomId: $roomId")
|
val room = session.getRoom(roomId) ?: error("Unknown roomId: $roomId")
|
||||||
|
|
||||||
Timber.d("## GetLastVoiceBroadcastUseCase: get last voice broadcast in $roomId")
|
|
||||||
|
|
||||||
return room.stateService().getStateEvents(
|
return room.stateService().getStateEvents(
|
||||||
setOf(VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO),
|
setOf(VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO),
|
||||||
QueryStringValue.IsNotEmpty
|
QueryStringValue.IsNotEmpty
|
||||||
)
|
)
|
||||||
.mapNotNull { it.asVoiceBroadcastEvent() }
|
.mapNotNull { it.asVoiceBroadcastEvent() }
|
||||||
.filter { it.content?.voiceBroadcastState != null && it.content?.voiceBroadcastState != VoiceBroadcastState.STOPPED }
|
.filter { it.isLive }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -42,7 +42,7 @@ import org.matrix.android.sdk.flow.mapOptional
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class GetMostRecentVoiceBroadcastStateEventUseCase @Inject constructor(
|
class GetVoiceBroadcastStateEventLiveUseCase @Inject constructor(
|
||||||
private val session: Session,
|
private val session: Session,
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -52,14 +52,14 @@ class StartVoiceBroadcastUseCaseTest {
|
||||||
private val fakeRoom = FakeRoom()
|
private val fakeRoom = FakeRoom()
|
||||||
private val fakeSession = FakeSession(fakeRoomService = FakeRoomService(fakeRoom))
|
private val fakeSession = FakeSession(fakeRoomService = FakeRoomService(fakeRoom))
|
||||||
private val fakeVoiceBroadcastRecorder = mockk<VoiceBroadcastRecorder>(relaxed = true)
|
private val fakeVoiceBroadcastRecorder = mockk<VoiceBroadcastRecorder>(relaxed = true)
|
||||||
private val fakeGetOngoingVoiceBroadcastsUseCase = mockk<GetOngoingVoiceBroadcastsUseCase>()
|
private val fakeGetRoomLiveVoiceBroadcastsUseCase = mockk<GetRoomLiveVoiceBroadcastsUseCase>()
|
||||||
private val startVoiceBroadcastUseCase = spyk(
|
private val startVoiceBroadcastUseCase = spyk(
|
||||||
StartVoiceBroadcastUseCase(
|
StartVoiceBroadcastUseCase(
|
||||||
session = fakeSession,
|
session = fakeSession,
|
||||||
voiceBroadcastRecorder = fakeVoiceBroadcastRecorder,
|
voiceBroadcastRecorder = fakeVoiceBroadcastRecorder,
|
||||||
context = FakeContext().instance,
|
context = FakeContext().instance,
|
||||||
buildMeta = mockk(),
|
buildMeta = mockk(),
|
||||||
getOngoingVoiceBroadcastsUseCase = fakeGetOngoingVoiceBroadcastsUseCase,
|
getRoomLiveVoiceBroadcastsUseCase = fakeGetRoomLiveVoiceBroadcastsUseCase,
|
||||||
stopVoiceBroadcastUseCase = mockk()
|
stopVoiceBroadcastUseCase = mockk()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -140,7 +140,7 @@ class StartVoiceBroadcastUseCaseTest {
|
||||||
}
|
}
|
||||||
.mapNotNull { it.asVoiceBroadcastEvent() }
|
.mapNotNull { it.asVoiceBroadcastEvent() }
|
||||||
.filter { it.content?.voiceBroadcastState != VoiceBroadcastState.STOPPED }
|
.filter { it.content?.voiceBroadcastState != VoiceBroadcastState.STOPPED }
|
||||||
every { fakeGetOngoingVoiceBroadcastsUseCase.execute(any()) } returns events
|
every { fakeGetRoomLiveVoiceBroadcastsUseCase.execute(any()) } returns events
|
||||||
}
|
}
|
||||||
|
|
||||||
private data class VoiceBroadcast(val userId: String, val state: VoiceBroadcastState)
|
private data class VoiceBroadcast(val userId: String, val state: VoiceBroadcastState)
|
||||||
|
|
Loading…
Reference in a new issue