Merge branch 'release/1.5.32' into main

This commit is contained in:
ganfra 2023-04-19 16:25:54 +02:00
commit 8dcb5f7098
33 changed files with 223 additions and 104 deletions

View File

@ -11,7 +11,7 @@ jobs:
- run: | - run: |
npm install --save-dev @babel/plugin-transform-flow-strip-types npm install --save-dev @babel/plugin-transform-flow-strip-types
- name: Danger - name: Danger
uses: danger/danger-js@11.2.4 uses: danger/danger-js@11.2.6
with: with:
args: "--dangerfile ./tools/danger/dangerfile.js" args: "--dangerfile ./tools/danger/dangerfile.js"
env: env:

View File

@ -66,7 +66,7 @@ jobs:
yarn add danger-plugin-lint-report --dev yarn add danger-plugin-lint-report --dev
- name: Danger lint - name: Danger lint
if: always() if: always()
uses: danger/danger-js@11.2.4 uses: danger/danger-js@11.2.6
with: with:
args: "--dangerfile ./tools/danger/dangerfile-lint.js" args: "--dangerfile ./tools/danger/dangerfile-lint.js"
env: env:

View File

@ -23,7 +23,7 @@ jobs:
- name: Run Emoji script - name: Run Emoji script
run: ./tools/import_emojis.py run: ./tools/import_emojis.py
- name: Create Pull Request for Emojis - name: Create Pull Request for Emojis
uses: peter-evans/create-pull-request@v4 uses: peter-evans/create-pull-request@v5
with: with:
commit-message: Sync Emojis commit-message: Sync Emojis
title: Sync Emojis title: Sync Emojis
@ -49,7 +49,7 @@ jobs:
- name: Run SAS String script - name: Run SAS String script
run: ./tools/import_sas_strings.py run: ./tools/import_sas_strings.py
- name: Create Pull Request for SAS Strings - name: Create Pull Request for SAS Strings
uses: peter-evans/create-pull-request@v4 uses: peter-evans/create-pull-request@v5
with: with:
commit-message: Sync SAS Strings commit-message: Sync SAS Strings
title: Sync SAS Strings title: Sync SAS Strings
@ -68,7 +68,7 @@ jobs:
- name: Run analytics import script - name: Run analytics import script
run: ./tools/import_analytic_plan.sh run: ./tools/import_analytic_plan.sh
- name: Create Pull Request for analytics plan - name: Create Pull Request for analytics plan
uses: peter-evans/create-pull-request@v4 uses: peter-evans/create-pull-request@v5
with: with:
commit-message: Sync analytics plan commit-message: Sync analytics plan
title: Sync analytics plan title: Sync analytics plan

View File

