Merge tag 'v1.5.32' into sc
Note: mainly conflicts in differing implementations to fix duplicate read receipts. After a quick glance at the upstream implementation, my first impression is that we likely want to keep our downstream implementation: - Better performance by checking for the more recent receipt in the SDK upon receipt receival, instead on rendering the timeline - null seems to always map to main, which caused us jumping receipts in the past, which we have addressed by now Conflicts: matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt vector/src/main/java/im/vector/app/features/html/PillsPostProcessor.kt Change-Id: I47ae2afe5fcbe77a4fec69374d83821b94a431de
This commit is contained in:
commit
acdf419768
|
@ -11,7 +11,7 @@ jobs:
|
|||
- run: |
|
||||
npm install --save-dev @babel/plugin-transform-flow-strip-types
|
||||
- name: Danger
|
||||
uses: danger/danger-js@11.2.4
|
||||
uses: danger/danger-js@11.2.6
|
||||
with:
|
||||
args: "--dangerfile ./tools/danger/dangerfile.js"
|
||||
env:
|
||||
|
|
|
@ -66,7 +66,7 @@ jobs:
|
|||
yarn add danger-plugin-lint-report --dev
|
||||
- name: Danger lint
|
||||
if: always()
|
||||
uses: danger/danger-js@11.2.4
|
||||
uses: danger/danger-js@11.2.6
|
||||
with:
|
||||
args: "--dangerfile ./tools/danger/dangerfile-lint.js"
|
||||
env:
|
||||
|
|
|
@ -23,7 +23,7 @@ jobs:
|
|||
- name: Run Emoji script
|
||||
run: ./tools/import_emojis.py
|
||||
- name: Create Pull Request for Emojis
|
||||
uses: peter-evans/create-pull-request@v4
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
with:
|
||||
commit-message: Sync Emojis
|
||||
title: Sync Emojis
|
||||
|
@ -49,7 +49,7 @@ jobs:
|
|||
- name: Run SAS String script
|
||||
run: ./tools/import_sas_strings.py
|
||||
- name: Create Pull Request for SAS Strings
|
||||
uses: peter-evans/create-pull-request@v4
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
with:
|
||||
commit-message: Sync SAS Strings
|
||||
title: Sync SAS Strings
|
||||
|
@ -68,7 +68,7 @@ jobs:
|
|||
- name: Run analytics import script
|
||||
run: ./tools/import_analytic_plan.sh
|
||||
- name: Create Pull Request for analytics plan
|
||||
uses: peter-evans/create-pull-request@v4
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
with:
|
||||
commit-message: Sync analytics plan
|
||||
title: Sync analytics plan
|
||||
|
|
10
CHANGES.md
10
CHANGES.md
|
@ -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)
|
||||
=======================================
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ def markwon = "4.6.2"
|
|||
def moshi = "1.14.0"
|
||||
def lifecycle = "2.5.1"
|
||||
def flowBinding = "1.2.0"
|
||||
def flipper = "0.188.0"
|
||||
def flipper = "0.189.0"
|
||||
def epoxy = "5.0.0"
|
||||
def mavericks = "3.0.2"
|
||||
def glide = "4.14.2"
|
||||
|
@ -27,7 +27,7 @@ def jjwt = "0.11.5"
|
|||
def vanniktechEmoji = "0.16.0"
|
||||
def sentry = "6.17.0"
|
||||
// Use 1.6.0 alpha to fix issue with test
|
||||
def fragment = "1.6.0-alpha08"
|
||||
def fragment = "1.6.0-alpha09"
|
||||
// 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 espresso = "3.5.1"
|
||||
|
@ -51,14 +51,14 @@ ext.libs = [
|
|||
'activity' : "androidx.activity:activity-ktx:1.7.0",
|
||||
'appCompat' : "androidx.appcompat:appcompat:1.6.1",
|
||||
'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",
|
||||
'exifinterface' : "androidx.exifinterface:exifinterface:1.3.6",
|
||||
'fragmentKtx' : "androidx.fragment:fragment-ktx:$fragment",
|
||||
'fragmentTesting' : "androidx.fragment:fragment-testing:$fragment",
|
||||
'fragmentTestingManifest' : "androidx.fragment:fragment-testing-manifest:$fragment",
|
||||
'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",
|
||||
'preferenceKtx' : "androidx.preference:preference-ktx:1.2.0",
|
||||
'junit' : "androidx.test.ext:junit:1.1.5",
|
||||
|
@ -87,7 +87,7 @@ ext.libs = [
|
|||
//'appdistributionApi' : "com.google.firebase:firebase-appdistribution-api-ktx:$appDistribution",
|
||||
//'appdistribution' : "com.google.firebase:firebase-appdistribution:$appDistribution",
|
||||
// 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' : "com.google.dagger:dagger:$dagger",
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
Main changes in this version: Mainly bugfixing.
|
||||
Full changelog: https://github.com/vector-im/element-android/releases
|
|
@ -62,7 +62,7 @@ android {
|
|||
// that the app's state is completely cleared between tests.
|
||||
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_UNIX_DATE", "\"${gitRevisionUnixDate()}\""
|
||||
|
|
|
@ -48,4 +48,8 @@ class TestPermalinkService : PermalinkService {
|
|||
MARKDOWN -> "[%2\$s](https://matrix.to/#/%1\$s)"
|
||||
}
|
||||
}
|
||||
|
||||
override fun isPermalinkSupported(supportedHosts: Array<String>, url: String): Boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ object MatrixPatterns {
|
|||
// regex pattern to find permalink with message id.
|
||||
// Android does not support in URL so extract it.
|
||||
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 = "/"
|
||||
|
||||
private val PATTERN_CONTAIN_MATRIX_TO_PERMALINK = PERMALINK_BASE_REGEX.toRegex(RegexOption.IGNORE_CASE)
|
||||
|
|
|
@ -97,4 +97,15 @@ interface PermalinkService {
|
|||
* @return the created template
|
||||
*/
|
||||
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
|
||||
}
|
||||
|
|
|
@ -23,6 +23,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.toModel
|
||||
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.api.session.room.read.ReadService.Companion.THREAD_ID_MAIN
|
||||
import org.matrix.android.sdk.api.session.room.read.ReadService.Companion.THREAD_ID_MAIN_OR_NULL
|
||||
import org.matrix.android.sdk.internal.crypto.model.SessionInfo
|
||||
|
@ -80,7 +81,7 @@ internal fun ChunkEntity.addTimelineEvent(
|
|||
val senderId = eventEntity.sender ?: ""
|
||||
|
||||
// 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 {
|
||||
this.localId = localId
|
||||
this.root = eventEntity
|
||||
|
@ -130,7 +131,7 @@ internal fun computeIsUnique(
|
|||
|
||||
private val rrDimber = Dimber("ReadReceipts", DbgUtil.DBG_READ_RECEIPTS)
|
||||
|
||||
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()
|
||||
?: realm.createObject<ReadReceiptsSummaryEntity>(eventEntity.eventId).apply {
|
||||
this.roomId = roomId
|
||||
|
@ -140,12 +141,20 @@ private fun handleReadReceipts(realm: Realm, roomId: String, eventEntity: EventE
|
|||
val timestampOfEvent = originServerTs.toDouble()
|
||||
// SC: fight duplicate read receipts in main timeline
|
||||
val receiptDestinations = if (eventEntity.rootThreadEventId in listOf(null, THREAD_ID_MAIN)) {
|
||||
// Upstream v1.3.32 now does ?: THREAD_ID_MAIN for this... but that might cause some issues with jumping receipts
|
||||
// that we had before, so stick to our approach for now...?
|
||||
// setOf(eventEntity.rootThreadEventId ?: THREAD_ID_MAIN, THREAD_ID_MAIN_OR_NULL)
|
||||
setOf(eventEntity.rootThreadEventId, THREAD_ID_MAIN_OR_NULL)
|
||||
} else {
|
||||
setOf(eventEntity.rootThreadEventId)
|
||||
}
|
||||
receiptDestinations.forEach { rootThreadEventId ->
|
||||
val readReceiptOfSender = ReadReceiptEntity.getOrCreate(realm, roomId = roomId, userId = senderId, threadId = rootThreadEventId)
|
||||
val readReceiptOfSender = ReadReceiptEntity.getOrCreate(
|
||||
realm = realm,
|
||||
roomId = roomId,
|
||||
userId = senderId,
|
||||
threadId = rootThreadEventId
|
||||
)
|
||||
val shouldForceMon: Boolean
|
||||
val shouldSkipMon: Boolean
|
||||
if (rootThreadEventId == THREAD_ID_MAIN_OR_NULL) {
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
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.permalinks.PermalinkService
|
||||
import javax.inject.Inject
|
||||
|
@ -47,4 +48,9 @@ internal class DefaultPermalinkService @Inject constructor(
|
|||
override fun createMentionSpanTemplate(type: PermalinkService.SpanTemplateType, forceMatrixTo: Boolean): String {
|
||||
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 }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2599,7 +2599,9 @@
|
|||
"face",
|
||||
"shaking",
|
||||
"shock",
|
||||
"vibrate"
|
||||
"vibrate",
|
||||
"dizzy",
|
||||
"blurry"
|
||||
]
|
||||
},
|
||||
"relieved-face": {
|
||||
|
@ -3800,7 +3802,8 @@
|
|||
"heart",
|
||||
"like",
|
||||
"love",
|
||||
"pink"
|
||||
"pink",
|
||||
"valentines"
|
||||
]
|
||||
},
|
||||
"orange-heart": {
|
||||
|
@ -3854,8 +3857,9 @@
|
|||
"cyan",
|
||||
"heart",
|
||||
"light blue",
|
||||
"light blue heart",
|
||||
"teal"
|
||||
"teal",
|
||||
"ice",
|
||||
"baby blue"
|
||||
]
|
||||
},
|
||||
"purple-heart": {
|
||||
|
@ -3892,10 +3896,10 @@
|
|||
"b": "1FA76",
|
||||
"j": [
|
||||
"gray",
|
||||
"grey heart",
|
||||
"heart",
|
||||
"silver",
|
||||
"slate"
|
||||
"slate",
|
||||
"monochrome"
|
||||
]
|
||||
},
|
||||
"white-heart": {
|
||||
|
@ -4198,11 +4202,12 @@
|
|||
"j": [
|
||||
"high five",
|
||||
"leftward",
|
||||
"leftwards pushing hand",
|
||||
"push",
|
||||
"refuse",
|
||||
"stop",
|
||||
"wait"
|
||||
"wait",
|
||||
"highfive",
|
||||
"pressing"
|
||||
]
|
||||
},
|
||||
"rightwards-pushing-hand": {
|
||||
|
@ -4213,9 +4218,10 @@
|
|||
"push",
|
||||
"refuse",
|
||||
"rightward",
|
||||
"rightwards pushing hand",
|
||||
"stop",
|
||||
"wait"
|
||||
"wait",
|
||||
"highfive",
|
||||
"pressing"
|
||||
]
|
||||
},
|
||||
"ok-hand": {
|
||||
|
@ -8620,7 +8626,11 @@
|
|||
"antlers",
|
||||
"elk",
|
||||
"mammal",
|
||||
"moose"
|
||||
"shrek",
|
||||
"canada",
|
||||
"sweden",
|
||||
"sven",
|
||||
"cool"
|
||||
]
|
||||
},
|
||||
"donkey": {
|
||||
|
@ -8630,10 +8640,10 @@
|
|||
"animal",
|
||||
"ass",
|
||||
"burro",
|
||||
"donkey",
|
||||
"mammal",
|
||||
"mule",
|
||||
"stubborn"
|
||||
"stubborn",
|
||||
"eeyore"
|
||||
]
|
||||
},
|
||||
"horse": {
|
||||
|
@ -9311,7 +9321,8 @@
|
|||
"bird",
|
||||
"flying",
|
||||
"mythology",
|
||||
"wing"
|
||||
"angel",
|
||||
"birds"
|
||||
]
|
||||
},
|
||||
"black-bird": {
|
||||
|
@ -9331,9 +9342,10 @@
|
|||
"j": [
|
||||
"bird",
|
||||
"fowl",
|
||||
"goose",
|
||||
"honk",
|
||||
"silly"
|
||||
"silly",
|
||||
"jemima",
|
||||
"goosebumps"
|
||||
]
|
||||
},
|
||||
"frog": {
|
||||
|
@ -9581,10 +9593,11 @@
|
|||
"burn",
|
||||
"invertebrate",
|
||||
"jelly",
|
||||
"jellyfish",
|
||||
"marine",
|
||||
"ouch",
|
||||
"stinger"
|
||||
"stinger",
|
||||
"sting",
|
||||
"tentacles"
|
||||
]
|
||||
},
|
||||
"snail": {
|
||||
|
@ -9885,7 +9898,6 @@
|
|||
"j": [
|
||||
"bluebonnet",
|
||||
"flower",
|
||||
"hyacinth",
|
||||
"lavender",
|
||||
"lupine",
|
||||
"snapdragon"
|
||||
|
@ -10423,9 +10435,11 @@
|
|||
"b": "1FADA",
|
||||
"j": [
|
||||
"beer",
|
||||
"ginger root",
|
||||
"root",
|
||||
"spice"
|
||||
"spice",
|
||||
"yellow",
|
||||
"cooking",
|
||||
"gingerbread"
|
||||
]
|
||||
},
|
||||
"pea-pod": {
|
||||
|
@ -10437,7 +10451,9 @@
|
|||
"legume",
|
||||
"pea",
|
||||
"pod",
|
||||
"vegetable"
|
||||
"vegetable",
|
||||
"cozy",
|
||||
"green"
|
||||
]
|
||||
},
|
||||
"bread": {
|
||||
|
@ -15533,9 +15549,9 @@
|
|||
"dance",
|
||||
"fan",
|
||||
"flutter",
|
||||
"folding hand fan",
|
||||
"hot",
|
||||
"shy"
|
||||
"shy",
|
||||
"flamenco"
|
||||
]
|
||||
},
|
||||
"purse": {
|
||||
|
@ -15719,7 +15735,8 @@
|
|||
"Afro",
|
||||
"comb",
|
||||
"hair",
|
||||
"pick"
|
||||
"pick",
|
||||
"afro"
|
||||
]
|
||||
},
|
||||
"crown": {
|
||||
|
@ -16165,7 +16182,6 @@
|
|||
"b": "1FA87",
|
||||
"j": [
|
||||
"instrument",
|
||||
"maracas",
|
||||
"music",
|
||||
"percussion",
|
||||
"rattle",
|
||||
|
@ -16177,11 +16193,13 @@
|
|||
"b": "1FA88",
|
||||
"j": [
|
||||
"fife",
|
||||
"flute",
|
||||
"music",
|
||||
"pipe",
|
||||
"recorder",
|
||||
"woodwind"
|
||||
"woodwind",
|
||||
"bamboo",
|
||||
"instrument",
|
||||
"pied piper"
|
||||
]
|
||||
},
|
||||
"mobile-phone": {
|
||||
|
@ -19036,9 +19054,9 @@
|
|||
"a": "⊛ Khanda",
|
||||
"b": "1FAAF",
|
||||
"j": [
|
||||
"khanda",
|
||||
"religion",
|
||||
"Sikh"
|
||||
"Sikh",
|
||||
"Sikhism"
|
||||
]
|
||||
},
|
||||
"aries": {
|
||||
|
@ -19460,7 +19478,9 @@
|
|||
"computer",
|
||||
"internet",
|
||||
"network",
|
||||
"wireless"
|
||||
"wifi",
|
||||
"contactless",
|
||||
"signal"
|
||||
]
|
||||
},
|
||||
"vibration-mode": {
|
||||
|
|
|
@ -37,7 +37,7 @@ ext.versionMinor = 5
|
|||
// Note: even values are reserved for regular release, odd values for hotfix release.
|
||||
// When creating a hotfix, you should decrease the value, since the current value
|
||||
// is the value for the next regular release.
|
||||
ext.versionPatch = 30
|
||||
ext.versionPatch = 32
|
||||
|
||||
ext.scVersion = 66
|
||||
|
||||
|
|
|
@ -133,7 +133,7 @@ dependencies {
|
|||
implementation libs.androidx.biometric
|
||||
|
||||
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.moshiKt
|
||||
|
@ -311,7 +311,7 @@ dependencies {
|
|||
// 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)
|
||||
//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.testRunner
|
||||
|
|
|
@ -184,6 +184,7 @@ class HomeActivityViewModel @AssistedInject constructor(
|
|||
if (analyticsConfig.isEnabled) {
|
||||
analyticsStore.didAskUserConsentFlow
|
||||
.onEach { didAskUser ->
|
||||
Timber.v("DidAskUserConsent: $didAskUser")
|
||||
if (!didAskUser) {
|
||||
_viewEvents.post(HomeActivityViewEvents.ShowAnalyticsOptIn)
|
||||
} else {
|
||||
|
|
|
@ -152,7 +152,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.voice.VoiceRecorderFragment
|
||||
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.action.EventSharedAction
|
||||
import im.vector.app.features.home.room.detail.timeline.action.MessageActionsBottomSheet
|
||||
|
@ -172,6 +171,7 @@ 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.TimelineReadMarkerItem
|
||||
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.reply.ReplyPreviewRetriever
|
||||
import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever
|
||||
import im.vector.app.features.home.room.detail.upgrade.MigrateRoomBottomSheet
|
||||
|
|
|
@ -1442,13 +1442,17 @@ class TimelineViewModel @AssistedInject constructor(
|
|||
computeUnreadState(timelineEvents, roomSummary)
|
||||
}
|
||||
// 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.
|
||||
.distinctUntilChanged { previous, current ->
|
||||
when {
|
||||
previous is UnreadState.Unknown || previous is UnreadState.ReadMarkerNotLoaded -> false
|
||||
previous is UnreadState.HasUnread && current is UnreadState.HasUnread &&
|
||||
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
|
||||
else -> false
|
||||
}
|
||||
|
|
|
@ -239,7 +239,7 @@ class AudioMessageHelper @Inject constructor(
|
|||
val percentage = currentPosition.toFloat() / totalDuration
|
||||
playbackTracker.updatePlayingAtPlaybackTime(id, currentPosition, percentage)
|
||||
} else {
|
||||
playbackTracker.stopPlayback(id)
|
||||
playbackTracker.stopPlaybackOrRecorder(id)
|
||||
stopPlaybackTicker()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,6 +60,8 @@ import im.vector.app.features.home.room.detail.timeline.item.MessageInformationD
|
|||
import im.vector.app.features.home.room.detail.timeline.item.ReactionsSummaryEvents
|
||||
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.TypingItem_
|
||||
import im.vector.app.features.home.room.detail.timeline.readreceipts.ReadReceiptsCache
|
||||
import im.vector.app.features.home.room.detail.timeline.reply.ReplyPreviewRetriever
|
||||
import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever
|
||||
import im.vector.app.features.media.AttachmentData
|
||||
|
@ -213,7 +215,7 @@ class TimelineEventController @Inject constructor(
|
|||
// Map eventId to adapter position
|
||||
private val adapterPositionMapping = HashMap<String, Int>()
|
||||
private val timelineEventsGroups = TimelineEventsGroups()
|
||||
private val receiptsByEvent = HashMap<String, MutableList<ReadReceipt>>()
|
||||
private val readReceiptsCache = ReadReceiptsCache()
|
||||
private val modelCache = arrayListOf<CacheItemData?>()
|
||||
private var currentSnapshot: List<TimelineEvent> = emptyList()
|
||||
private var inSubmitList: Boolean = false
|
||||
|
@ -463,7 +465,7 @@ class TimelineEventController @Inject constructor(
|
|||
}
|
||||
Timber.v("Preprocess events took $preprocessEventsTiming ms")
|
||||
var numberOfEventsToBuild = 0
|
||||
val lastSentEventWithoutReadReceipts = searchLastSentEventWithoutReadReceipts(receiptsByEvent)
|
||||
val lastSentEventWithoutReadReceipts = searchLastSentEventWithoutReadReceipts(readReceiptsCache.receiptsByEvent())
|
||||
(0 until modelCache.size).forEach { position ->
|
||||
val event = currentSnapshot[position]
|
||||
val nextEvent = currentSnapshot.nextOrNull(position)
|
||||
|
@ -509,7 +511,7 @@ class TimelineEventController @Inject constructor(
|
|||
}
|
||||
val itemCachedData = modelCache[position] ?: return@forEach
|
||||
// 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")
|
||||
}
|
||||
|
@ -598,15 +600,15 @@ class TimelineEventController @Inject constructor(
|
|||
}
|
||||
|
||||
private fun preprocessReverseEvents() {
|
||||
receiptsByEvent.clear()
|
||||
readReceiptsCache.clear()
|
||||
timelineEventsGroups.clear()
|
||||
val itr = currentSnapshot.listIterator(currentSnapshot.size)
|
||||
var lastShownEventId: String? = null
|
||||
while (itr.hasPrevious()) {
|
||||
val event = itr.previous()
|
||||
timelineEventsGroups.addOrIgnore(event)
|
||||
val currentReadReceipts = ArrayList(event.readReceipts).filter {
|
||||
it.roomMember.userId != session.myUserId && it.isVisibleInThisThread()
|
||||
val currentReadReceipts = event.readReceipts.filter {
|
||||
it.roomMember.userId != session.myUserId
|
||||
}
|
||||
if (timelineEventVisibilityHelper.shouldShowEvent(
|
||||
timelineEvent = event,
|
||||
|
@ -619,18 +621,7 @@ class TimelineEventController @Inject constructor(
|
|||
if (lastShownEventId == null) {
|
||||
continue
|
||||
}
|
||||
val existingReceipts = receiptsByEvent.getOrPut(lastShownEventId) { ArrayList() }
|
||||
existingReceipts.addAll(currentReadReceipts)
|
||||
}
|
||||
}
|
||||
|
||||
private fun ReadReceipt.isVisibleInThisThread(): Boolean {
|
||||
return if (partialState.isFromThreadTimeline()) {
|
||||
this.threadId == partialState.rootThreadEventId
|
||||
} else if (DbgUtil.isDbgEnabled(DbgUtil.DBG_SHOW_DUPLICATE_READ_RECEIPTS)) {
|
||||
this.threadId in listOf(null, ReadService.THREAD_ID_MAIN, ReadService.THREAD_ID_MAIN_OR_NULL)
|
||||
} else {
|
||||
this.threadId == ReadService.THREAD_ID_MAIN_OR_NULL
|
||||
readReceiptsCache.addReceiptsOnEvent(currentReadReceipts, lastShownEventId)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -100,7 +100,7 @@ class AudioMessagePlaybackTracker @Inject constructor() {
|
|||
}
|
||||
}
|
||||
|
||||
fun stopPlayback(id: String) {
|
||||
fun stopPlaybackOrRecorder(id: String) {
|
||||
val state = getPlaybackState(id)
|
||||
if (state !is Listener.State.Error) {
|
||||
setState(id, Listener.State.Idle)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
* 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.
|
||||
|
@ -14,7 +14,7 @@
|
|||
* 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.TextView
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
* 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.
|
||||
|
@ -14,7 +14,7 @@
|
|||
* 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.Parcelable
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
* 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.
|
||||
|
@ -14,7 +14,7 @@
|
|||
* 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 de.spiritcroc.matrixsdk.util.DbgUtil
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -29,7 +29,7 @@ import im.vector.app.core.di.ActiveSessionHolder
|
|||
import im.vector.app.core.glide.GlideApp
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
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.getUserOrDefault
|
||||
import org.matrix.android.sdk.api.session.permalinks.PermalinkData
|
||||
|
@ -99,7 +99,9 @@ class EventTextRenderer @AssistedInject constructor(
|
|||
private fun addPermalinksSpans(text: Spannable) {
|
||||
for (match in Patterns.WEB_URL.toRegex().findAll(text)) {
|
||||
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)) {
|
||||
is PermalinkData.UserLink -> permalinkData.toMatrixItem()
|
||||
is PermalinkData.RoomLink -> permalinkData.toMatrixItem()
|
||||
|
|
|
@ -22,11 +22,13 @@ import android.text.Spanned
|
|||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.di.ActiveSessionHolder
|
||||
import im.vector.app.core.glide.GlideApp
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import io.noties.markwon.core.spans.LinkSpan
|
||||
import io.noties.markwon.image.AsyncDrawableSpan
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import org.matrix.android.sdk.api.session.getRoomSummary
|
||||
import org.matrix.android.sdk.api.session.getUser
|
||||
import org.matrix.android.sdk.api.session.permalinks.PermalinkData
|
||||
|
@ -106,12 +108,18 @@ class PillsPostProcessor @AssistedInject constructor(
|
|||
PillImageSpan(GlideApp.with(context), avatarRenderer, context, matrixItem)
|
||||
|
||||
private fun LinkSpan.createPillSpan(): PillImageSpan? {
|
||||
val supportedHosts = context.resources.getStringArray(R.array.permalink_supported_hosts)
|
||||
val isPermalinkSupported = sessionHolder.getSafeActiveSession()?.permalinkService()?.isPermalinkSupported(supportedHosts, url).orFalse()
|
||||
if (isPermalinkSupported) {
|
||||
val matrixItem = when (val permalinkData = PermalinkParser.parse(url)) {
|
||||
is PermalinkData.UserLink -> permalinkData.toMatrixItem()
|
||||
is PermalinkData.RoomLink -> permalinkData.toMatrixItem()
|
||||
else -> null
|
||||
} ?: return null
|
||||
return createPillImageSpan(matrixItem)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private fun PermalinkData.UserLink.toMatrixItem(): MatrixItem? =
|
||||
|
|
|
@ -18,7 +18,6 @@ package im.vector.app.features.permalink
|
|||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.core.net.toUri
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import im.vector.app.R
|
||||
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.permalinks.PermalinkData
|
||||
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.model.Membership
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
|
@ -70,10 +68,11 @@ class PermalinkHandler @Inject constructor(
|
|||
buildTask: Boolean = false,
|
||||
openAnonymously: Boolean = false
|
||||
): Boolean {
|
||||
val supportedHosts = fragmentActivity.resources.getStringArray(R.array.permalink_supported_hosts)
|
||||
return when {
|
||||
deepLink == null -> false
|
||||
deepLink.isIgnored() -> true
|
||||
!isPermalinkSupported(fragmentActivity, deepLink.toString()) -> false
|
||||
!activeSessionHolder.getSafeActiveSession()?.permalinkService()?.isPermalinkSupported(supportedHosts, deepLink.toString()).orFalse() -> false
|
||||
else -> {
|
||||
tryOrNull {
|
||||
withContext(Dispatchers.Default) {
|
||||
|
@ -172,12 +171,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? {
|
||||
val session = activeSessionHolder.getSafeActiveSession()
|
||||
return if (isRoomAlias && session != null) {
|
||||
|
|
|
@ -527,7 +527,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor(
|
|||
State.Idle -> {
|
||||
// restart the playback time if player completed with less than 1s remaining time
|
||||
if (percentage == null || (playlist.duration - position) < 1000) {
|
||||
playbackTracker.stopPlayback(id)
|
||||
playbackTracker.stopPlaybackOrRecorder(id)
|
||||
} else {
|
||||
playbackTracker.updatePausedAtPlaybackTime(id, position, percentage)
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ import android.content.Context
|
|||
import androidx.core.content.FileProvider
|
||||
import im.vector.app.core.resources.BuildMeta
|
||||
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.voicebroadcast.VoiceBroadcastConstants
|
||||
import im.vector.app.features.voicebroadcast.VoiceBroadcastFailure
|
||||
|
@ -54,6 +55,7 @@ import javax.inject.Inject
|
|||
class StartVoiceBroadcastUseCase @Inject constructor(
|
||||
private val session: Session,
|
||||
private val voiceBroadcastRecorder: VoiceBroadcastRecorder?,
|
||||
private val playbackTracker: AudioMessagePlaybackTracker,
|
||||
private val context: Context,
|
||||
private val buildMeta: BuildMeta,
|
||||
private val getRoomLiveVoiceBroadcastsUseCase: GetRoomLiveVoiceBroadcastsUseCase,
|
||||
|
@ -106,10 +108,14 @@ class StartVoiceBroadcastUseCase @Inject constructor(
|
|||
}
|
||||
|
||||
override fun onStateUpdated(state: VoiceBroadcastRecorder.State) {
|
||||
if (state == VoiceBroadcastRecorder.State.Error) {
|
||||
session.coroutineScope.launch {
|
||||
pauseVoiceBroadcastUseCase.execute(room.roomId)
|
||||
when (state) {
|
||||
VoiceBroadcastRecorder.State.Recording -> playbackTracker.updateCurrentRecording(AudioMessagePlaybackTracker.RECORDING_ID, emptyList())
|
||||
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
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -47,8 +47,7 @@ class GetVoiceBroadcastStateEventLiveUseCase @Inject constructor(
|
|||
) {
|
||||
|
||||
fun execute(voiceBroadcast: VoiceBroadcast): Flow<Optional<VoiceBroadcastEvent>> {
|
||||
val room = session.getRoom(voiceBroadcast.roomId) ?: error("Unknown roomId: ${voiceBroadcast.roomId}")
|
||||
return getMostRecentVoiceBroadcastEventFlow(room, voiceBroadcast)
|
||||
return getMostRecentVoiceBroadcastEventFlow(voiceBroadcast)
|
||||
.onEach { event ->
|
||||
Timber.d(
|
||||
"## VoiceBroadcast | " +
|
||||
|
@ -61,7 +60,8 @@ class GetVoiceBroadcastStateEventLiveUseCase @Inject constructor(
|
|||
/**
|
||||
* 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)
|
||||
// observe started event changes
|
||||
return startedEventFlow
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -57,6 +57,7 @@ class StartVoiceBroadcastUseCaseTest {
|
|||
StartVoiceBroadcastUseCase(
|
||||
session = fakeSession,
|
||||
voiceBroadcastRecorder = fakeVoiceBroadcastRecorder,
|
||||
playbackTracker = mockk(),
|
||||
context = FakeContext().instance,
|
||||
buildMeta = mockk(),
|
||||
getRoomLiveVoiceBroadcastsUseCase = fakeGetRoomLiveVoiceBroadcastsUseCase,
|
||||
|
|
Loading…
Reference in New Issue