@ -1,3 +1,13 @@
Changes in Element v1.5.32 (2023-04-19)
=======================================
Bugfixes 🐛
----------
- Fix multiple read receipts for the same user in timeline. ([#7882](https://github.com/vector-im/element-android/issues/7882))
- The new permalink rendering is not applied on permalink created with the potential clientPermalinkBaseUrl ([#8307](https://github.com/vector-im/element-android/issues/8307))
- Keep screen on while recording voicebroadcast ([#8313](https://github.com/vector-im/element-android/issues/8313))
Changes in Element v1.5.30 (2023-04-05) Changes in Element v1.5.30 (2023-04-05)
======================================= =======================================

View File

@ -18,7 +18,7 @@ def markwon = "4.6.2"
def moshi = "1.14.0" def moshi = "1.14.0"
def lifecycle = "2.5.1" def lifecycle = "2.5.1"
def flowBinding = "1.2.0" def flowBinding = "1.2.0"
def flipper = "0.188.0" def flipper = "0.189.0"
def epoxy = "5.0.0" def epoxy = "5.0.0"
def mavericks = "3.0.2" def mavericks = "3.0.2"
def glide = "4.15.1" def glide = "4.15.1"
@ -27,7 +27,7 @@ def jjwt = "0.11.5"
def vanniktechEmoji = "0.16.0" def vanniktechEmoji = "0.16.0"
def sentry = "6.17.0" def sentry = "6.17.0"
// Use 1.6.0 alpha to fix issue with test // Use 1.6.0 alpha to fix issue with test
def fragment = "1.6.0-alpha08" def fragment = "1.6.0-alpha09"
// 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
def espresso = "3.5.1" def espresso = "3.5.1"
@ -50,14 +50,14 @@ ext.libs = [
'activity' : "androidx.activity:activity-ktx:1.7.0", 'activity' : "androidx.activity:activity-ktx:1.7.0",
'appCompat' : "androidx.appcompat:appcompat:1.6.1", 'appCompat' : "androidx.appcompat:appcompat:1.6.1",
'biometric' : "androidx.biometric:biometric:1.1.0", 'biometric' : "androidx.biometric:biometric:1.1.0",
'core' : "androidx.core:core-ktx:1.9.0", 'core' : "androidx.core:core-ktx:1.10.0",
'recyclerview' : "androidx.recyclerview:recyclerview:1.3.0", 'recyclerview' : "androidx.recyclerview:recyclerview:1.3.0",
'exifinterface' : "androidx.exifinterface:exifinterface:1.3.6", 'exifinterface' : "androidx.exifinterface:exifinterface:1.3.6",
'fragmentKtx' : "androidx.fragment:fragment-ktx:$fragment", 'fragmentKtx' : "androidx.fragment:fragment-ktx:$fragment",
'fragmentTesting' : "androidx.fragment:fragment-testing:$fragment", 'fragmentTesting' : "androidx.fragment:fragment-testing:$fragment",
'fragmentTestingManifest' : "androidx.fragment:fragment-testing-manifest:$fragment", 'fragmentTestingManifest' : "androidx.fragment:fragment-testing-manifest:$fragment",
'constraintLayout' : "androidx.constraintlayout:constraintlayout:2.1.4", 'constraintLayout' : "androidx.constraintlayout:constraintlayout:2.1.4",
'work' : "androidx.work:work-runtime-ktx:2.8.0", 'work' : "androidx.work:work-runtime-ktx:2.8.1",
'autoFill' : "androidx.autofill:autofill:1.1.0", 'autoFill' : "androidx.autofill:autofill:1.1.0",
'preferenceKtx' : "androidx.preference:preference-ktx:1.2.0", 'preferenceKtx' : "androidx.preference:preference-ktx:1.2.0",
'junit' : "androidx.test.ext:junit:1.1.5", 'junit' : "androidx.test.ext:junit:1.1.5",
@ -86,7 +86,7 @@ ext.libs = [
'appdistributionApi' : "com.google.firebase:firebase-appdistribution-api-ktx:$appDistribution", 'appdistributionApi' : "com.google.firebase:firebase-appdistribution-api-ktx:$appDistribution",
'appdistribution' : "com.google.firebase:firebase-appdistribution:$appDistribution", 'appdistribution' : "com.google.firebase:firebase-appdistribution:$appDistribution",
// Phone number https://github.com/google/libphonenumber // Phone number https://github.com/google/libphonenumber
'phonenumber' : "com.googlecode.libphonenumber:libphonenumber:8.13.8" 'phonenumber' : "com.googlecode.libphonenumber:libphonenumber:8.13.10"
], ],
dagger : [ dagger : [
'dagger' : "com.google.dagger:dagger:$dagger", 'dagger' : "com.google.dagger:dagger:$dagger",

View File

@ -0,0 +1,2 @@
Main changes in this version: Mainly bugfixing.
Full changelog: https://github.com/vector-im/element-android/releases

View File

@ -62,7 +62,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.30\"" buildConfigField "String", "SDK_VERSION", "\"1.5.32\""
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()}\""

View File

@ -48,4 +48,8 @@ class TestPermalinkService : PermalinkService {
MARKDOWN -> "[%2\$s](https://matrix.to/#/%1\$s)" MARKDOWN -> "[%2\$s](https://matrix.to/#/%1\$s)"
} }
} }
override fun isPermalinkSupported(supportedHosts: Array<String>, url: String): Boolean {
return false
}
} }

View File

@ -62,7 +62,7 @@ object MatrixPatterns {
// regex pattern to find permalink with message id. // regex pattern to find permalink with message id.
// Android does not support in URL so extract it. // Android does not support in URL so extract it.
private const val PERMALINK_BASE_REGEX = "https://matrix\\.to/#/" private const val PERMALINK_BASE_REGEX = "https://matrix\\.to/#/"
private const val APP_BASE_REGEX = "https://[A-Z0-9.-]+\\.[A-Z]{2,}/[A-Z]{3,}/#/room/" private const val APP_BASE_REGEX = "https://[A-Z0-9.-]+\\.[A-Z]{2,}/#/(room|user)/"
const val SEP_REGEX = "/" const val SEP_REGEX = "/"
private val PATTERN_CONTAIN_MATRIX_TO_PERMALINK = PERMALINK_BASE_REGEX.toRegex(RegexOption.IGNORE_CASE) private val PATTERN_CONTAIN_MATRIX_TO_PERMALINK = PERMALINK_BASE_REGEX.toRegex(RegexOption.IGNORE_CASE)

View File

@ -97,4 +97,15 @@ interface PermalinkService {
* @return the created template * @return the created template
*/ */
fun createMentionSpanTemplate(type: SpanTemplateType, forceMatrixTo: Boolean = false): String fun createMentionSpanTemplate(type: SpanTemplateType, forceMatrixTo: Boolean = false): String
/**
* Check if the url is a permalink. It must be a matrix.to link
* or a link with host provided by the string-array `permalink_supported_hosts` in the config file
*
* @param supportedHosts the list of hosts supported for permalinks
* @param url the link to check, Ex: "https://matrix.to/#/@benoit:matrix.org"
*
* @return true when url is a permalink
*/
fun isPermalinkSupported(supportedHosts: Array<String>, url: String): Boolean
} }

View File

@ -21,6 +21,7 @@ import io.realm.kotlin.createObject
import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.events.model.toModel
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.read.ReadService
import org.matrix.android.sdk.internal.crypto.model.SessionInfo import org.matrix.android.sdk.internal.crypto.model.SessionInfo
import org.matrix.android.sdk.internal.database.mapper.asDomain import org.matrix.android.sdk.internal.database.mapper.asDomain
import org.matrix.android.sdk.internal.database.model.ChunkEntity import org.matrix.android.sdk.internal.database.model.ChunkEntity
@ -76,7 +77,7 @@ internal fun ChunkEntity.addTimelineEvent(
val senderId = eventEntity.sender ?: "" val senderId = eventEntity.sender ?: ""
// Update RR for the sender of a new message with a dummy one // Update RR for the sender of a new message with a dummy one
val readReceiptsSummaryEntity = if (!ownedByThreadChunk) handleReadReceipts(realm, roomId, eventEntity, senderId) else null val readReceiptsSummaryEntity = handleReadReceiptsOfSender(realm, roomId, eventEntity, senderId)
val timelineEventEntity = realm.createObject<TimelineEventEntity>().apply { val timelineEventEntity = realm.createObject<TimelineEventEntity>().apply {
this.localId = localId this.localId = localId
this.root = eventEntity this.root = eventEntity
@ -124,7 +125,7 @@ internal fun computeIsUnique(
} }
} }
private fun handleReadReceipts(realm: Realm, roomId: String, eventEntity: EventEntity, senderId: String): ReadReceiptsSummaryEntity { private fun handleReadReceiptsOfSender(realm: Realm, roomId: String, eventEntity: EventEntity, senderId: String): ReadReceiptsSummaryEntity {
val readReceiptsSummaryEntity = ReadReceiptsSummaryEntity.where(realm, eventEntity.eventId).findFirst() val readReceiptsSummaryEntity = ReadReceiptsSummaryEntity.where(realm, eventEntity.eventId).findFirst()
?: realm.createObject<ReadReceiptsSummaryEntity>(eventEntity.eventId).apply { ?: realm.createObject<ReadReceiptsSummaryEntity>(eventEntity.eventId).apply {
this.roomId = roomId this.roomId = roomId
@ -132,7 +133,12 @@ private fun handleReadReceipts(realm: Realm, roomId: String, eventEntity: EventE
val originServerTs = eventEntity.originServerTs val originServerTs = eventEntity.originServerTs
if (originServerTs != null) { if (originServerTs != null) {
val timestampOfEvent = originServerTs.toDouble() val timestampOfEvent = originServerTs.toDouble()
val readReceiptOfSender = ReadReceiptEntity.getOrCreate(realm, roomId = roomId, userId = senderId, threadId = eventEntity.rootThreadEventId) val readReceiptOfSender = ReadReceiptEntity.getOrCreate(
realm = realm,
roomId = roomId,
userId = senderId,
threadId = eventEntity.rootThreadEventId ?: ReadService.THREAD_ID_MAIN
)
// If the synced RR is older, update // If the synced RR is older, update
if (timestampOfEvent > readReceiptOfSender.originServerTs) { if (timestampOfEvent > readReceiptOfSender.originServerTs) {
val previousReceiptsSummary = ReadReceiptsSummaryEntity.where(realm, eventId = readReceiptOfSender.eventId).findFirst() val previousReceiptsSummary = ReadReceiptsSummaryEntity.where(realm, eventId = readReceiptOfSender.eventId).findFirst()

View File

@ -16,6 +16,7 @@
package org.matrix.android.sdk.internal.session.permalinks package org.matrix.android.sdk.internal.session.permalinks
import androidx.core.net.toUri
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.permalinks.PermalinkService import org.matrix.android.sdk.api.session.permalinks.PermalinkService
import javax.inject.Inject import javax.inject.Inject
@ -47,4 +48,9 @@ internal class DefaultPermalinkService @Inject constructor(
override fun createMentionSpanTemplate(type: PermalinkService.SpanTemplateType, forceMatrixTo: Boolean): String { override fun createMentionSpanTemplate(type: PermalinkService.SpanTemplateType, forceMatrixTo: Boolean): String {
return permalinkFactory.createMentionSpanTemplate(type, forceMatrixTo) return permalinkFactory.createMentionSpanTemplate(type, forceMatrixTo)
} }
override fun isPermalinkSupported(supportedHosts: Array<String>, url: String): Boolean {
return url.startsWith(PermalinkService.MATRIX_TO_URL_BASE) ||
supportedHosts.any { url.toUri().host == it }
}
} }

View File

@ -2599,7 +2599,9 @@
"face", "face",
"shaking", "shaking",
"shock", "shock",
"vibrate" "vibrate",
"dizzy",
"blurry"
] ]
}, },
"relieved-face": { "relieved-face": {
@ -3800,7 +3802,8 @@
"heart", "heart",
"like", "like",
"love", "love",
"pink" "pink",
"valentines"
] ]
}, },
"orange-heart": { "orange-heart": {
@ -3854,8 +3857,9 @@
"cyan", "cyan",
"heart", "heart",
"light blue", "light blue",
"light blue heart", "teal",
"teal" "ice",
"baby blue"
] ]
}, },
"purple-heart": { "purple-heart": {
@ -3892,10 +3896,10 @@
"b": "1FA76", "b": "1FA76",
"j": [ "j": [
"gray", "gray",
"grey heart",
"heart", "heart",
"silver", "silver",
"slate" "slate",
"monochrome"
] ]
}, },
"white-heart": { "white-heart": {
@ -4198,11 +4202,12 @@
"j": [ "j": [
"high five", "high five",
"leftward", "leftward",
"leftwards pushing hand",
"push", "push",
"refuse", "refuse",
"stop", "stop",
"wait" "wait",
"highfive",
"pressing"
] ]
}, },
"rightwards-pushing-hand": { "rightwards-pushing-hand": {
@ -4213,9 +4218,10 @@
"push", "push",
"refuse", "refuse",
"rightward", "rightward",
"rightwards pushing hand",
"stop", "stop",
"wait" "wait",
"highfive",
"pressing"
] ]
}, },
"ok-hand": { "ok-hand": {
@ -8620,7 +8626,11 @@
"antlers", "antlers",
"elk", "elk",
"mammal", "mammal",
"moose" "shrek",
"canada",
"sweden",
"sven",
"cool"
] ]
}, },
"donkey": { "donkey": {
@ -8630,10 +8640,10 @@
"animal", "animal",
"ass", "ass",
"burro", "burro",
"donkey",
"mammal", "mammal",
"mule", "mule",
"stubborn" "stubborn",
"eeyore"
] ]
}, },
"horse": { "horse": {
@ -9311,7 +9321,8 @@
"bird", "bird",
"flying", "flying",
"mythology", "mythology",
"wing" "angel",
"birds"
] ]
}, },
"black-bird": { "black-bird": {
@ -9331,9 +9342,10 @@
"j": [ "j": [
"bird", "bird",
"fowl", "fowl",
"goose",
"honk", "honk",
"silly" "silly",
"jemima",
"goosebumps"
] ]
}, },
"frog": { "frog": {
@ -9581,10 +9593,11 @@
"burn", "burn",
"invertebrate", "invertebrate",
"jelly", "jelly",
"jellyfish",
"marine", "marine",
"ouch", "ouch",
"stinger" "stinger",
"sting",
"tentacles"
] ]
}, },
"snail": { "snail": {
@ -9885,7 +9898,6 @@
"j": [ "j": [
"bluebonnet", "bluebonnet",
"flower", "flower",
"hyacinth",
"lavender", "lavender",
"lupine", "lupine",
"snapdragon" "snapdragon"
@ -10423,9 +10435,11 @@
"b": "1FADA", "b": "1FADA",
"j": [ "j": [
"beer", "beer",
"ginger root",
"root", "root",
"spice" "spice",
"yellow",
"cooking",
"gingerbread"
] ]
}, },
"pea-pod": { "pea-pod": {
@ -10437,7 +10451,9 @@
"legume", "legume",
"pea", "pea",
"pod", "pod",
"vegetable" "vegetable",
"cozy",
"green"
] ]
}, },
"bread": { "bread": {
@ -15533,9 +15549,9 @@
"dance", "dance",
"fan", "fan",
"flutter", "flutter",
"folding hand fan",
"hot", "hot",
"shy" "shy",
"flamenco"
] ]
}, },
"purse": { "purse": {
@ -15719,7 +15735,8 @@
"Afro", "Afro",
"comb", "comb",
"hair", "hair",
"pick" "pick",
"afro"
] ]
}, },
"crown": { "crown": {
@ -16165,7 +16182,6 @@
"b": "1FA87", "b": "1FA87",
"j": [ "j": [
"instrument", "instrument",
"maracas",
"music", "music",
"percussion", "percussion",
"rattle", "rattle",
@ -16177,11 +16193,13 @@
"b": "1FA88", "b": "1FA88",
"j": [ "j": [
"fife", "fife",
"flute",
"music", "music",
"pipe", "pipe",
"recorder", "recorder",
"woodwind" "woodwind",
"bamboo",
"instrument",
"pied piper"
] ]
}, },
"mobile-phone": { "mobile-phone": {
@ -19036,9 +19054,9 @@
"a": "⊛ Khanda", "a": "⊛ Khanda",
"b": "1FAAF", "b": "1FAAF",
"j": [ "j": [
"khanda",
"religion", "religion",
"Sikh" "Sikh",
"Sikhism"
] ]
}, },
"aries": { "aries": {
@ -19460,7 +19478,9 @@
"computer", "computer",
"internet", "internet",
"network", "network",
"wireless" "wifi",
"contactless",
"signal"
] ]
}, },
"vibration-mode": { "vibration-mode": {

View File

@ -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 = 30 ext.versionPatch = 32
static def getGitTimestamp() { static def getGitTimestamp() {
def cmd = 'git show -s --format=%ct' def cmd = 'git show -s --format=%ct'

View File

@ -132,7 +132,7 @@ dependencies {
implementation libs.androidx.biometric implementation libs.androidx.biometric
api "org.threeten:threetenbp:1.4.0:no-tzdb" api "org.threeten:threetenbp:1.4.0:no-tzdb"
api "com.gabrielittner.threetenbp:lazythreetenbp:0.14.0" api "com.gabrielittner.threetenbp:lazythreetenbp:0.16.0"
implementation libs.squareup.moshi implementation libs.squareup.moshi
kapt libs.squareup.moshiKotlin kapt libs.squareup.moshiKotlin
@ -308,7 +308,7 @@ dependencies {
// Fix issue with Jitsi. Inspired from https://github.com/android/android-test/issues/861#issuecomment-872067868 // Fix issue with Jitsi. Inspired from https://github.com/android/android-test/issues/861#issuecomment-872067868
// Error was lots of `Duplicate class org.checkerframework.common.reflection.qual.MethodVal found in modules jetified-checker-3.1 (org.checkerframework:checker:3.1.1) and jetified-checker-qual-3.12.0 (org.checkerframework:checker-qual:3.12.0) // Error was lots of `Duplicate class org.checkerframework.common.reflection.qual.MethodVal found in modules jetified-checker-3.1 (org.checkerframework:checker:3.1.1) and jetified-checker-qual-3.12.0 (org.checkerframework:checker-qual:3.12.0)
//noinspection GradleDependency Cannot use latest 3.15.0 since it required min API 26. //noinspection GradleDependency Cannot use latest 3.15.0 since it required min API 26.
implementation "org.checkerframework:checker:3.32.0" implementation "org.checkerframework:checker:3.33.0"
androidTestImplementation libs.androidx.testCore androidTestImplementation libs.androidx.testCore
androidTestImplementation libs.androidx.testRunner androidTestImplementation libs.androidx.testRunner

View File

@ -182,6 +182,7 @@ class HomeActivityViewModel @AssistedInject constructor(
if (analyticsConfig.isEnabled) { if (analyticsConfig.isEnabled) {
analyticsStore.didAskUserConsentFlow analyticsStore.didAskUserConsentFlow
.onEach { didAskUser -> .onEach { didAskUser ->
Timber.v("DidAskUserConsent: $didAskUser")
if (!didAskUser) { if (!didAskUser) {
_viewEvents.post(HomeActivityViewEvents.ShowAnalyticsOptIn) _viewEvents.post(HomeActivityViewEvents.ShowAnalyticsOptIn)
} else { } else {

View File

@ -139,7 +139,6 @@ import im.vector.app.features.home.room.detail.composer.MessageComposerViewModel
import im.vector.app.features.home.room.detail.composer.boolean import im.vector.app.features.home.room.detail.composer.boolean
import im.vector.app.features.home.room.detail.composer.voice.VoiceRecorderFragment import im.vector.app.features.home.room.detail.composer.voice.VoiceRecorderFragment
import im.vector.app.features.home.room.detail.error.RoomNotFound import im.vector.app.features.home.room.detail.error.RoomNotFound
import im.vector.app.features.home.room.detail.readreceipts.DisplayReadReceiptsBottomSheet
import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.home.room.detail.timeline.TimelineEventController
import im.vector.app.features.home.room.detail.timeline.action.EventSharedAction import im.vector.app.features.home.room.detail.timeline.action.EventSharedAction
import im.vector.app.features.home.room.detail.timeline.action.MessageActionsBottomSheet import im.vector.app.features.home.room.detail.timeline.action.MessageActionsBottomSheet
@ -156,6 +155,7 @@ import im.vector.app.features.home.room.detail.timeline.item.MessageTextItem
import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceItem import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceItem
import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptData import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptData
import im.vector.app.features.home.room.detail.timeline.reactions.ViewReactionsBottomSheet import im.vector.app.features.home.room.detail.timeline.reactions.ViewReactionsBottomSheet
import im.vector.app.features.home.room.detail.timeline.readreceipts.DisplayReadReceiptsBottomSheet
import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever
import im.vector.app.features.home.room.detail.upgrade.MigrateRoomBottomSheet import im.vector.app.features.home.room.detail.upgrade.MigrateRoomBottomSheet
import im.vector.app.features.home.room.detail.views.RoomDetailLazyLoadedViews import im.vector.app.features.home.room.detail.views.RoomDetailLazyLoadedViews

View File

@ -1325,13 +1325,17 @@ class TimelineViewModel @AssistedInject constructor(
computeUnreadState(timelineEvents, roomSummary) computeUnreadState(timelineEvents, roomSummary)
} }
// We don't want live update of unread so we skip when we already had a HasUnread or HasNoUnread // We don't want live update of unread so we skip when we already had a HasUnread or HasNoUnread
// However, we want to update an existing HasUnread, if the readMarkerId hasn't changed, // However, we want to update an existing HasUnread, if the readMarkerId hasn't changed or when we go back in live,
// as we might be loading new events to fill gaps in the timeline. // as we might be loading new events to fill gaps in the timeline.
.distinctUntilChanged { previous, current -> .distinctUntilChanged { previous, current ->
when { when {
previous is UnreadState.Unknown || previous is UnreadState.ReadMarkerNotLoaded -> false previous is UnreadState.Unknown || previous is UnreadState.ReadMarkerNotLoaded -> false
previous is UnreadState.HasUnread && current is UnreadState.HasUnread && previous is UnreadState.HasUnread && current is UnreadState.HasUnread &&
previous.readMarkerId == current.readMarkerId -> false previous.readMarkerId == current.readMarkerId -> false
previous is UnreadState.HasUnread && (
current is UnreadState.HasUnread && previous.firstUnreadEventId != current.firstUnreadEventId ||
current is UnreadState.HasNoUnread
) && timeline?.isLive.orFalse() -> false
current is UnreadState.HasUnread || current is UnreadState.HasNoUnread -> true current is UnreadState.HasUnread || current is UnreadState.HasNoUnread -> true
else -> false else -> false
} }

View File

@ -239,7 +239,7 @@ class AudioMessageHelper @Inject constructor(
val percentage = currentPosition.toFloat() / totalDuration val percentage = currentPosition.toFloat() / totalDuration
playbackTracker.updatePlayingAtPlaybackTime(id, currentPosition, percentage) playbackTracker.updatePlayingAtPlaybackTime(id, currentPosition, percentage)
} else { } else {
playbackTracker.stopPlayback(id) playbackTracker.stopPlaybackOrRecorder(id)
stopPlaybackTicker() stopPlaybackTicker()
} }
} }

View File

@ -58,6 +58,7 @@ import im.vector.app.features.home.room.detail.timeline.item.ReactionsSummaryEve
import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptData import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptData
import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptsItem import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptsItem
import im.vector.app.features.home.room.detail.timeline.item.TypingItem_ import im.vector.app.features.home.room.detail.timeline.item.TypingItem_
import im.vector.app.features.home.room.detail.timeline.readreceipts.ReadReceiptsCache
import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever
import im.vector.app.features.media.AttachmentData import im.vector.app.features.media.AttachmentData
import im.vector.app.features.media.ImageContentRenderer import im.vector.app.features.media.ImageContentRenderer
@ -74,7 +75,6 @@ import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent
import org.matrix.android.sdk.api.session.room.model.message.MessageImageInfoContent import org.matrix.android.sdk.api.session.room.model.message.MessageImageInfoContent
import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent
import org.matrix.android.sdk.api.session.room.read.ReadService
import org.matrix.android.sdk.api.session.room.timeline.Timeline import org.matrix.android.sdk.api.session.room.timeline.Timeline
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import timber.log.Timber import timber.log.Timber
@ -201,7 +201,7 @@ class TimelineEventController @Inject constructor(
// Map eventId to adapter position // Map eventId to adapter position
private val adapterPositionMapping = HashMap<String, Int>() private val adapterPositionMapping = HashMap<String, Int>()
private val timelineEventsGroups = TimelineEventsGroups() private val timelineEventsGroups = TimelineEventsGroups()
private val receiptsByEvent = HashMap<String, MutableList<ReadReceipt>>() private val readReceiptsCache = ReadReceiptsCache()
private val modelCache = arrayListOf<CacheItemData?>() private val modelCache = arrayListOf<CacheItemData?>()
private var currentSnapshot: List<TimelineEvent> = emptyList() private var currentSnapshot: List<TimelineEvent> = emptyList()
private var inSubmitList: Boolean = false private var inSubmitList: Boolean = false
@ -417,7 +417,7 @@ class TimelineEventController @Inject constructor(
} }
Timber.v("Preprocess events took $preprocessEventsTiming ms") Timber.v("Preprocess events took $preprocessEventsTiming ms")
var numberOfEventsToBuild = 0 var numberOfEventsToBuild = 0
val lastSentEventWithoutReadReceipts = searchLastSentEventWithoutReadReceipts(receiptsByEvent) val lastSentEventWithoutReadReceipts = searchLastSentEventWithoutReadReceipts(readReceiptsCache.receiptsByEvent())
(0 until modelCache.size).forEach { position -> (0 until modelCache.size).forEach { position ->
val event = currentSnapshot[position] val event = currentSnapshot[position]
val nextEvent = currentSnapshot.nextOrNull(position) val nextEvent = currentSnapshot.nextOrNull(position)
@ -463,7 +463,7 @@ class TimelineEventController @Inject constructor(
} }
val itemCachedData = modelCache[position] ?: return@forEach val itemCachedData = modelCache[position] ?: return@forEach
// Then update with additional models if needed // Then update with additional models if needed
modelCache[position] = itemCachedData.enrichWithModels(event, nextEvent, position, receiptsByEvent) modelCache[position] = itemCachedData.enrichWithModels(event, nextEvent, position, readReceiptsCache.receiptsByEvent())
} }
Timber.v("Number of events to rebuild: $numberOfEventsToBuild on ${modelCache.size} total events") Timber.v("Number of events to rebuild: $numberOfEventsToBuild on ${modelCache.size} total events")
} }
@ -552,15 +552,15 @@ class TimelineEventController @Inject constructor(
} }
private fun preprocessReverseEvents() { private fun preprocessReverseEvents() {
receiptsByEvent.clear() readReceiptsCache.clear()
timelineEventsGroups.clear() timelineEventsGroups.clear()
val itr = currentSnapshot.listIterator(currentSnapshot.size) val itr = currentSnapshot.listIterator(currentSnapshot.size)
var lastShownEventId: String? = null var lastShownEventId: String? = null
while (itr.hasPrevious()) { while (itr.hasPrevious()) {
val event = itr.previous() val event = itr.previous()
timelineEventsGroups.addOrIgnore(event) timelineEventsGroups.addOrIgnore(event)
val currentReadReceipts = ArrayList(event.readReceipts).filter { val currentReadReceipts = event.readReceipts.filter {
it.roomMember.userId != session.myUserId && it.isVisibleInThisThread() it.roomMember.userId != session.myUserId
} }
if (timelineEventVisibilityHelper.shouldShowEvent( if (timelineEventVisibilityHelper.shouldShowEvent(
timelineEvent = event, timelineEvent = event,
@ -573,16 +573,7 @@ class TimelineEventController @Inject constructor(
if (lastShownEventId == null) { if (lastShownEventId == null) {
continue continue
} }
val existingReceipts = receiptsByEvent.getOrPut(lastShownEventId) { ArrayList() } readReceiptsCache.addReceiptsOnEvent(currentReadReceipts, lastShownEventId)
existingReceipts.addAll(currentReadReceipts)
}
}
private fun ReadReceipt.isVisibleInThisThread(): Boolean {
return if (partialState.isFromThreadTimeline()) {
this.threadId == partialState.rootThreadEventId
} else {
this.threadId == null || this.threadId == ReadService.THREAD_ID_MAIN
} }
} }

View File

@ -100,7 +100,7 @@ class AudioMessagePlaybackTracker @Inject constructor() {
} }
} }
fun stopPlayback(id: String) { fun stopPlaybackOrRecorder(id: String) {
val state = getPlaybackState(id) val state = getPlaybackState(id)
if (state !is Listener.State.Error) { if (state !is Listener.State.Error) {
setState(id, Listener.State.Idle) setState(id, Listener.State.Idle)

View File

@ -1,11 +1,11 @@
/* /*
* Copyright 2019 New Vector Ltd * Copyright (c) 2023 New Vector Ltd
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package im.vector.app.features.home.room.detail.readreceipts package im.vector.app.features.home.room.detail.timeline.readreceipts
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView

View File

@ -1,11 +1,11 @@
/* /*
* Copyright 2019 New Vector Ltd * Copyright (c) 2023 New Vector Ltd
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package im.vector.app.features.home.room.detail.readreceipts package im.vector.app.features.home.room.detail.timeline.readreceipts
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable import android.os.Parcelable

View File

@ -1,11 +1,11 @@
/* /*
* Copyright 2019 New Vector Ltd * Copyright (c) 2023 New Vector Ltd
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package im.vector.app.features.home.room.detail.readreceipts package im.vector.app.features.home.room.detail.timeline.readreceipts
import com.airbnb.epoxy.TypedEpoxyController import com.airbnb.epoxy.TypedEpoxyController
import im.vector.app.core.date.DateFormatKind import im.vector.app.core.date.DateFormatKind

View File

@ -0,0 +1,54 @@
/*
* Copyright (c) 2023 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.detail.timeline.readreceipts
import im.vector.lib.core.utils.compat.removeIfCompat
import org.matrix.android.sdk.api.session.room.model.ReadReceipt
class ReadReceiptsCache {
private val receiptsByEventId = HashMap<String, MutableList<ReadReceipt>>()
// Key is userId, Value is eventId
private val receiptEventIdByUserId = HashMap<String, String>()
fun receiptsByEvent(): Map<String, List<ReadReceipt>> {
return receiptsByEventId
}
fun addReceiptsOnEvent(receipts: List<ReadReceipt>, eventId: String) {
val existingReceipts = receiptsByEventId.getOrPut(eventId) { ArrayList() }
receipts.forEach { readReceipt ->
val receiptUserId = readReceipt.roomMember.userId
val receiptEventId = receiptEventIdByUserId[receiptUserId]
// If we already have a read receipt for this user, move it so we only
// use the most recent. It can happen because of threaded read receipts.
if (receiptEventId != null) {
receiptsByEventId[receiptEventId]?.removeIfCompat {
it.roomMember.userId == receiptUserId
}
}
receiptEventIdByUserId[receiptUserId] = eventId
existingReceipts.add(readReceipt)
}
}
fun clear() {
receiptsByEventId.clear()
receiptEventIdByUserId.clear()
}
}

View File

@ -29,7 +29,7 @@ import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.glide.GlideApp import im.vector.app.core.glide.GlideApp
import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.html.PillImageSpan import im.vector.app.features.html.PillImageSpan
import org.matrix.android.sdk.api.MatrixPatterns import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.getRoomSummary
import org.matrix.android.sdk.api.session.getUserOrDefault import org.matrix.android.sdk.api.session.getUserOrDefault
import org.matrix.android.sdk.api.session.permalinks.PermalinkData import org.matrix.android.sdk.api.session.permalinks.PermalinkData
@ -99,7 +99,9 @@ class EventTextRenderer @AssistedInject constructor(
private fun addPermalinksSpans(text: Spannable) { private fun addPermalinksSpans(text: Spannable) {
for (match in Patterns.WEB_URL.toRegex().findAll(text)) { for (match in Patterns.WEB_URL.toRegex().findAll(text)) {
val url = text.substring(match.range) val url = text.substring(match.range)
val matrixItem = if (MatrixPatterns.isPermalink(url)) { val supportedHosts = context.resources.getStringArray(R.array.permalink_supported_hosts)
val isPermalinkSupported = sessionHolder.getSafeActiveSession()?.permalinkService()?.isPermalinkSupported(supportedHosts, url).orFalse()
val matrixItem = if (isPermalinkSupported) {
when (val permalinkData = PermalinkParser.parse(url)) { when (val permalinkData = PermalinkParser.parse(url)) {
is PermalinkData.UserLink -> permalinkData.toMatrixItem() is PermalinkData.UserLink -> permalinkData.toMatrixItem()
is PermalinkData.RoomLink -> permalinkData.toMatrixItem() is PermalinkData.RoomLink -> permalinkData.toMatrixItem()

View File

@ -22,10 +22,12 @@ import android.text.Spanned
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 im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.glide.GlideApp import im.vector.app.core.glide.GlideApp
import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.AvatarRenderer
import io.noties.markwon.core.spans.LinkSpan import io.noties.markwon.core.spans.LinkSpan
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.getRoomSummary
import org.matrix.android.sdk.api.session.getUser import org.matrix.android.sdk.api.session.getUser
import org.matrix.android.sdk.api.session.permalinks.PermalinkData import org.matrix.android.sdk.api.session.permalinks.PermalinkData
@ -105,12 +107,18 @@ class PillsPostProcessor @AssistedInject constructor(
PillImageSpan(GlideApp.with(context), avatarRenderer, context, matrixItem) PillImageSpan(GlideApp.with(context), avatarRenderer, context, matrixItem)
private fun LinkSpan.createPillSpan(): PillImageSpan? { private fun LinkSpan.createPillSpan(): PillImageSpan? {
val matrixItem = when (val permalinkData = PermalinkParser.parse(url)) { val supportedHosts = context.resources.getStringArray(R.array.permalink_supported_hosts)
is PermalinkData.UserLink -> permalinkData.toMatrixItem() val isPermalinkSupported = sessionHolder.getSafeActiveSession()?.permalinkService()?.isPermalinkSupported(supportedHosts, url).orFalse()
is PermalinkData.RoomLink -> permalinkData.toMatrixItem() if (isPermalinkSupported) {
else -> null val matrixItem = when (val permalinkData = PermalinkParser.parse(url)) {
} ?: return null is PermalinkData.UserLink -> permalinkData.toMatrixItem()
return createPillImageSpan(matrixItem) is PermalinkData.RoomLink -> permalinkData.toMatrixItem()
else -> null
} ?: return null
return createPillImageSpan(matrixItem)
} else {
return null
}
} }
private fun PermalinkData.UserLink.toMatrixItem(): MatrixItem? = private fun PermalinkData.UserLink.toMatrixItem(): MatrixItem? =

View File

@ -18,7 +18,6 @@ package im.vector.app.features.permalink
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import androidx.core.net.toUri
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.ActiveSessionHolder
@ -38,7 +37,6 @@ import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.getRoomSummary
import org.matrix.android.sdk.api.session.permalinks.PermalinkData import org.matrix.android.sdk.api.session.permalinks.PermalinkData
import org.matrix.android.sdk.api.session.permalinks.PermalinkParser import org.matrix.android.sdk.api.session.permalinks.PermalinkParser
import org.matrix.android.sdk.api.session.permalinks.PermalinkService
import org.matrix.android.sdk.api.session.room.getTimelineEvent import org.matrix.android.sdk.api.session.room.getTimelineEvent
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
@ -68,10 +66,11 @@ class PermalinkHandler @Inject constructor(
navigationInterceptor: NavigationInterceptor? = null, navigationInterceptor: NavigationInterceptor? = null,
buildTask: Boolean = false buildTask: Boolean = false
): Boolean { ): Boolean {
val supportedHosts = fragmentActivity.resources.getStringArray(R.array.permalink_supported_hosts)
return when { return when {
deepLink == null -> false deepLink == null -> false
deepLink.isIgnored() -> true deepLink.isIgnored() -> true
!isPermalinkSupported(fragmentActivity, deepLink.toString()) -> false !activeSessionHolder.getSafeActiveSession()?.permalinkService()?.isPermalinkSupported(supportedHosts, deepLink.toString()).orFalse() -> false
else -> { else -> {
tryOrNull { tryOrNull {
withContext(Dispatchers.Default) { withContext(Dispatchers.Default) {
@ -167,12 +166,6 @@ class PermalinkHandler @Inject constructor(
} }
} }
private fun isPermalinkSupported(context: Context, url: String): Boolean {
return url.startsWith(PermalinkService.MATRIX_TO_URL_BASE) ||
context.resources.getStringArray(R.array.permalink_supported_hosts)
.any { url.toUri().host == it }
}
private suspend fun PermalinkData.RoomLink.getRoomId(): String? { private suspend fun PermalinkData.RoomLink.getRoomId(): String? {
val session = activeSessionHolder.getSafeActiveSession() val session = activeSessionHolder.getSafeActiveSession()
return if (isRoomAlias && session != null) { return if (isRoomAlias && session != null) {

View File

@ -527,7 +527,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
State.Idle -> { State.Idle -> {
// restart the playback time if player completed with less than 1s remaining time // restart the playback time if player completed with less than 1s remaining time
if (percentage == null || (playlist.duration - position) < 1000) { if (percentage == null || (playlist.duration - position) < 1000) {
playbackTracker.stopPlayback(id) playbackTracker.stopPlaybackOrRecorder(id)
} else { } else {
playbackTracker.updatePausedAtPlaybackTime(id, position, percentage) playbackTracker.updatePausedAtPlaybackTime(id, position, percentage)
} }

View File

@ -20,6 +20,7 @@ import android.content.Context
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import im.vector.app.core.resources.BuildMeta import im.vector.app.core.resources.BuildMeta
import im.vector.app.features.attachments.toContentAttachmentData import im.vector.app.features.attachments.toContentAttachmentData
import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker
import im.vector.app.features.session.coroutineScope import im.vector.app.features.session.coroutineScope
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
import im.vector.app.features.voicebroadcast.VoiceBroadcastFailure import im.vector.app.features.voicebroadcast.VoiceBroadcastFailure
@ -54,6 +55,7 @@ import javax.inject.Inject
class StartVoiceBroadcastUseCase @Inject constructor( class StartVoiceBroadcastUseCase @Inject constructor(
private val session: Session, private val session: Session,
private val voiceBroadcastRecorder: VoiceBroadcastRecorder?, private val voiceBroadcastRecorder: VoiceBroadcastRecorder?,
private val playbackTracker: AudioMessagePlaybackTracker,
private val context: Context, private val context: Context,
private val buildMeta: BuildMeta, private val buildMeta: BuildMeta,
private val getRoomLiveVoiceBroadcastsUseCase: GetRoomLiveVoiceBroadcastsUseCase, private val getRoomLiveVoiceBroadcastsUseCase: GetRoomLiveVoiceBroadcastsUseCase,
@ -106,10 +108,14 @@ class StartVoiceBroadcastUseCase @Inject constructor(
} }
override fun onStateUpdated(state: VoiceBroadcastRecorder.State) { override fun onStateUpdated(state: VoiceBroadcastRecorder.State) {
if (state == VoiceBroadcastRecorder.State.Error) { when (state) {
session.coroutineScope.launch { VoiceBroadcastRecorder.State.Recording -> playbackTracker.updateCurrentRecording(AudioMessagePlaybackTracker.RECORDING_ID, emptyList())
pauseVoiceBroadcastUseCase.execute(room.roomId) VoiceBroadcastRecorder.State.Idle -> playbackTracker.stopPlaybackOrRecorder(AudioMessagePlaybackTracker.RECORDING_ID)
VoiceBroadcastRecorder.State.Error -> {
playbackTracker.stopPlaybackOrRecorder(AudioMessagePlaybackTracker.RECORDING_ID)
session.coroutineScope.launch { pauseVoiceBroadcastUseCase.execute(room.roomId) }
} }
else -> Unit
} }
} }
}) })

View File

@ -47,8 +47,7 @@ class GetVoiceBroadcastStateEventLiveUseCase @Inject constructor(
) { ) {
fun execute(voiceBroadcast: VoiceBroadcast): Flow<Optional<VoiceBroadcastEvent>> { fun execute(voiceBroadcast: VoiceBroadcast): Flow<Optional<VoiceBroadcastEvent>> {
val room = session.getRoom(voiceBroadcast.roomId) ?: error("Unknown roomId: ${voiceBroadcast.roomId}") return getMostRecentVoiceBroadcastEventFlow(voiceBroadcast)
return getMostRecentVoiceBroadcastEventFlow(room, voiceBroadcast)
.onEach { event -> .onEach { event ->
Timber.d( Timber.d(
"## VoiceBroadcast | " + "## VoiceBroadcast | " +
@ -61,7 +60,8 @@ class GetVoiceBroadcastStateEventLiveUseCase @Inject constructor(
/** /**
* Get a flow of the most recent event for the given voice broadcast. * Get a flow of the most recent event for the given voice broadcast.
*/ */
private fun getMostRecentVoiceBroadcastEventFlow(room: Room, voiceBroadcast: VoiceBroadcast): Flow<Optional<VoiceBroadcastEvent>> { private fun getMostRecentVoiceBroadcastEventFlow(voiceBroadcast: VoiceBroadcast): Flow<Optional<VoiceBroadcastEvent>> {
val room = session.getRoom(voiceBroadcast.roomId) ?: error("Unknown roomId: ${voiceBroadcast.roomId}")
val startedEventFlow = room.flow().liveTimelineEvent(voiceBroadcast.voiceBroadcastId) val startedEventFlow = room.flow().liveTimelineEvent(voiceBroadcast.voiceBroadcastId)
// observe started event changes // observe started event changes
return startedEventFlow return startedEventFlow

File diff suppressed because one or more lines are too long

View File

@ -57,6 +57,7 @@ class StartVoiceBroadcastUseCaseTest {
StartVoiceBroadcastUseCase( StartVoiceBroadcastUseCase(
session = fakeSession, session = fakeSession,
voiceBroadcastRecorder = fakeVoiceBroadcastRecorder, voiceBroadcastRecorder = fakeVoiceBroadcastRecorder,
playbackTracker = mockk(),
context = FakeContext().instance, context = FakeContext().instance,
buildMeta = mockk(), buildMeta = mockk(),
getRoomLiveVoiceBroadcastsUseCase = fakeGetRoomLiveVoiceBroadcastsUseCase, getRoomLiveVoiceBroadcastsUseCase = fakeGetRoomLiveVoiceBroadcastsUseCase,