Merge branch 'develop' into feature/bca/rust_flavor
This commit is contained in:
commit
c0397875f0
|
@ -11,7 +11,7 @@ jobs:
|
|||
- run: |
|
||||
npm install --save-dev @babel/plugin-transform-flow-strip-types
|
||||
- name: Danger
|
||||
uses: danger/danger-js@11.2.0
|
||||
uses: danger/danger-js@11.2.1
|
||||
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.0
|
||||
uses: danger/danger-js@11.2.1
|
||||
with:
|
||||
args: "--dangerfile ./tools/danger/dangerfile-lint.js"
|
||||
env:
|
||||
|
|
27
CHANGES.md
27
CHANGES.md
|
@ -1,3 +1,30 @@
|
|||
Changes in Element v1.5.20 (2023-01-10)
|
||||
=======================================
|
||||
|
||||
Features ✨
|
||||
----------
|
||||
- "[Rich text editor] Add list formatting buttons to the rich text editor" ([#7887](https://github.com/vector-im/element-android/issues/7887))
|
||||
|
||||
Bugfixes 🐛
|
||||
----------
|
||||
- ReplyTo are not updated if the original message is edited or deleted. ([#5546](https://github.com/vector-im/element-android/issues/5546))
|
||||
- Observe ViewEvents only when resumed and ensure ViewEvents are not lost. ([#7724](https://github.com/vector-im/element-android/issues/7724))
|
||||
- [Session manager] Missing info when a session does not support encryption ([#7853](https://github.com/vector-im/element-android/issues/7853))
|
||||
- Reduce number of crypto database transactions when handling the sync response ([#7879](https://github.com/vector-im/element-android/issues/7879))
|
||||
- [Voice Broadcast] Stop listening if we reach the last received chunk and there is no last sequence number ([#7899](https://github.com/vector-im/element-android/issues/7899))
|
||||
- Handle network error on API `rooms/{roomId}/threads` ([#7913](https://github.com/vector-im/element-android/issues/7913))
|
||||
|
||||
In development 🚧
|
||||
----------------
|
||||
- [Poll] Render active polls list of a room
|
||||
- [Poll] Render past polls list of a room ([#7864](https://github.com/vector-im/element-android/issues/7864))
|
||||
|
||||
Other changes
|
||||
-------------
|
||||
- fix: increase font size for messages ([#5717](https://github.com/vector-im/element-android/issues/5717))
|
||||
- Add trim to username input on the app side and SDK side when sign-in ([#7111](https://github.com/vector-im/element-android/issues/7111))
|
||||
|
||||
|
||||
Changes in Element v1.5.18 (2023-01-02)
|
||||
=======================================
|
||||
|
||||
|
|
|
@ -127,7 +127,8 @@ GEM
|
|||
xcpretty (~> 0.3.0)
|
||||
xcpretty-travis-formatter (>= 0.0.3)
|
||||
gh_inspector (1.1.3)
|
||||
git (1.11.0)
|
||||
git (1.13.0)
|
||||
addressable (~> 2.8)
|
||||
rchardet (~> 1.8)
|
||||
google-apis-androidpublisher_v3 (0.25.0)
|
||||
google-apis-core (>= 0.7, < 2.a)
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
# Reporting a Vulnerability
|
||||
|
||||
**If you've found a security vulnerability, please report it to security@matrix.org**
|
||||
|
||||
For more information on our security disclosure policy, visit https://www.matrix.org/security-disclosure-policy/
|
|
@ -27,8 +27,8 @@ buildscript {
|
|||
classpath 'com.google.firebase:firebase-appdistribution-gradle:3.1.1'
|
||||
classpath 'com.google.gms:google-services:4.3.14'
|
||||
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.likethesalad.android:stem-plugin:2.2.3"
|
||||
classpath 'com.google.android.gms:oss-licenses-plugin:0.10.6'
|
||||
classpath "com.likethesalad.android:stem-plugin:2.3.0"
|
||||
classpath 'org.owasp:dependency-check-gradle:7.4.4'
|
||||
classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.7.20"
|
||||
classpath "org.jetbrains.kotlinx:kotlinx-knit:0.4.0"
|
||||
|
@ -45,10 +45,10 @@ plugins {
|
|||
// Detekt
|
||||
id "io.gitlab.arturbosch.detekt" version "1.22.0"
|
||||
// Ksp
|
||||
id "com.google.devtools.ksp" version "1.7.22-1.0.8"
|
||||
id "com.google.devtools.ksp" version "1.8.0-1.0.8"
|
||||
|
||||
// Dependency Analysis
|
||||
id 'com.autonomousapps.dependency-analysis' version "1.17.0"
|
||||
id 'com.autonomousapps.dependency-analysis' version "1.18.0"
|
||||
// Gradle doctor
|
||||
id "com.osacky.doctor" version "0.8.1"
|
||||
}
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
ReplyTo are not updated if the original message is edited or deleted.
|
|
@ -1 +0,0 @@
|
|||
Observe ViewEvents only when resumed and ensure ViewEvents are not lost.
|
|
@ -0,0 +1 @@
|
|||
[Voice Broadcast] Fix unexpected "live broadcast" in the room list
|
|
@ -1 +0,0 @@
|
|||
[Session manager] Missing info when a session does not support encryption
|
|
@ -1,2 +0,0 @@
|
|||
[Poll] Render active polls list of a room
|
||||
[Poll] Render past polls list of a room
|
|
@ -1 +0,0 @@
|
|||
Reduce number of crypto database transactions when handling the sync response
|
|
@ -1 +0,0 @@
|
|||
"[Rich text editor] Add list formatting buttons to the rich text editor"
|
|
@ -1 +0,0 @@
|
|||
[Voice Broadcast] Stop listening if we reach the last received chunk and there is no last sequence number
|
|
@ -0,0 +1 @@
|
|||
Render ended polls
|
|
@ -1 +0,0 @@
|
|||
Handle network error on API `rooms/{roomId}/threads`
|
|
@ -0,0 +1 @@
|
|||
"[Rich text editor] Update list item bullet appearance"
|
|
@ -0,0 +1 @@
|
|||
Upgrade to Kotlin 1.8
|
|
@ -80,12 +80,12 @@ task generateCoverageReport(type: JacocoReport) {
|
|||
|
||||
task unitTestsWithCoverage(type: GradleBuild) {
|
||||
// the 7.1.3 android gradle plugin has a bug where enableTestCoverage generates invalid coverage
|
||||
startParameter.projectProperties.coverage = [enableTestCoverage: false]
|
||||
startParameter.projectProperties.coverage = "false"
|
||||
tasks = ['testDebugUnitTest']
|
||||
}
|
||||
|
||||
task instrumentationTestsWithCoverage(type: GradleBuild) {
|
||||
startParameter.projectProperties.coverage = [enableTestCoverage: true]
|
||||
startParameter.projectProperties.coverage = "true"
|
||||
startParameter.projectProperties['android.testInstrumentationRunnerArguments.notPackage'] = 'im.vector.app.ui'
|
||||
tasks = [':vector-app:connectedGplayKotlinCryptoDebugAndroidTest', ':vector:connectedKotlinCryptoDebugAndroidTest', 'matrix-sdk-android:connectedKotlinCryptoDebugAndroidTest']
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ ext.versions = [
|
|||
|
||||
def gradle = "7.3.1"
|
||||
// Ref: https://kotlinlang.org/releases.html
|
||||
def kotlin = "1.7.22"
|
||||
def kotlin = "1.8.0"
|
||||
def kotlinCoroutines = "1.6.4"
|
||||
def dagger = "2.44.2"
|
||||
def firebaseBom = "31.1.1"
|
||||
|
@ -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.176.0"
|
||||
def flipper = "0.176.1"
|
||||
def epoxy = "5.0.0"
|
||||
def mavericks = "3.0.1"
|
||||
def glide = "4.14.2"
|
||||
|
@ -27,12 +27,13 @@ def jjwt = "0.11.5"
|
|||
// 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
|
||||
def vanniktechEmoji = "0.16.0-SNAPSHOT"
|
||||
def sentry = "6.9.2"
|
||||
def fragment = "1.5.5"
|
||||
def sentry = "6.11.0"
|
||||
// Use 1.6.0 alpha to fix issue with test
|
||||
def fragment = "1.6.0-alpha04"
|
||||
// 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.4.0"
|
||||
def androidxTest = "1.4.0"
|
||||
def espresso = "3.5.1"
|
||||
def androidxTest = "1.5.0"
|
||||
def androidxOrchestrator = "1.4.2"
|
||||
def paparazzi = "1.1.0"
|
||||
|
||||
|
@ -56,11 +57,12 @@ ext.libs = [
|
|||
'exifinterface' : "androidx.exifinterface:exifinterface:1.3.5",
|
||||
'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.7.1",
|
||||
'autoFill' : "androidx.autofill:autofill:1.1.0",
|
||||
'preferenceKtx' : "androidx.preference:preference-ktx:1.2.0",
|
||||
'junit' : "androidx.test.ext:junit:1.1.3",
|
||||
'junit' : "androidx.test.ext:junit:1.1.5",
|
||||
'lifecycleCommon' : "androidx.lifecycle:lifecycle-common:$lifecycle",
|
||||
'lifecycleLivedata' : "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle",
|
||||
'lifecycleProcess' : "androidx.lifecycle:lifecycle-process:$lifecycle",
|
||||
|
@ -86,7 +88,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.3"
|
||||
'phonenumber' : "com.googlecode.libphonenumber:libphonenumber:8.13.4"
|
||||
],
|
||||
dagger : [
|
||||
'dagger' : "com.google.dagger:dagger:$dagger",
|
||||
|
@ -101,7 +103,7 @@ ext.libs = [
|
|||
],
|
||||
element : [
|
||||
'opusencoder' : "io.element.android:opusencoder:1.1.0",
|
||||
'wysiwyg' : "io.element.android:wysiwyg:0.14.0"
|
||||
'wysiwyg' : "io.element.android:wysiwyg:0.15.0"
|
||||
],
|
||||
squareup : [
|
||||
'moshi' : "com.squareup.moshi:moshi:$moshi",
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
Main changes in this version: Mainly bugfixing!
|
||||
Full changelog: https://github.com/vector-im/element-android/releases
|
|
@ -3187,7 +3187,8 @@
|
|||
<item quantity="other">Final result based on %1$d votes</item>
|
||||
</plurals>
|
||||
<string name="poll_end_action">End poll</string>
|
||||
<string name="a11y_poll_winner_option">winner option</string>
|
||||
<!-- TODO TO BE REMOVED -->
|
||||
<string name="a11y_poll_winner_option" tools:ignore="UnusedResources">winner option</string>
|
||||
<string name="end_poll_confirmation_title">End this poll?</string>
|
||||
<string name="end_poll_confirmation_description">This will stop people from being able to vote and will display the final results of the poll.</string>
|
||||
<string name="end_poll_confirmation_approve_button">End poll</string>
|
||||
|
@ -3201,6 +3202,7 @@
|
|||
<string name="open_poll_option_description">Voters see results as soon as they have voted</string>
|
||||
<string name="closed_poll_option_title">Closed poll</string>
|
||||
<string name="closed_poll_option_description">Results are only revealed when you end the poll</string>
|
||||
<string name="ended_poll_indicator">Ended the poll.</string>
|
||||
<string name="room_polls_active">Active polls</string>
|
||||
<string name="room_polls_active_no_item">There are no active polls in this room</string>
|
||||
<string name="room_polls_ended">Past polls</string>
|
||||
|
@ -3518,6 +3520,9 @@
|
|||
<string name="message_reply_to_sender_sent_video">sent a video.</string>
|
||||
<string name="message_reply_to_sender_sent_sticker">sent a sticker.</string>
|
||||
<string name="message_reply_to_sender_created_poll">created a poll.</string>
|
||||
<string name="message_reply_to_sender_ended_poll">ended a poll.</string>
|
||||
<string name="message_reply_to_poll_preview">Poll</string>
|
||||
<string name="message_reply_to_ended_poll_preview">Ended poll</string>
|
||||
|
||||
<string name="settings_access_token">Access Token</string>
|
||||
<string name="settings_access_token_summary">Your access token gives full access to your account. Do not share it with anyone.</string>
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
<item name="android:clipToPadding">false</item>
|
||||
<item name="android:textSize">15sp</item>
|
||||
<item name="android:textColor">?vctr_message_text_color</item>
|
||||
<item name="lineHeight">20sp</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -63,7 +63,7 @@ android {
|
|||
// that the app's state is completely cleared between tests.
|
||||
testInstrumentationRunnerArguments clearPackageData: 'true'
|
||||
|
||||
buildConfigField "String", "SDK_VERSION", "\"1.5.20\""
|
||||
buildConfigField "String", "SDK_VERSION", "\"1.5.22\""
|
||||
|
||||
buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\""
|
||||
buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\""
|
||||
|
@ -82,7 +82,7 @@ android {
|
|||
buildTypes {
|
||||
debug {
|
||||
if (project.hasProperty("coverage")) {
|
||||
testCoverageEnabled = coverage.enableTestCoverage
|
||||
testCoverageEnabled = coverage == "true"
|
||||
}
|
||||
// Set to true to log privacy or sensible data, such as token
|
||||
buildConfigField "boolean", "LOG_PRIVATE_DATA", project.property("vector.debugPrivateData")
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="org.matrix.android.sdk">
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<application>
|
||||
|
||||
|
|
|
@ -248,7 +248,7 @@ data class Event(
|
|||
if (isRedacted()) return "Message removed"
|
||||
val text = getDecryptedValue() ?: run {
|
||||
if (isPoll()) {
|
||||
return getPollQuestion() ?: "created a poll."
|
||||
return getTextSummaryForPoll()
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
@ -261,13 +261,23 @@ data class Event(
|
|||
isImageMessage() -> "sent an image."
|
||||
isVideoMessage() -> "sent a video."
|
||||
isSticker() -> "sent a sticker."
|
||||
isPoll() -> getPollQuestion() ?: "created a poll."
|
||||
isPoll() -> getTextSummaryForPoll()
|
||||
isLiveLocation() -> "Live location."
|
||||
isLocationMessage() -> "has shared their location."
|
||||
else -> text
|
||||
}
|
||||
}
|
||||
|
||||
private fun getTextSummaryForPoll(): String? {
|
||||
val pollQuestion = getPollQuestion()
|
||||
return when {
|
||||
pollQuestion != null -> pollQuestion
|
||||
isPollStart() -> "created a poll."
|
||||
isPollEnd() -> "ended a poll."
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun Event.isQuote(): Boolean {
|
||||
if (isReplyRenderedInThread()) return false
|
||||
return getDecryptedValue("formatted_body")?.contains("<blockquote>") ?: false
|
||||
|
|
|
@ -18,6 +18,7 @@ package org.matrix.android.sdk.api.session.room.model.message
|
|||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import org.matrix.android.sdk.api.session.events.model.Content
|
||||
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
|
||||
|
||||
/**
|
||||
|
@ -25,5 +26,12 @@ import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultCon
|
|||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class MessageEndPollContent(
|
||||
@Json(name = "m.relates_to") val relatesTo: RelationDefaultContent? = null
|
||||
)
|
||||
/**
|
||||
* Local message type, not from server.
|
||||
*/
|
||||
@Transient
|
||||
override val msgType: String = MessageType.MSGTYPE_POLL_END,
|
||||
@Json(name = "body") override val body: String = "",
|
||||
@Json(name = "m.new_content") override val newContent: Content? = null,
|
||||
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null
|
||||
) : MessageContent
|
||||
|
|
|
@ -38,6 +38,7 @@ object MessageType {
|
|||
// Because poll events are not message events and they don't have msgtype field
|
||||
const val MSGTYPE_POLL_START = "org.matrix.android.sdk.poll.start"
|
||||
const val MSGTYPE_POLL_RESPONSE = "org.matrix.android.sdk.poll.response"
|
||||
const val MSGTYPE_POLL_END = "org.matrix.android.sdk.poll.end"
|
||||
|
||||
const val MSGTYPE_CONFETTI = "nic.custom.confetti"
|
||||
const val MSGTYPE_SNOWFALL = "io.element.effect.snowfall"
|
||||
|
|
|
@ -35,6 +35,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoCo
|
|||
import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageContentWithFormattedBody
|
||||
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.MessageStickerContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
|
||||
|
@ -148,6 +149,7 @@ fun TimelineEvent.getLastMessageContent(): MessageContent? {
|
|||
// so toModel<MessageContent> won't parse them correctly
|
||||
// It's discriminated on event type instead. Maybe it shouldn't be MessageContent at all to avoid confusion?
|
||||
in EventType.POLL_START.values -> (getLastEditNewContent() ?: root.getClearContent()).toModel<MessagePollContent>()
|
||||
in EventType.POLL_END.values -> (getLastEditNewContent() ?: root.getClearContent()).toModel<MessageEndPollContent>()
|
||||
in EventType.STATE_ROOM_BEACON_INFO.values -> (getLastEditNewContent() ?: root.getClearContent()).toModel<MessageBeaconInfoContent>()
|
||||
in EventType.BEACON_LOCATION_DATA.values -> (getLastEditNewContent() ?: root.getClearContent()).toModel<MessageBeaconLocationDataContent>()
|
||||
else -> (getLastEditNewContent() ?: root.getClearContent()).toModel()
|
||||
|
|
|
@ -69,7 +69,7 @@ internal class DefaultLoginWizard(
|
|||
)
|
||||
} else {
|
||||
PasswordLoginParams.userIdentifier(
|
||||
user = login,
|
||||
user = login.trim(),
|
||||
password = password,
|
||||
deviceDisplayName = initialDeviceName,
|
||||
deviceId = deviceId
|
||||
|
|
|
@ -30,10 +30,4 @@ internal data class GetPushRulesResponse(
|
|||
*/
|
||||
@Json(name = "global")
|
||||
val global: RuleSet,
|
||||
|
||||
/**
|
||||
* Device specific rules, apply only to current device.
|
||||
*/
|
||||
@Json(name = "device")
|
||||
val device: RuleSet? = null
|
||||
)
|
||||
|
|
|
@ -42,7 +42,6 @@ internal class DefaultSavePushRulesTask @Inject constructor(@SessionDatabase pri
|
|||
.findAll()
|
||||
.forEach { it.deleteOnCascade() }
|
||||
|
||||
// Save only global rules for the moment
|
||||
val globalRules = params.pushRules.global
|
||||
|
||||
val content = PushRulesEntity(RuleScope.GLOBAL).apply { kind = RuleSetKey.CONTENT }
|
||||
|
|
|
@ -359,9 +359,9 @@ adb -d install ${apkPath}
|
|||
read -p "Please run the APK on your phone to check that the upgrade went well (no init sync, etc.). Press enter when it's done."
|
||||
|
||||
printf "\n================================================================================\n"
|
||||
githubCreateReleaseLink="https://github.com/vector-im/element-android/releases/new?tag=v${version}&title=Element%%20Android%%20v${version}&body=${changelogUrlEncoded}"
|
||||
githubCreateReleaseLink="https://github.com/vector-im/element-android/releases/new?tag=v${version}&title=Element%20Android%20v${version}&body=${changelogUrlEncoded}"
|
||||
printf "Creating the release on gitHub.\n"
|
||||
printf "Open this link: ${githubCreateReleaseLink}\n"
|
||||
printf -- "Open this link: %s\n" ${githubCreateReleaseLink}
|
||||
printf "Then\n"
|
||||
printf " - click on the 'Generate releases notes' button\n"
|
||||
printf " - Add the 4 signed APKs to the GitHub release. They are located at ${targetPath}\n"
|
||||
|
@ -369,7 +369,7 @@ read -p ". Press enter when it's done. "
|
|||
|
||||
printf "\n================================================================================\n"
|
||||
printf "Message for the Android internal room:\n\n"
|
||||
message="@room Element Android ${version} is ready to be tested. You can get if from https://github.com/vector-im/element-android/releases/tag/v${version}. Please report any feedback here. Thanks!"
|
||||
message="@room Element Android ${version} is ready to be tested. You can get it from https://github.com/vector-im/element-android/releases/tag/v${version}. Please report any feedback here. Thanks!"
|
||||
printf "${message}\n\n"
|
||||
|
||||
if [[ -z "${elementBotToken}" ]]; then
|
||||
|
|
|
@ -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 = 20
|
||||
ext.versionPatch = 22
|
||||
|
||||
static def getGitTimestamp() {
|
||||
def cmd = 'git show -s --format=%ct'
|
||||
|
@ -251,7 +251,7 @@ android {
|
|||
signingConfig signingConfigs.debug
|
||||
|
||||
if (project.hasProperty("coverage")) {
|
||||
testCoverageEnabled = coverage.enableTestCoverage
|
||||
testCoverageEnabled = coverage == "true"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -448,7 +448,7 @@ dependencies {
|
|||
androidTestImplementation libs.mockk.mockkAndroid
|
||||
androidTestUtil libs.androidx.orchestrator
|
||||
androidTestImplementation libs.androidx.fragmentTesting
|
||||
androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:1.7.22"
|
||||
debugImplementation libs.androidx.fragmentTesting
|
||||
androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:1.8.0"
|
||||
debugImplementation libs.androidx.fragmentTestingManifest
|
||||
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.10'
|
||||
}
|
||||
|
|
|
@ -69,7 +69,7 @@ android {
|
|||
buildTypes {
|
||||
debug {
|
||||
if (project.hasProperty("coverage")) {
|
||||
testCoverageEnabled = coverage.enableTestCoverage
|
||||
testCoverageEnabled = coverage == "true"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -135,7 +135,7 @@ dependencies {
|
|||
implementation libs.androidx.biometric
|
||||
|
||||
api "org.threeten:threetenbp:1.4.0:no-tzdb"
|
||||
api "com.gabrielittner.threetenbp:lazythreetenbp:0.12.0"
|
||||
api "com.gabrielittner.threetenbp:lazythreetenbp:0.13.0"
|
||||
|
||||
implementation libs.squareup.moshi
|
||||
kapt libs.squareup.moshiKotlin
|
||||
|
@ -333,6 +333,7 @@ dependencies {
|
|||
}
|
||||
androidTestImplementation libs.mockk.mockkAndroid
|
||||
androidTestUtil libs.androidx.orchestrator
|
||||
debugImplementation libs.androidx.fragmentTesting
|
||||
androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:1.7.22"
|
||||
debugImplementation libs.androidx.fragmentTestingManifest
|
||||
androidTestImplementation libs.androidx.fragmentTesting
|
||||
androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:1.8.0"
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
|
|||
|
||||
fun TimelineEvent.canReact(): Boolean {
|
||||
// Only event of type EventType.MESSAGE, EventType.STICKER and EventType.POLL_START are supported for the moment
|
||||
return root.getClearType() in listOf(EventType.MESSAGE, EventType.STICKER) + EventType.POLL_START.values &&
|
||||
return root.getClearType() in listOf(EventType.MESSAGE, EventType.STICKER) + EventType.POLL_START.values + EventType.POLL_END.values &&
|
||||
root.sendState == SendState.SYNCED &&
|
||||
!root.isRedacted()
|
||||
}
|
||||
|
|
|
@ -16,16 +16,18 @@
|
|||
|
||||
package im.vector.app.core.utils
|
||||
|
||||
import im.vector.app.core.platform.VectorViewEvents
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.transform
|
||||
import java.util.concurrent.CopyOnWriteArraySet
|
||||
|
||||
interface SharedEvents<out T> {
|
||||
interface SharedEvents<out T : VectorViewEvents> {
|
||||
fun stream(consumerId: String): Flow<T>
|
||||
}
|
||||
|
||||
class EventQueue<T>(capacity: Int) : SharedEvents<T> {
|
||||
class EventQueue<T : VectorViewEvents>(capacity: Int) : SharedEvents<T> {
|
||||
|
||||
private val innerQueue = MutableSharedFlow<OneTimeEvent<T>>(replay = capacity)
|
||||
|
||||
|
@ -33,7 +35,12 @@ class EventQueue<T>(capacity: Int) : SharedEvents<T> {
|
|||
innerQueue.tryEmit(OneTimeEvent(event))
|
||||
}
|
||||
|
||||
override fun stream(consumerId: String): Flow<T> = innerQueue.filterNotHandledBy(consumerId)
|
||||
override fun stream(consumerId: String): Flow<T> = innerQueue
|
||||
.onEach {
|
||||
// Ensure that buffered Events will not be sent again to new subscribers.
|
||||
innerQueue.resetReplayCache()
|
||||
}
|
||||
.filterNotHandledBy(consumerId)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -42,7 +49,7 @@ class EventQueue<T>(capacity: Int) : SharedEvents<T> {
|
|||
*
|
||||
* Keeps track of who has already handled its content.
|
||||
*/
|
||||
private class OneTimeEvent<out T>(private val content: T) {
|
||||
private class OneTimeEvent<out T : VectorViewEvents>(private val content: T) {
|
||||
|
||||
private val handlers = CopyOnWriteArraySet<String>()
|
||||
|
||||
|
@ -53,6 +60,6 @@ private class OneTimeEvent<out T>(private val content: T) {
|
|||
fun getIfNotHandled(asker: String): T? = if (handlers.add(asker)) content else null
|
||||
}
|
||||
|
||||
private fun <T> Flow<OneTimeEvent<T>>.filterNotHandledBy(consumerId: String): Flow<T> = transform { event ->
|
||||
private fun <T : VectorViewEvents> Flow<OneTimeEvent<T>>.filterNotHandledBy(consumerId: String): Flow<T> = transform { event ->
|
||||
event.getIfNotHandled(consumerId)?.let { emit(it) }
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@ import org.commonmark.parser.Parser
|
|||
import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageEndPollContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageFormat
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
|
||||
|
@ -181,6 +182,7 @@ class PlainTextComposerLayout @JvmOverloads constructor(
|
|||
is MessageAudioContent -> getAudioContentBodyText(messageContent)
|
||||
is MessagePollContent -> messageContent.getBestPollCreationInfo()?.question?.getBestQuestion()
|
||||
is MessageBeaconInfoContent -> resources.getString(R.string.live_location_description)
|
||||
is MessageEndPollContent -> resources.getString(R.string.message_reply_to_ended_poll_preview)
|
||||
else -> messageContent?.body.orEmpty()
|
||||
}
|
||||
var formattedBody: CharSequence? = null
|
||||
|
|
|
@ -25,8 +25,14 @@ import javax.inject.Inject
|
|||
class CheckIfCanReplyEventUseCase @Inject constructor() {
|
||||
|
||||
fun execute(event: TimelineEvent, messageContent: MessageContent?, actionPermissions: ActionPermissions): Boolean {
|
||||
// Only EventType.MESSAGE, EventType.POLL_START and EventType.STATE_ROOM_BEACON_INFO event types are supported for the moment
|
||||
if (event.root.getClearType() !in EventType.STATE_ROOM_BEACON_INFO.values + EventType.POLL_START.values + EventType.MESSAGE) return false
|
||||
// Only EventType.MESSAGE, EventType.POLL_START, EventType.POLL_END and EventType.STATE_ROOM_BEACON_INFO event types are supported for the moment
|
||||
if (event.root.getClearType() !in
|
||||
EventType.STATE_ROOM_BEACON_INFO.values +
|
||||
EventType.POLL_START.values +
|
||||
EventType.POLL_END.values +
|
||||
EventType.MESSAGE
|
||||
) return false
|
||||
|
||||
if (!actionPermissions.canSendMessage) return false
|
||||
return when (messageContent?.msgType) {
|
||||
MessageType.MSGTYPE_TEXT,
|
||||
|
@ -37,6 +43,7 @@ class CheckIfCanReplyEventUseCase @Inject constructor() {
|
|||
MessageType.MSGTYPE_AUDIO,
|
||||
MessageType.MSGTYPE_FILE,
|
||||
MessageType.MSGTYPE_POLL_START,
|
||||
MessageType.MSGTYPE_POLL_END,
|
||||
MessageType.MSGTYPE_BEACON_INFO,
|
||||
MessageType.MSGTYPE_LOCATION -> true
|
||||
else -> false
|
||||
|
|
|
@ -499,6 +499,7 @@ class MessageActionsViewModel @AssistedInject constructor(
|
|||
MessageType.MSGTYPE_AUDIO,
|
||||
MessageType.MSGTYPE_FILE,
|
||||
MessageType.MSGTYPE_POLL_START,
|
||||
MessageType.MSGTYPE_POLL_END,
|
||||
MessageType.MSGTYPE_STICKER_LOCAL -> event.root.threadDetails?.isRootThread ?: false
|
||||
else -> false
|
||||
}
|
||||
|
@ -530,8 +531,8 @@ class MessageActionsViewModel @AssistedInject constructor(
|
|||
}
|
||||
|
||||
private fun canViewReactions(event: TimelineEvent): Boolean {
|
||||
// Only event of type EventType.MESSAGE, EventType.STICKER and EventType.POLL_START are supported for the moment
|
||||
if (event.root.getClearType() !in listOf(EventType.MESSAGE, EventType.STICKER) + EventType.POLL_START.values) return false
|
||||
// Only event of type EventType.MESSAGE, EventType.STICKER, EventType.POLL_START, EventType.POLL_END are supported for the moment
|
||||
if (event.root.getClearType() !in listOf(EventType.MESSAGE, EventType.STICKER) + EventType.POLL_START.values + EventType.POLL_END.values) return false
|
||||
return event.annotations?.reactionsSummary?.isNotEmpty() ?: false
|
||||
}
|
||||
|
||||
|
|
|
@ -91,11 +91,13 @@ import org.matrix.android.sdk.api.session.events.model.RelationType
|
|||
import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
|
||||
import org.matrix.android.sdk.api.session.events.model.isThread
|
||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||
import org.matrix.android.sdk.api.session.room.getTimelineEvent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageContentWithFormattedBody
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageEmoteContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageEndPollContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageFileContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageImageInfoContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageLocationContent
|
||||
|
@ -109,8 +111,10 @@ import org.matrix.android.sdk.api.session.room.model.message.asMessageAudioEvent
|
|||
import org.matrix.android.sdk.api.session.room.model.message.getFileUrl
|
||||
import org.matrix.android.sdk.api.session.room.model.message.getThumbnailUrl
|
||||
import org.matrix.android.sdk.api.session.room.model.relation.ReplyToContent
|
||||
import org.matrix.android.sdk.api.session.room.timeline.getRelationContent
|
||||
import org.matrix.android.sdk.api.settings.LightweightSettingsStorage
|
||||
import org.matrix.android.sdk.api.util.MimeTypes
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class MessageItemFactory @Inject constructor(
|
||||
|
@ -202,7 +206,8 @@ class MessageItemFactory @Inject constructor(
|
|||
is MessageFileContent -> buildFileMessageItem(messageContent, highlight, attributes)
|
||||
is MessageAudioContent -> buildAudioContent(params, messageContent, informationData, highlight, attributes)
|
||||
is MessageVerificationRequestContent -> buildVerificationRequestMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||
is MessagePollContent -> buildPollItem(messageContent, informationData, highlight, callback, attributes)
|
||||
is MessagePollContent -> buildPollItem(messageContent, informationData, highlight, callback, attributes, isEnded = false)
|
||||
is MessageEndPollContent -> buildEndedPollItem(event.getRelationContent()?.eventId, informationData, highlight, callback, attributes)
|
||||
is MessageLocationContent -> buildLocationItem(messageContent, informationData, highlight, attributes)
|
||||
is MessageBeaconInfoContent -> liveLocationShareMessageItemFactory.create(event, highlight, attributes)
|
||||
is MessageVoiceBroadcastInfoContent -> voiceBroadcastItemFactory.create(params, messageContent, highlight, attributes)
|
||||
|
@ -245,6 +250,7 @@ class MessageItemFactory @Inject constructor(
|
|||
highlight: Boolean,
|
||||
callback: TimelineEventController.Callback?,
|
||||
attributes: AbsMessageItem.Attributes,
|
||||
isEnded: Boolean,
|
||||
): PollItem {
|
||||
val pollViewState = pollItemViewStateFactory.create(pollContent, informationData)
|
||||
|
||||
|
@ -256,11 +262,35 @@ class MessageItemFactory @Inject constructor(
|
|||
.votesStatus(pollViewState.votesStatus)
|
||||
.optionViewStates(pollViewState.optionViewStates.orEmpty())
|
||||
.edited(informationData.hasBeenEdited)
|
||||
.ended(isEnded)
|
||||
.highlighted(highlight)
|
||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||
.callback(callback)
|
||||
}
|
||||
|
||||
private fun buildEndedPollItem(
|
||||
pollStartEventId: String?,
|
||||
informationData: MessageInformationData,
|
||||
highlight: Boolean,
|
||||
callback: TimelineEventController.Callback?,
|
||||
attributes: AbsMessageItem.Attributes,
|
||||
): PollItem? {
|
||||
pollStartEventId ?: return null.also {
|
||||
Timber.e("### buildEndedPollItem. Cannot render poll end event because poll start event id is null")
|
||||
}
|
||||
val pollStartEvent = session.roomService().getRoom(roomId)?.getTimelineEvent(pollStartEventId)
|
||||
val pollContent = pollStartEvent?.root?.getClearContent()?.toModel<MessagePollContent>() ?: return null
|
||||
|
||||
return buildPollItem(
|
||||
pollContent,
|
||||
informationData,
|
||||
highlight,
|
||||
callback,
|
||||
attributes,
|
||||
isEnded = true
|
||||
)
|
||||
}
|
||||
|
||||
private fun createPollQuestion(
|
||||
informationData: MessageInformationData,
|
||||
question: String,
|
||||
|
|
|
@ -102,6 +102,7 @@ class TimelineItemFactory @Inject constructor(
|
|||
// Message itemsX
|
||||
EventType.STICKER,
|
||||
in EventType.POLL_START.values,
|
||||
in EventType.POLL_END.values,
|
||||
EventType.MESSAGE -> messageItemFactory.create(params)
|
||||
EventType.REDACTION,
|
||||
EventType.KEY_VERIFICATION_ACCEPT,
|
||||
|
@ -114,8 +115,7 @@ class TimelineItemFactory @Inject constructor(
|
|||
EventType.CALL_SELECT_ANSWER,
|
||||
EventType.CALL_NEGOTIATE,
|
||||
EventType.REACTION,
|
||||
in EventType.POLL_RESPONSE.values,
|
||||
in EventType.POLL_END.values -> noticeItemFactory.create(params)
|
||||
in EventType.POLL_RESPONSE.values -> noticeItemFactory.create(params)
|
||||
in EventType.BEACON_LOCATION_DATA.values -> {
|
||||
if (event.root.isRedacted()) {
|
||||
messageItemFactory.create(params)
|
||||
|
|
|
@ -17,11 +17,14 @@
|
|||
package im.vector.app.features.home.room.detail.timeline.format
|
||||
|
||||
import android.content.Context
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.utils.TextUtils
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import org.matrix.android.sdk.api.session.events.model.isAudioMessage
|
||||
import org.matrix.android.sdk.api.session.events.model.isFileMessage
|
||||
import org.matrix.android.sdk.api.session.events.model.isImageMessage
|
||||
import org.matrix.android.sdk.api.session.events.model.isPollEnd
|
||||
import org.matrix.android.sdk.api.session.events.model.isPollStart
|
||||
import org.matrix.android.sdk.api.session.events.model.isVideoMessage
|
||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent
|
||||
|
@ -51,10 +54,16 @@ class EventDetailsFormatter @Inject constructor(
|
|||
event.isVideoMessage() -> formatForVideoMessage(event)
|
||||
event.isAudioMessage() -> formatForAudioMessage(event)
|
||||
event.isFileMessage() -> formatForFileMessage(event)
|
||||
event.isPollStart() -> formatPollMessage()
|
||||
event.isPollEnd() -> formatPollEndMessage()
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatPollMessage() = context.getString(R.string.message_reply_to_poll_preview)
|
||||
|
||||
private fun formatPollEndMessage() = context.getString(R.string.message_reply_to_ended_poll_preview)
|
||||
|
||||
/**
|
||||
* Example: "1024 x 720 - 670 kB".
|
||||
*/
|
||||
|
|
|
@ -23,8 +23,6 @@ import im.vector.app.core.extensions.localDateTime
|
|||
import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFactoryParams
|
||||
import im.vector.app.features.home.room.detail.timeline.item.E2EDecoration
|
||||
import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData
|
||||
import im.vector.app.features.home.room.detail.timeline.item.PollResponseData
|
||||
import im.vector.app.features.home.room.detail.timeline.item.PollVoteSummaryData
|
||||
import im.vector.app.features.home.room.detail.timeline.item.ReferencesInfoData
|
||||
import im.vector.app.features.home.room.detail.timeline.item.SendStateDecoration
|
||||
import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayoutFactory
|
||||
|
@ -55,7 +53,8 @@ class MessageInformationDataFactory @Inject constructor(
|
|||
private val session: Session,
|
||||
private val dateFormatter: VectorDateFormatter,
|
||||
private val messageLayoutFactory: TimelineMessageLayoutFactory,
|
||||
private val reactionsSummaryFactory: ReactionsSummaryFactory
|
||||
private val reactionsSummaryFactory: ReactionsSummaryFactory,
|
||||
private val pollResponseDataFactory: PollResponseDataFactory,
|
||||
) {
|
||||
|
||||
fun create(params: TimelineItemFactoryParams): MessageInformationData {
|
||||
|
@ -102,20 +101,7 @@ class MessageInformationDataFactory @Inject constructor(
|
|||
memberName = event.senderInfo.disambiguatedDisplayName,
|
||||
messageLayout = messageLayout,
|
||||
reactionsSummary = reactionsSummaryFactory.create(event),
|
||||
pollResponseAggregatedSummary = event.annotations?.pollResponseSummary?.let {
|
||||
PollResponseData(
|
||||
myVote = it.aggregatedContent?.myVote,
|
||||
isClosed = it.closedTime != null,
|
||||
votes = it.aggregatedContent?.votesSummary?.mapValues { votesSummary ->
|
||||
PollVoteSummaryData(
|
||||
total = votesSummary.value.total,
|
||||
percentage = votesSummary.value.percentage
|
||||
)
|
||||
},
|
||||
winnerVoteCount = it.aggregatedContent?.winnerVoteCount ?: 0,
|
||||
totalVotes = it.aggregatedContent?.totalVotes ?: 0
|
||||
)
|
||||
},
|
||||
pollResponseAggregatedSummary = pollResponseDataFactory.create(event),
|
||||
hasBeenEdited = event.hasBeenEdited(),
|
||||
hasPendingEdits = event.annotations?.editSummary?.localEchos?.any() ?: false,
|
||||
referencesInfoData = event.annotations?.referencesAggregatedSummary?.let { referencesAggregatedSummary ->
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* 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.helper
|
||||
|
||||
import im.vector.app.core.di.ActiveSessionHolder
|
||||
import im.vector.app.features.home.room.detail.timeline.item.PollResponseData
|
||||
import im.vector.app.features.home.room.detail.timeline.item.PollVoteSummaryData
|
||||
import org.matrix.android.sdk.api.session.events.model.getRelationContent
|
||||
import org.matrix.android.sdk.api.session.events.model.isPollEnd
|
||||
import org.matrix.android.sdk.api.session.room.getTimelineEvent
|
||||
import org.matrix.android.sdk.api.session.room.model.PollResponseAggregatedSummary
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class PollResponseDataFactory @Inject constructor(
|
||||
private val activeSessionHolder: ActiveSessionHolder,
|
||||
) {
|
||||
|
||||
fun create(event: TimelineEvent): PollResponseData? {
|
||||
val pollResponseSummary = getPollResponseSummary(event)
|
||||
return pollResponseSummary?.let {
|
||||
PollResponseData(
|
||||
myVote = it.aggregatedContent?.myVote,
|
||||
isClosed = it.closedTime != null,
|
||||
votes = it.aggregatedContent?.votesSummary?.mapValues { votesSummary ->
|
||||
PollVoteSummaryData(
|
||||
total = votesSummary.value.total,
|
||||
percentage = votesSummary.value.percentage
|
||||
)
|
||||
},
|
||||
winnerVoteCount = it.aggregatedContent?.winnerVoteCount ?: 0,
|
||||
totalVotes = it.aggregatedContent?.totalVotes ?: 0
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getPollResponseSummary(event: TimelineEvent): PollResponseAggregatedSummary? {
|
||||
return if (event.root.isPollEnd()) {
|
||||
val pollStartEventId = event.root.getRelationContent()?.eventId
|
||||
if (pollStartEventId.isNullOrEmpty()) {
|
||||
Timber.e("### Cannot render poll end event because poll start event id is null")
|
||||
null
|
||||
} else {
|
||||
activeSessionHolder
|
||||
.getSafeActiveSession()
|
||||
?.roomService()
|
||||
?.getRoom(event.roomId)
|
||||
?.getTimelineEvent(pollStartEventId)
|
||||
?.annotations
|
||||
?.pollResponseSummary
|
||||
}
|
||||
} else {
|
||||
event.annotations?.pollResponseSummary
|
||||
}
|
||||
}
|
||||
}
|
|
@ -55,6 +55,7 @@ object TimelineDisplayableEvents {
|
|||
VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO,
|
||||
) +
|
||||
EventType.POLL_START.values +
|
||||
EventType.POLL_END.values +
|
||||
EventType.STATE_ROOM_BEACON_INFO.values +
|
||||
EventType.BEACON_LOCATION_DATA.values
|
||||
}
|
||||
|
|
|
@ -85,7 +85,7 @@ abstract class MessageTextItem : AbsMessageItem<MessageTextItem.Holder>() {
|
|||
if (useBigFont) {
|
||||
holder.messageView.textSize = 44F
|
||||
} else {
|
||||
holder.messageView.textSize = 14F
|
||||
holder.messageView.textSize = 15.5F
|
||||
}
|
||||
if (searchForPills) {
|
||||
message?.charSequence?.findPillsAndProcess(coroutineScope) {
|
||||
|
|
|
@ -19,6 +19,7 @@ package im.vector.app.features.home.room.detail.timeline.item
|
|||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.children
|
||||
import androidx.core.view.isVisible
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
import im.vector.app.R
|
||||
|
@ -50,6 +51,9 @@ abstract class PollItem : AbsMessageItem<PollItem.Holder>() {
|
|||
@EpoxyAttribute
|
||||
lateinit var optionViewStates: List<PollOptionViewState>
|
||||
|
||||
@EpoxyAttribute
|
||||
var ended: Boolean = false
|
||||
|
||||
override fun getViewStubId() = STUB_ID
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
|
@ -75,6 +79,8 @@ abstract class PollItem : AbsMessageItem<PollItem.Holder>() {
|
|||
it.setOnClickListener { onPollItemClick(optionViewState) }
|
||||
}
|
||||
}
|
||||
|
||||
holder.endedPollTextView.isVisible = ended
|
||||
}
|
||||
|
||||
private fun onPollItemClick(optionViewState: PollOptionViewState) {
|
||||
|
@ -89,6 +95,7 @@ abstract class PollItem : AbsMessageItem<PollItem.Holder>() {
|
|||
val questionTextView by bind<TextView>(R.id.questionTextView)
|
||||
val optionsContainer by bind<LinearLayout>(R.id.optionsContainer)
|
||||
val votesStatusTextView by bind<TextView>(R.id.optionsVotesStatusTextView)
|
||||
val endedPollTextView by bind<TextView>(R.id.endedPollTextView)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -25,6 +25,7 @@ import androidx.core.view.isVisible
|
|||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.setAttributeTintedImageResource
|
||||
import im.vector.app.databinding.ItemPollOptionBinding
|
||||
import im.vector.app.features.themes.ThemeUtils
|
||||
|
||||
class PollOptionView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
|
@ -53,35 +54,40 @@ class PollOptionView @JvmOverloads constructor(
|
|||
|
||||
private fun renderPollSending() {
|
||||
views.optionCheckImageView.isVisible = false
|
||||
views.optionWinnerImageView.isVisible = false
|
||||
views.optionVoteCountTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0)
|
||||
hideVotes()
|
||||
renderVoteSelection(false)
|
||||
}
|
||||
|
||||
private fun renderPollEnded(state: PollOptionViewState.PollEnded) {
|
||||
views.optionCheckImageView.isVisible = false
|
||||
views.optionWinnerImageView.isVisible = state.isWinner
|
||||
val drawableStart = if (state.isWinner) R.drawable.ic_poll_winner else 0
|
||||
views.optionVoteCountTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(drawableStart, 0, 0, 0)
|
||||
views.optionVoteCountTextView.setTextColor(
|
||||
if (state.isWinner) ThemeUtils.getColor(context, R.attr.colorPrimary)
|
||||
else ThemeUtils.getColor(context, R.attr.vctr_content_secondary)
|
||||
)
|
||||
showVotes(state.voteCount, state.votePercentage)
|
||||
renderVoteSelection(state.isWinner)
|
||||
}
|
||||
|
||||
private fun renderPollReady() {
|
||||
views.optionCheckImageView.isVisible = true
|
||||
views.optionWinnerImageView.isVisible = false
|
||||
views.optionVoteCountTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0)
|
||||
hideVotes()
|
||||
renderVoteSelection(false)
|
||||
}
|
||||
|
||||
private fun renderPollVoted(state: PollOptionViewState.PollVoted) {
|
||||
views.optionCheckImageView.isVisible = true
|
||||
views.optionWinnerImageView.isVisible = false
|
||||
views.optionVoteCountTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0)
|
||||
showVotes(state.voteCount, state.votePercentage)
|
||||
renderVoteSelection(state.isSelected)
|
||||
}
|
||||
|
||||
private fun renderPollUndisclosed(state: PollOptionViewState.PollUndisclosed) {
|
||||
views.optionCheckImageView.isVisible = true
|
||||
views.optionWinnerImageView.isVisible = false
|
||||
views.optionVoteCountTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0)
|
||||
hideVotes()
|
||||
renderVoteSelection(state.isSelected)
|
||||
}
|
||||
|
|
|
@ -25,6 +25,8 @@ import org.matrix.android.sdk.api.session.events.model.isFileMessage
|
|||
import org.matrix.android.sdk.api.session.events.model.isImageMessage
|
||||
import org.matrix.android.sdk.api.session.events.model.isLiveLocation
|
||||
import org.matrix.android.sdk.api.session.events.model.isPoll
|
||||
import org.matrix.android.sdk.api.session.events.model.isPollEnd
|
||||
import org.matrix.android.sdk.api.session.events.model.isPollStart
|
||||
import org.matrix.android.sdk.api.session.events.model.isSticker
|
||||
import org.matrix.android.sdk.api.session.events.model.isVideoMessage
|
||||
import org.matrix.android.sdk.api.session.events.model.isVoiceMessage
|
||||
|
@ -93,10 +95,15 @@ class ProcessBodyOfReplyToEventUseCase @Inject constructor(
|
|||
)
|
||||
}
|
||||
repliedToEvent.isPoll() -> {
|
||||
val fallbackText = when {
|
||||
repliedToEvent.isPollStart() -> stringProvider.getString(R.string.message_reply_to_sender_created_poll)
|
||||
repliedToEvent.isPollEnd() -> stringProvider.getString(R.string.message_reply_to_sender_ended_poll)
|
||||
else -> ""
|
||||
}
|
||||
matrixFormattedBody.replaceRange(
|
||||
afterBreakingLineIndex,
|
||||
endOfBlockQuoteIndex,
|
||||
repliedToEvent.getPollQuestion() ?: stringProvider.getString(R.string.message_reply_to_sender_created_poll)
|
||||
repliedToEvent.getPollQuestion() ?: fallbackText
|
||||
)
|
||||
}
|
||||
repliedToEvent.isLiveLocation() -> {
|
||||
|
|
|
@ -50,6 +50,7 @@ class TimelineMessageLayoutFactory @Inject constructor(
|
|||
EventType.STICKER,
|
||||
) +
|
||||
EventType.POLL_START.values +
|
||||
EventType.POLL_END.values +
|
||||
EventType.STATE_ROOM_BEACON_INFO.values
|
||||
|
||||
// Can't be rendered in bubbles, so get back to default layout
|
||||
|
|
|
@ -22,41 +22,33 @@ import com.airbnb.mvrx.Loading
|
|||
import im.vector.app.R
|
||||
import im.vector.app.core.date.DateFormatKind
|
||||
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.error.ErrorFormatter
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
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.list.usecase.GetLatestPreviewableEventUseCase
|
||||
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 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.model.Membership
|
||||
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.message.asMessageAudioEvent
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||
import javax.inject.Inject
|
||||
|
||||
class RoomSummaryItemFactory @Inject constructor(
|
||||
private val sessionHolder: ActiveSessionHolder,
|
||||
private val displayableEventFormatter: DisplayableEventFormatter,
|
||||
private val dateFormatter: VectorDateFormatter,
|
||||
private val stringProvider: StringProvider,
|
||||
private val typingHelper: TypingHelper,
|
||||
private val avatarRenderer: AvatarRenderer,
|
||||
private val errorFormatter: ErrorFormatter,
|
||||
private val getRoomLiveVoiceBroadcastsUseCase: GetRoomLiveVoiceBroadcastsUseCase,
|
||||
private val getLatestPreviewableEventUseCase: GetLatestPreviewableEventUseCase,
|
||||
) {
|
||||
|
||||
fun create(
|
||||
|
@ -142,7 +134,7 @@ class RoomSummaryItemFactory @Inject constructor(
|
|||
val showSelected = selectedRoomIds.contains(roomSummary.roomId)
|
||||
var latestFormattedEvent: CharSequence = ""
|
||||
var latestEventTime = ""
|
||||
val latestEvent = roomSummary.getVectorLatestPreviewableEvent()
|
||||
val latestEvent = getLatestPreviewableEventUseCase.execute(roomSummary.roomId)
|
||||
if (latestEvent != null) {
|
||||
latestFormattedEvent = displayableEventFormatter.format(latestEvent, roomSummary.isDirect, roomSummary.isDirect.not())
|
||||
latestEventTime = dateFormatter.format(latestEvent.root.originServerTs, DateFormatKind.ROOM_LIST)
|
||||
|
@ -150,7 +142,8 @@ class RoomSummaryItemFactory @Inject constructor(
|
|||
|
||||
val typingMessage = typingHelper.getTypingMessage(roomSummary.typingUsers)
|
||||
// Skip typing while there is a live voice broadcast
|
||||
.takeUnless { latestEvent?.root?.asVoiceBroadcastEvent()?.isLive.orFalse() }.orEmpty()
|
||||
.takeUnless { latestEvent?.root?.asVoiceBroadcastEvent()?.isLive.orFalse() }
|
||||
.orEmpty()
|
||||
|
||||
return if (subtitle.isBlank() && displayMode == RoomListDisplayMode.FILTERED) {
|
||||
createCenteredRoomSummaryItem(roomSummary, displayMode, showSelected, unreadCount, onClick, onLongClick)
|
||||
|
@ -240,14 +233,4 @@ class RoomSummaryItemFactory @Inject constructor(
|
|||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* 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.list.usecase
|
||||
|
||||
import im.vector.app.core.di.ActiveSessionHolder
|
||||
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.app.features.voicebroadcast.voiceBroadcastId
|
||||
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.Room
|
||||
import org.matrix.android.sdk.api.session.room.getTimelineEvent
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
import org.matrix.android.sdk.api.session.room.model.message.asMessageAudioEvent
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||
import javax.inject.Inject
|
||||
|
||||
class GetLatestPreviewableEventUseCase @Inject constructor(
|
||||
private val sessionHolder: ActiveSessionHolder,
|
||||
private val getRoomLiveVoiceBroadcastsUseCase: GetRoomLiveVoiceBroadcastsUseCase,
|
||||
) {
|
||||
|
||||
fun execute(roomId: String): TimelineEvent? {
|
||||
val room = sessionHolder.getSafeActiveSession()?.getRoom(roomId) ?: return null
|
||||
val roomSummary = room.roomSummary() ?: return null
|
||||
return getCallEvent(roomSummary)
|
||||
?: getLiveVoiceBroadcastEvent(room)
|
||||
?: getDefaultLatestEvent(room, roomSummary)
|
||||
}
|
||||
|
||||
private fun getCallEvent(roomSummary: RoomSummary): TimelineEvent? {
|
||||
return roomSummary.latestPreviewableEvent
|
||||
?.takeIf { it.root.getClearType() == EventType.CALL_INVITE }
|
||||
}
|
||||
|
||||
private fun getLiveVoiceBroadcastEvent(room: Room): TimelineEvent? {
|
||||
return getRoomLiveVoiceBroadcastsUseCase.execute(room.roomId)
|
||||
.lastOrNull()
|
||||
?.voiceBroadcastId
|
||||
?.let { room.getTimelineEvent(it) }
|
||||
}
|
||||
|
||||
private fun getDefaultLatestEvent(room: Room, roomSummary: RoomSummary): TimelineEvent? {
|
||||
val latestPreviewableEvent = roomSummary.latestPreviewableEvent
|
||||
|
||||
// If the default latest event is a live voice broadcast (paused or resumed), rely to the started event
|
||||
val liveVoiceBroadcastEventId = latestPreviewableEvent?.root?.asVoiceBroadcastEvent()?.takeIf { it.isLive }?.voiceBroadcastId
|
||||
if (liveVoiceBroadcastEventId != null) {
|
||||
return room.getTimelineEvent(liveVoiceBroadcastEventId)
|
||||
}
|
||||
|
||||
return latestPreviewableEvent
|
||||
?.takeUnless { it.root.asMessageAudioEvent()?.isVoiceBroadcast().orFalse() } // Skip voice messages related to voice broadcast
|
||||
}
|
||||
}
|
|
@ -685,7 +685,7 @@ class LoginViewModel @AssistedInject constructor(
|
|||
currentJob = viewModelScope.launch {
|
||||
try {
|
||||
safeLoginWizard.login(
|
||||
action.username,
|
||||
action.username.trim(),
|
||||
action.password,
|
||||
action.initialDeviceName
|
||||
)
|
||||
|
|
|
@ -19,14 +19,20 @@ package im.vector.app.features.voicebroadcast.usecase
|
|||
import im.vector.app.core.di.ActiveSessionHolder
|
||||
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
|
||||
import im.vector.app.features.voicebroadcast.isLive
|
||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcast
|
||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastEvent
|
||||
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
||||
import im.vector.app.features.voicebroadcast.voiceBroadcastId
|
||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||
import org.matrix.android.sdk.api.session.getRoom
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Get the list of live (not ended) voice broadcast events in the given room.
|
||||
*/
|
||||
class GetRoomLiveVoiceBroadcastsUseCase @Inject constructor(
|
||||
private val activeSessionHolder: ActiveSessionHolder,
|
||||
private val getVoiceBroadcastStateEventUseCase: GetVoiceBroadcastStateEventUseCase,
|
||||
) {
|
||||
|
||||
fun execute(roomId: String): List<VoiceBroadcastEvent> {
|
||||
|
@ -37,7 +43,8 @@ class GetRoomLiveVoiceBroadcastsUseCase @Inject constructor(
|
|||
setOf(VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO),
|
||||
QueryStringValue.IsNotEmpty
|
||||
)
|
||||
.mapNotNull { it.asVoiceBroadcastEvent() }
|
||||
.mapNotNull { stateEvent -> stateEvent.asVoiceBroadcastEvent()?.voiceBroadcastId }
|
||||
.mapNotNull { voiceBroadcastId -> getVoiceBroadcastStateEventUseCase.execute(VoiceBroadcast(voiceBroadcastId, roomId)) }
|
||||
.filter { it.isLive }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,6 @@ import kotlinx.coroutines.flow.onStart
|
|||
import kotlinx.coroutines.flow.transformWhile
|
||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.events.model.RelationType
|
||||
import org.matrix.android.sdk.api.session.getRoom
|
||||
import org.matrix.android.sdk.api.session.room.Room
|
||||
import org.matrix.android.sdk.api.util.Optional
|
||||
|
@ -44,6 +43,7 @@ import javax.inject.Inject
|
|||
|
||||
class GetVoiceBroadcastStateEventLiveUseCase @Inject constructor(
|
||||
private val session: Session,
|
||||
private val getVoiceBroadcastStateEventUseCase: GetVoiceBroadcastStateEventUseCase,
|
||||
) {
|
||||
|
||||
fun execute(voiceBroadcast: VoiceBroadcast): Flow<Optional<VoiceBroadcastEvent>> {
|
||||
|
@ -93,7 +93,7 @@ class GetVoiceBroadcastStateEventLiveUseCase @Inject constructor(
|
|||
* Get a flow of the most recent related event.
|
||||
*/
|
||||
private fun getMostRecentRelatedEventFlow(room: Room, voiceBroadcast: VoiceBroadcast): Flow<Optional<VoiceBroadcastEvent>> {
|
||||
val mostRecentEvent = getMostRecentRelatedEvent(room, voiceBroadcast).toOptional()
|
||||
val mostRecentEvent = getVoiceBroadcastStateEventUseCase.execute(voiceBroadcast).toOptional()
|
||||
return if (mostRecentEvent.hasValue()) {
|
||||
val stateKey = mostRecentEvent.get().root.stateKey.orEmpty()
|
||||
// observe incoming voice broadcast state events
|
||||
|
@ -141,15 +141,6 @@ class GetVoiceBroadcastStateEventLiveUseCase @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the most recent event related to the given voice broadcast.
|
||||
*/
|
||||
private fun getMostRecentRelatedEvent(room: Room, voiceBroadcast: VoiceBroadcast): VoiceBroadcastEvent? {
|
||||
return room.timelineService().getTimelineEventsRelatedTo(RelationType.REFERENCE, voiceBroadcast.voiceBroadcastId)
|
||||
.mapNotNull { timelineEvent -> timelineEvent.root.asVoiceBroadcastEvent()?.takeUnless { it.root.isRedacted() } }
|
||||
.maxByOrNull { it.root.originServerTs ?: 0 }
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a flow of the given voice broadcast event changes.
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* 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.voicebroadcast.usecase
|
||||
|
||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcast
|
||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastEvent
|
||||
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
||||
import im.vector.app.features.voicebroadcast.voiceBroadcastId
|
||||
import org.matrix.android.sdk.api.extensions.orTrue
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.events.model.RelationType
|
||||
import org.matrix.android.sdk.api.session.getRoom
|
||||
import org.matrix.android.sdk.api.session.room.Room
|
||||
import org.matrix.android.sdk.api.session.room.getTimelineEvent
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class GetVoiceBroadcastStateEventUseCase @Inject constructor(
|
||||
private val session: Session,
|
||||
) {
|
||||
|
||||
fun execute(voiceBroadcast: VoiceBroadcast): VoiceBroadcastEvent? {
|
||||
val room = session.getRoom(voiceBroadcast.roomId) ?: error("Unknown roomId: ${voiceBroadcast.roomId}")
|
||||
return getMostRecentRelatedEvent(room, voiceBroadcast)
|
||||
.also { event ->
|
||||
Timber.d(
|
||||
"## VoiceBroadcast | " +
|
||||
"voiceBroadcastId=${event?.voiceBroadcastId}, " +
|
||||
"state=${event?.content?.voiceBroadcastState}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the most recent event related to the given voice broadcast.
|
||||
*/
|
||||
private fun getMostRecentRelatedEvent(room: Room, voiceBroadcast: VoiceBroadcast): VoiceBroadcastEvent? {
|
||||
val startedEvent = room.getTimelineEvent(voiceBroadcast.voiceBroadcastId)
|
||||
return if (startedEvent?.root?.isRedacted().orTrue()) {
|
||||
null
|
||||
} else {
|
||||
room.timelineService().getTimelineEventsRelatedTo(RelationType.REFERENCE, voiceBroadcast.voiceBroadcastId)
|
||||
.mapNotNull { timelineEvent -> timelineEvent.root.asVoiceBroadcastEvent() }
|
||||
.filterNot { it.root.isRedacted() }
|
||||
.maxByOrNull { it.root.originServerTs ?: 0 }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -124,6 +124,8 @@
|
|||
app:layout_constraintEnd_toStartOf="@id/composerFullScreenButton"
|
||||
app:layout_constraintStart_toStartOf="@id/composerEditTextOuterBorder"
|
||||
app:layout_constraintTop_toBottomOf="@id/composerModeBarrier"
|
||||
app:bulletRadius="4sp"
|
||||
app:bulletGap="8sp"
|
||||
tools:text="@tools:sample/lorem/random" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
|
|
|
@ -36,34 +36,23 @@
|
|||
android:layout_marginStart="12dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
app:layout_constraintEnd_toEndOf="@id/optionWinnerImageView"
|
||||
app:layout_constraintEnd_toStartOf="@id/optionVoteCountTextView"
|
||||
app:layout_constraintStart_toEndOf="@id/optionCheckImageView"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="@sample/poll.json/data/answer" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/optionWinnerImageView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:contentDescription="@string/a11y_poll_winner_option"
|
||||
android:src="@drawable/ic_poll_winner"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/optionVoteCountTextView"
|
||||
style="@style/Widget.Vector.TextView.Caption"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:drawablePadding="6dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="@id/optionVoteProgress"
|
||||
app:layout_constraintBottom_toBottomOf="@id/optionNameTextView"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/optionVoteProgress"
|
||||
app:layout_constraintTop_toTopOf="@id/optionNameTextView"
|
||||
tools:drawableStartCompat="@drawable/ic_poll_winner"
|
||||
tools:text="@sample/poll.json/data/votes"
|
||||
tools:visibility="visible" />
|
||||
|
||||
|
@ -78,9 +67,9 @@
|
|||
android:layout_marginBottom="8dp"
|
||||
android:progressDrawable="@drawable/poll_option_progressbar_checked"
|
||||
app:layout_constraintBottom_toBottomOf="@id/optionBorderImageView"
|
||||
app:layout_constraintEnd_toStartOf="@id/optionVoteCountTextView"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/optionNameTextView"
|
||||
tools:progress="60" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
|
@ -2,9 +2,21 @@
|
|||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:minWidth="@dimen/chat_bubble_fixed_size"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="wrap_content"
|
||||
android:minWidth="@dimen/chat_bubble_fixed_size">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/endedPollTextView"
|
||||
style="@style/Widget.Vector.TextView.Caption"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/ended_poll_indicator"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/questionTextView"
|
||||
|
@ -13,11 +25,10 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:textColor="?vctr_content_primary"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/endedPollTextView"
|
||||
tools:text="@sample/poll.json/question" />
|
||||
|
||||
<LinearLayout
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
* 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.home
|
||||
|
||||
import com.airbnb.mvrx.test.MavericksTestRule
|
||||
import im.vector.app.features.home.room.list.home.invites.InvitesAction
|
||||
import im.vector.app.features.home.room.list.home.invites.InvitesViewEvents
|
||||
import im.vector.app.features.home.room.list.home.invites.InvitesViewModel
|
||||
import im.vector.app.features.home.room.list.home.invites.InvitesViewState
|
||||
import im.vector.app.test.fakes.FakeDrawableProvider
|
||||
import im.vector.app.test.fakes.FakeSession
|
||||
import im.vector.app.test.fakes.FakeStringProvider
|
||||
import im.vector.app.test.fixtures.RoomSummaryFixture
|
||||
import im.vector.app.test.test
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkStatic
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||
|
||||
class InvitesViewModelTest {
|
||||
|
||||
@get:Rule
|
||||
val mavericksTestRule = MavericksTestRule()
|
||||
|
||||
private val fakeSession = FakeSession()
|
||||
private val fakeStringProvider = FakeStringProvider()
|
||||
private val fakeDrawableProvider = FakeDrawableProvider()
|
||||
|
||||
private var initialState = InvitesViewState()
|
||||
private lateinit var viewModel: InvitesViewModel
|
||||
|
||||
private val anInvite = RoomSummaryFixture.aRoomSummary("invite")
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
mockkStatic("org.matrix.android.sdk.flow.FlowSessionKt")
|
||||
|
||||
every {
|
||||
fakeSession.fakeRoomService.getPagedRoomSummariesLive(
|
||||
queryParams = match {
|
||||
it.memberships == listOf(Membership.INVITE)
|
||||
},
|
||||
pagedListConfig = any(),
|
||||
sortOrder = any()
|
||||
)
|
||||
} returns mockk()
|
||||
|
||||
viewModelWith(initialState)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when invite accepted then membership map is updated and open event posted`() = runTest {
|
||||
val test = viewModel.test()
|
||||
|
||||
viewModel.handle(InvitesAction.AcceptInvitation(anInvite))
|
||||
|
||||
test.assertEvents(
|
||||
InvitesViewEvents.OpenRoom(
|
||||
roomSummary = anInvite,
|
||||
shouldCloseInviteView = false,
|
||||
isInviteAlreadySelected = true
|
||||
)
|
||||
).finish()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when invite rejected then membership map is updated and open event posted`() = runTest {
|
||||
coEvery { fakeSession.roomService().leaveRoom(any(), any()) } returns Unit
|
||||
|
||||
viewModel.handle(InvitesAction.RejectInvitation(anInvite))
|
||||
|
||||
coVerify {
|
||||
fakeSession.roomService().leaveRoom(anInvite.roomId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun viewModelWith(state: InvitesViewState) {
|
||||
InvitesViewModel(
|
||||
state,
|
||||
session = fakeSession,
|
||||
stringProvider = fakeStringProvider.instance,
|
||||
drawableProvider = fakeDrawableProvider.instance,
|
||||
).also {
|
||||
viewModel = it
|
||||
initialState = state
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,184 @@
|
|||
/*
|
||||
* 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.home
|
||||
|
||||
import android.widget.ImageView
|
||||
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
||||
import com.airbnb.mvrx.test.MavericksTestRule
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.platform.StateView
|
||||
import im.vector.app.features.displayname.getBestName
|
||||
import im.vector.app.features.home.room.list.home.HomeRoomListAction
|
||||
import im.vector.app.features.home.room.list.home.HomeRoomListViewModel
|
||||
import im.vector.app.features.home.room.list.home.HomeRoomListViewState
|
||||
import im.vector.app.features.home.room.list.home.header.HomeRoomFilter
|
||||
import im.vector.app.test.fakes.FakeAnalyticsTracker
|
||||
import im.vector.app.test.fakes.FakeDrawableProvider
|
||||
import im.vector.app.test.fakes.FakeHomeLayoutPreferencesStore
|
||||
import im.vector.app.test.fakes.FakeSession
|
||||
import im.vector.app.test.fakes.FakeSpaceStateHandler
|
||||
import im.vector.app.test.fakes.FakeStringProvider
|
||||
import im.vector.app.test.fixtures.RoomSummaryFixture.aRoomSummary
|
||||
import im.vector.app.test.test
|
||||
import io.mockk.every
|
||||
import io.mockk.mockkStatic
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.matrix.android.sdk.api.query.SpaceFilter
|
||||
import org.matrix.android.sdk.api.session.getUserOrDefault
|
||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||
import org.matrix.android.sdk.api.util.Optional
|
||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||
import org.matrix.android.sdk.flow.FlowSession
|
||||
|
||||
class RoomsListViewModelTest {
|
||||
|
||||
@get:Rule
|
||||
val mavericksTestRule = MavericksTestRule()
|
||||
|
||||
@get:Rule
|
||||
var rule = InstantTaskExecutorRule()
|
||||
|
||||
private val fakeSession = FakeSession()
|
||||
private val fakeAnalyticsTracker = FakeAnalyticsTracker()
|
||||
private val fakeStringProvider = FakeStringProvider()
|
||||
private val fakeDrawableProvider = FakeDrawableProvider()
|
||||
private val fakeSpaceStateHandler = FakeSpaceStateHandler()
|
||||
private val fakeHomeLayoutPreferencesStore = FakeHomeLayoutPreferencesStore()
|
||||
|
||||
private var initialState = HomeRoomListViewState()
|
||||
private lateinit var viewModel: HomeRoomListViewModel
|
||||
private lateinit var fakeFLowSession: FlowSession
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
mockkStatic("org.matrix.android.sdk.flow.FlowSessionKt")
|
||||
fakeFLowSession = fakeSession.givenFlowSession()
|
||||
|
||||
every { fakeSpaceStateHandler.getSelectedSpaceFlow() } returns flowOf(Optional.empty())
|
||||
every { fakeSpaceStateHandler.getCurrentSpace() } returns null
|
||||
every { fakeFLowSession.liveRoomSummaries(any(), any()) } returns flowOf(emptyList())
|
||||
|
||||
val roomA = aRoomSummary("room_a")
|
||||
val roomB = aRoomSummary("room_b")
|
||||
val roomC = aRoomSummary("room_c")
|
||||
val allRooms = listOf(roomA, roomB, roomC)
|
||||
|
||||
every {
|
||||
fakeFLowSession.liveRoomSummaries(
|
||||
match {
|
||||
it.roomCategoryFilter == null &&
|
||||
it.roomTagQueryFilter == null &&
|
||||
it.memberships == listOf(Membership.JOIN) &&
|
||||
it.spaceFilter is SpaceFilter.NoFilter
|
||||
}, any()
|
||||
)
|
||||
} returns flowOf(allRooms)
|
||||
|
||||
viewModelWith(initialState)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when recents are enabled then updates state`() = runTest {
|
||||
val fakeFLowSession = fakeSession.givenFlowSession()
|
||||
every { fakeFLowSession.liveRoomSummaries(any()) } returns flowOf(emptyList())
|
||||
val test = viewModel.test()
|
||||
|
||||
val roomA = aRoomSummary("room_a")
|
||||
val roomB = aRoomSummary("room_b")
|
||||
val roomC = aRoomSummary("room_c")
|
||||
val recentRooms = listOf(roomA, roomB, roomC)
|
||||
|
||||
every { fakeFLowSession.liveBreadcrumbs(any()) } returns flowOf(recentRooms)
|
||||
fakeHomeLayoutPreferencesStore.givenRecentsEnabled(true)
|
||||
|
||||
val userName = fakeSession.getUserOrDefault(fakeSession.myUserId).toMatrixItem().getBestName()
|
||||
val allEmptyState = StateView.State.Empty(
|
||||
title = fakeStringProvider.instance.getString(R.string.home_empty_no_rooms_title, userName),
|
||||
message = fakeStringProvider.instance.getString(R.string.home_empty_no_rooms_message),
|
||||
image = fakeDrawableProvider.instance.getDrawable(R.drawable.ill_empty_all_chats),
|
||||
isBigImage = true
|
||||
)
|
||||
|
||||
test.assertLatestState(
|
||||
initialState.copy(emptyState = allEmptyState, headersData = initialState.headersData.copy(recents = recentRooms))
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when filter tabs are enabled then updates state`() = runTest {
|
||||
val test = viewModel.test()
|
||||
|
||||
fakeHomeLayoutPreferencesStore.givenFiltersEnabled(true)
|
||||
|
||||
val filtersData = mutableListOf(
|
||||
HomeRoomFilter.ALL,
|
||||
HomeRoomFilter.UNREADS
|
||||
)
|
||||
|
||||
val userName = fakeSession.getUserOrDefault(fakeSession.myUserId).toMatrixItem().getBestName()
|
||||
val allEmptyState = StateView.State.Empty(
|
||||
title = fakeStringProvider.instance.getString(R.string.home_empty_no_rooms_title, userName),
|
||||
message = fakeStringProvider.instance.getString(R.string.home_empty_no_rooms_message),
|
||||
image = fakeDrawableProvider.instance.getDrawable(R.drawable.ill_empty_all_chats),
|
||||
isBigImage = true
|
||||
)
|
||||
|
||||
test.assertLatestState(
|
||||
initialState.copy(emptyState = allEmptyState, headersData = initialState.headersData.copy(filtersList = filtersData))
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when filter tab is selected then updates state`() = runTest {
|
||||
val test = viewModel.test()
|
||||
|
||||
val aFilter = HomeRoomFilter.UNREADS
|
||||
viewModel.handle(HomeRoomListAction.ChangeRoomFilter(filter = aFilter))
|
||||
|
||||
val unreadsEmptyState = StateView.State.Empty(
|
||||
title = fakeStringProvider.instance.getString(R.string.home_empty_no_unreads_title),
|
||||
message = fakeStringProvider.instance.getString(R.string.home_empty_no_unreads_message),
|
||||
image = fakeDrawableProvider.instance.getDrawable(R.drawable.ill_empty_unreads),
|
||||
isBigImage = true,
|
||||
imageScaleType = ImageView.ScaleType.CENTER_INSIDE
|
||||
)
|
||||
|
||||
test.assertLatestState(
|
||||
initialState.copy(emptyState = unreadsEmptyState, headersData = initialState.headersData.copy(currentFilter = aFilter))
|
||||
)
|
||||
}
|
||||
|
||||
private fun viewModelWith(state: HomeRoomListViewState) {
|
||||
HomeRoomListViewModel(
|
||||
state,
|
||||
session = fakeSession,
|
||||
spaceStateHandler = fakeSpaceStateHandler,
|
||||
preferencesStore = fakeHomeLayoutPreferencesStore.instance,
|
||||
stringProvider = fakeStringProvider.instance,
|
||||
drawableProvider = fakeDrawableProvider.instance,
|
||||
analyticsTracker = fakeAnalyticsTracker
|
||||
|
||||
).also {
|
||||
viewModel = it
|
||||
initialState = state
|
||||
}
|
||||
}
|
||||
}
|
|
@ -43,7 +43,7 @@ class CheckIfCanReplyEventUseCaseTest {
|
|||
|
||||
@Test
|
||||
fun `given reply is allowed for the event type when use case is executed then result is true`() {
|
||||
val eventTypes = EventType.STATE_ROOM_BEACON_INFO.values + EventType.POLL_START.values + EventType.MESSAGE
|
||||
val eventTypes = EventType.STATE_ROOM_BEACON_INFO.values + EventType.POLL_START.values + EventType.POLL_END.values + EventType.MESSAGE
|
||||
|
||||
eventTypes.forEach { eventType ->
|
||||
val event = givenAnEvent(eventType)
|
||||
|
@ -78,6 +78,7 @@ class CheckIfCanReplyEventUseCaseTest {
|
|||
MessageType.MSGTYPE_AUDIO,
|
||||
MessageType.MSGTYPE_FILE,
|
||||
MessageType.MSGTYPE_POLL_START,
|
||||
MessageType.MSGTYPE_POLL_END,
|
||||
MessageType.MSGTYPE_BEACON_INFO,
|
||||
MessageType.MSGTYPE_LOCATION
|
||||
)
|
||||
|
|
|
@ -29,6 +29,7 @@ import org.junit.After
|
|||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.api.session.events.model.getPollQuestion
|
||||
import org.matrix.android.sdk.api.session.events.model.isAudioMessage
|
||||
import org.matrix.android.sdk.api.session.events.model.isFileMessage
|
||||
|
@ -158,6 +159,7 @@ class ProcessBodyOfReplyToEventUseCaseTest {
|
|||
// Given
|
||||
givenTypeOfRepliedEvent(isPollMessage = true)
|
||||
givenNewContentForId(R.string.message_reply_to_sender_created_poll)
|
||||
every { fakeRepliedEvent.getClearType() } returns EventType.POLL_START.unstable
|
||||
every { fakeRepliedEvent.getPollQuestion() } returns null
|
||||
|
||||
executeAndAssertResult()
|
||||
|
@ -168,11 +170,23 @@ class ProcessBodyOfReplyToEventUseCaseTest {
|
|||
// Given
|
||||
givenTypeOfRepliedEvent(isPollMessage = true)
|
||||
givenNewContentForId(R.string.message_reply_to_sender_created_poll)
|
||||
every { fakeRepliedEvent.getClearType() } returns EventType.POLL_START.unstable
|
||||
every { fakeRepliedEvent.getPollQuestion() } returns A_NEW_CONTENT
|
||||
|
||||
executeAndAssertResult()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given a replied event of type poll end message when process the formatted body then content is replaced by correct string`() {
|
||||
// Given
|
||||
givenTypeOfRepliedEvent(isPollMessage = true)
|
||||
givenNewContentForId(R.string.message_reply_to_sender_ended_poll)
|
||||
every { fakeRepliedEvent.getClearType() } returns EventType.POLL_END.unstable
|
||||
every { fakeRepliedEvent.getPollQuestion() } returns null
|
||||
|
||||
executeAndAssertResult()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given a replied event of type live location message when process the formatted body then content is replaced by correct string`() {
|
||||
// Given
|
||||
|
|
|
@ -0,0 +1,196 @@
|
|||
/*
|
||||
* 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.list.usecase
|
||||
|
||||
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
|
||||
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants.VOICE_BROADCAST_CHUNK_KEY
|
||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
||||
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
||||
import im.vector.app.features.voicebroadcast.usecase.GetRoomLiveVoiceBroadcastsUseCase
|
||||
import im.vector.app.test.fakes.FakeActiveSessionHolder
|
||||
import im.vector.app.test.fakes.FakeRoom
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import org.amshove.kluent.shouldBe
|
||||
import org.amshove.kluent.shouldBeEqualTo
|
||||
import org.amshove.kluent.shouldBeNull
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.api.session.events.model.RelationType
|
||||
import org.matrix.android.sdk.api.session.getRoom
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||
|
||||
private const val A_ROOM_ID = "a-room-id"
|
||||
|
||||
internal class GetLatestPreviewableEventUseCaseTest {
|
||||
|
||||
private val fakeRoom = FakeRoom()
|
||||
private val fakeSessionHolder = FakeActiveSessionHolder()
|
||||
private val fakeRoomSummary = mockk<RoomSummary>()
|
||||
private val fakeGetRoomLiveVoiceBroadcastsUseCase = mockk<GetRoomLiveVoiceBroadcastsUseCase>()
|
||||
|
||||
private val getLatestPreviewableEventUseCase = GetLatestPreviewableEventUseCase(
|
||||
fakeSessionHolder.instance,
|
||||
fakeGetRoomLiveVoiceBroadcastsUseCase,
|
||||
)
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
every { fakeSessionHolder.instance.getSafeActiveSession()?.getRoom(A_ROOM_ID) } returns fakeRoom
|
||||
every { fakeRoom.roomSummary() } returns fakeRoomSummary
|
||||
every { fakeRoom.roomId } returns A_ROOM_ID
|
||||
every { fakeRoom.timelineService().getTimelineEvent(any()) } answers {
|
||||
mockk(relaxed = true) {
|
||||
every { eventId } returns firstArg()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given the latest event is a call invite and there is a live broadcast, when execute, returns the call event`() {
|
||||
// Given
|
||||
val aLatestPreviewableEvent = mockk<TimelineEvent> {
|
||||
every { root.type } returns EventType.MESSAGE
|
||||
every { root.getClearType() } returns EventType.CALL_INVITE
|
||||
}
|
||||
every { fakeRoomSummary.latestPreviewableEvent } returns aLatestPreviewableEvent
|
||||
every { fakeGetRoomLiveVoiceBroadcastsUseCase.execute(A_ROOM_ID) } returns listOf(
|
||||
givenAVoiceBroadcastEvent("id1", VoiceBroadcastState.STARTED, "id1"),
|
||||
givenAVoiceBroadcastEvent("id2", VoiceBroadcastState.RESUMED, "id1"),
|
||||
).mapNotNull { it.asVoiceBroadcastEvent() }
|
||||
|
||||
// When
|
||||
val result = getLatestPreviewableEventUseCase.execute(A_ROOM_ID)
|
||||
|
||||
// Then
|
||||
result shouldBe aLatestPreviewableEvent
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given the latest event is not a call invite and there is a live broadcast, when execute, returns the latest broadcast event`() {
|
||||
// Given
|
||||
val aLatestPreviewableEvent = mockk<TimelineEvent> {
|
||||
every { root.type } returns EventType.MESSAGE
|
||||
every { root.getClearType() } returns EventType.MESSAGE
|
||||
}
|
||||
every { fakeRoomSummary.latestPreviewableEvent } returns aLatestPreviewableEvent
|
||||
every { fakeGetRoomLiveVoiceBroadcastsUseCase.execute(A_ROOM_ID) } returns listOf(
|
||||
givenAVoiceBroadcastEvent("id1", VoiceBroadcastState.STARTED, "vb_id1"),
|
||||
givenAVoiceBroadcastEvent("id2", VoiceBroadcastState.RESUMED, "vb_id2"),
|
||||
).mapNotNull { it.asVoiceBroadcastEvent() }
|
||||
|
||||
// When
|
||||
val result = getLatestPreviewableEventUseCase.execute(A_ROOM_ID)
|
||||
|
||||
// Then
|
||||
result?.eventId shouldBeEqualTo "vb_id2"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given there is no live broadcast, when execute, returns the latest event`() {
|
||||
// Given
|
||||
val aLatestPreviewableEvent = mockk<TimelineEvent> {
|
||||
every { root.type } returns EventType.MESSAGE
|
||||
every { root.getClearType() } returns EventType.MESSAGE
|
||||
}
|
||||
every { fakeRoomSummary.latestPreviewableEvent } returns aLatestPreviewableEvent
|
||||
every { fakeGetRoomLiveVoiceBroadcastsUseCase.execute(A_ROOM_ID) } returns emptyList()
|
||||
|
||||
// When
|
||||
val result = getLatestPreviewableEventUseCase.execute(A_ROOM_ID)
|
||||
|
||||
// Then
|
||||
result shouldBe aLatestPreviewableEvent
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given there is no live broadcast and the latest event is a vb message, when execute, returns null`() {
|
||||
// Given
|
||||
val aLatestPreviewableEvent = mockk<TimelineEvent> {
|
||||
every { root.type } returns EventType.MESSAGE
|
||||
every { root.getClearType() } returns EventType.MESSAGE
|
||||
every { root.getClearContent() } returns mapOf(
|
||||
MessageContent.MSG_TYPE_JSON_KEY to "m.audio",
|
||||
VOICE_BROADCAST_CHUNK_KEY to "1",
|
||||
"body" to "",
|
||||
)
|
||||
}
|
||||
every { fakeRoomSummary.latestPreviewableEvent } returns aLatestPreviewableEvent
|
||||
every { fakeGetRoomLiveVoiceBroadcastsUseCase.execute(A_ROOM_ID) } returns emptyList()
|
||||
|
||||
// When
|
||||
val result = getLatestPreviewableEventUseCase.execute(A_ROOM_ID)
|
||||
|
||||
// Then
|
||||
result.shouldBeNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given the latest event is an ended vb, when execute, returns the stopped event`() {
|
||||
// Given
|
||||
val aLatestPreviewableEvent = mockk<TimelineEvent> {
|
||||
every { eventId } returns "id1"
|
||||
every { root } returns givenAVoiceBroadcastEvent("id1", VoiceBroadcastState.STOPPED, "vb_id1")
|
||||
}
|
||||
every { fakeRoomSummary.latestPreviewableEvent } returns aLatestPreviewableEvent
|
||||
every { fakeGetRoomLiveVoiceBroadcastsUseCase.execute(A_ROOM_ID) } returns emptyList()
|
||||
|
||||
// When
|
||||
val result = getLatestPreviewableEventUseCase.execute(A_ROOM_ID)
|
||||
|
||||
// Then
|
||||
result?.eventId shouldBeEqualTo "id1"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given the latest event is a resumed vb, when execute, returns the started event`() {
|
||||
// Given
|
||||
val aLatestPreviewableEvent = mockk<TimelineEvent> {
|
||||
every { eventId } returns "id1"
|
||||
every { root } returns givenAVoiceBroadcastEvent("id1", VoiceBroadcastState.RESUMED, "vb_id1")
|
||||
}
|
||||
every { fakeRoomSummary.latestPreviewableEvent } returns aLatestPreviewableEvent
|
||||
every { fakeGetRoomLiveVoiceBroadcastsUseCase.execute(A_ROOM_ID) } returns emptyList()
|
||||
|
||||
// When
|
||||
val result = getLatestPreviewableEventUseCase.execute(A_ROOM_ID)
|
||||
|
||||
// Then
|
||||
result?.eventId shouldBeEqualTo "vb_id1"
|
||||
}
|
||||
|
||||
private fun givenAVoiceBroadcastEvent(
|
||||
eventId: String,
|
||||
state: VoiceBroadcastState,
|
||||
voiceBroadcastId: String,
|
||||
): Event = mockk {
|
||||
every { this@mockk.eventId } returns eventId
|
||||
every { getClearType() } returns VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO
|
||||
every { type } returns VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO
|
||||
every { content } returns mapOf(
|
||||
"state" to state.value,
|
||||
"m.relates_to" to mapOf(
|
||||
"rel_type" to RelationType.REFERENCE,
|
||||
"event_id" to voiceBroadcastId
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* 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.voicebroadcast.usecase
|
||||
|
||||
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
|
||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcast
|
||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
||||
import im.vector.app.test.fakes.FakeSession
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import org.amshove.kluent.shouldBeEqualTo
|
||||
import org.amshove.kluent.shouldBeNull
|
||||
import org.amshove.kluent.shouldNotBeNull
|
||||
import org.junit.Test
|
||||
import org.matrix.android.sdk.api.session.getRoom
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||
|
||||
private const val A_ROOM_ID = "A_ROOM_ID"
|
||||
private const val A_VOICE_BROADCAST_ID = "A_VOICE_BROADCAST_ID"
|
||||
|
||||
internal class GetVoiceBroadcastStateEventUseCaseTest {
|
||||
|
||||
private val fakeSession = FakeSession()
|
||||
private val getVoiceBroadcastStateEventUseCase = GetVoiceBroadcastStateEventUseCase(fakeSession)
|
||||
|
||||
@Test
|
||||
fun `given there is no event related to the given vb, when execute, then return null`() {
|
||||
// Given
|
||||
val aVoiceBroadcast = VoiceBroadcast(A_VOICE_BROADCAST_ID, A_ROOM_ID)
|
||||
every { fakeSession.getRoom(A_ROOM_ID)?.timelineService()?.getTimelineEvent(A_VOICE_BROADCAST_ID) } returns null
|
||||
every { fakeSession.getRoom(A_ROOM_ID)?.timelineService()?.getTimelineEventsRelatedTo(any(), any()) } returns emptyList()
|
||||
|
||||
// When
|
||||
val result = getVoiceBroadcastStateEventUseCase.execute(aVoiceBroadcast)
|
||||
|
||||
// Then
|
||||
result.shouldBeNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given there are several related events related to the given vb, when execute, then return the most recent one`() {
|
||||
// Given
|
||||
val aVoiceBroadcast = VoiceBroadcast(A_VOICE_BROADCAST_ID, A_ROOM_ID)
|
||||
val aListOfTimelineEvents = listOf(
|
||||
givenAVoiceBroadcastEvent(eventId = A_VOICE_BROADCAST_ID, state = VoiceBroadcastState.STARTED, isRedacted = false, timestamp = 1L),
|
||||
givenAVoiceBroadcastEvent(eventId = "event_id_3", state = VoiceBroadcastState.STOPPED, isRedacted = false, timestamp = 3L),
|
||||
givenAVoiceBroadcastEvent(eventId = "event_id_2", state = VoiceBroadcastState.PAUSED, isRedacted = false, timestamp = 2L),
|
||||
)
|
||||
every { fakeSession.getRoom(A_ROOM_ID)?.timelineService()?.getTimelineEventsRelatedTo(any(), any()) } returns aListOfTimelineEvents
|
||||
|
||||
// When
|
||||
val result = getVoiceBroadcastStateEventUseCase.execute(aVoiceBroadcast)
|
||||
|
||||
// Then
|
||||
result.shouldNotBeNull()
|
||||
result.root.eventId shouldBeEqualTo "event_id_3"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given there are several related events related to the given vb, when execute, then return the most recent one which is not redacted`() {
|
||||
// Given
|
||||
val aVoiceBroadcast = VoiceBroadcast(A_VOICE_BROADCAST_ID, A_ROOM_ID)
|
||||
val aListOfTimelineEvents = listOf(
|
||||
givenAVoiceBroadcastEvent(eventId = A_VOICE_BROADCAST_ID, state = VoiceBroadcastState.STARTED, isRedacted = false, timestamp = 1L),
|
||||
givenAVoiceBroadcastEvent(eventId = "event_id_2", state = VoiceBroadcastState.STOPPED, isRedacted = true, timestamp = 2L),
|
||||
)
|
||||
every { fakeSession.getRoom(A_ROOM_ID)?.timelineService()?.getTimelineEventsRelatedTo(any(), any()) } returns aListOfTimelineEvents
|
||||
|
||||
// When
|
||||
val result = getVoiceBroadcastStateEventUseCase.execute(aVoiceBroadcast)
|
||||
|
||||
// Then
|
||||
result.shouldNotBeNull()
|
||||
result.root.eventId shouldBeEqualTo A_VOICE_BROADCAST_ID
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given a not ended voice broadcast with a redacted start event, when execute, then return null`() {
|
||||
// Given
|
||||
val aVoiceBroadcast = VoiceBroadcast(A_VOICE_BROADCAST_ID, A_ROOM_ID)
|
||||
val aListOfTimelineEvents = listOf(
|
||||
givenAVoiceBroadcastEvent(eventId = A_VOICE_BROADCAST_ID, state = VoiceBroadcastState.STARTED, isRedacted = true, timestamp = 1L),
|
||||
givenAVoiceBroadcastEvent(eventId = "event_id_2", state = VoiceBroadcastState.PAUSED, isRedacted = false, timestamp = 2L),
|
||||
givenAVoiceBroadcastEvent(eventId = "event_id_3", state = VoiceBroadcastState.RESUMED, isRedacted = false, timestamp = 3L),
|
||||
)
|
||||
every { fakeSession.getRoom(A_ROOM_ID)?.timelineService()?.getTimelineEventsRelatedTo(any(), any()) } returns aListOfTimelineEvents
|
||||
|
||||
// When
|
||||
val result = getVoiceBroadcastStateEventUseCase.execute(aVoiceBroadcast)
|
||||
|
||||
// Then
|
||||
result.shouldBeNull()
|
||||
}
|
||||
|
||||
private fun givenAVoiceBroadcastEvent(
|
||||
eventId: String,
|
||||
state: VoiceBroadcastState,
|
||||
isRedacted: Boolean,
|
||||
timestamp: Long,
|
||||
): TimelineEvent {
|
||||
val timelineEvent = mockk<TimelineEvent> {
|
||||
every { root.eventId } returns eventId
|
||||
every { root.type } returns VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO
|
||||
every { root.content } returns mapOf("state" to state.value)
|
||||
every { root.isRedacted() } returns isRedacted
|
||||
every { root.originServerTs } returns timestamp
|
||||
}
|
||||
every { fakeSession.getRoom(A_ROOM_ID)?.timelineService()?.getTimelineEvent(eventId) } returns timelineEvent
|
||||
return timelineEvent
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright (c) 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.test.fakes
|
||||
|
||||
import im.vector.app.core.resources.DrawableProvider
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
|
||||
class FakeDrawableProvider {
|
||||
val instance = mockk<DrawableProvider>()
|
||||
|
||||
init {
|
||||
every { instance.getDrawable(any()) } returns mockk()
|
||||
every { instance.getDrawable(any(), any()) } returns mockk()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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.test.fakes
|
||||
|
||||
import im.vector.app.features.home.room.list.home.HomeLayoutPreferencesStore
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
|
||||
class FakeHomeLayoutPreferencesStore {
|
||||
|
||||
private val _areRecentsEnabledFlow = MutableSharedFlow<Boolean>()
|
||||
private val _areFiltersEnabledFlow = MutableSharedFlow<Boolean>()
|
||||
private val _isAZOrderingEnabledFlow = MutableSharedFlow<Boolean>()
|
||||
|
||||
val instance = mockk<HomeLayoutPreferencesStore>(relaxed = true) {
|
||||
every { areRecentsEnabledFlow } returns _areRecentsEnabledFlow
|
||||
every { areFiltersEnabledFlow } returns _areFiltersEnabledFlow
|
||||
every { isAZOrderingEnabledFlow } returns _isAZOrderingEnabledFlow
|
||||
}
|
||||
|
||||
suspend fun givenRecentsEnabled(enabled: Boolean) {
|
||||
_areRecentsEnabledFlow.emit(enabled)
|
||||
}
|
||||
|
||||
suspend fun givenFiltersEnabled(enabled: Boolean) {
|
||||
_areFiltersEnabledFlow.emit(enabled)
|
||||
}
|
||||
}
|
|
@ -30,4 +30,8 @@ class FakeRoomService(
|
|||
fun getRoomSummaryReturns(roomSummary: RoomSummary?) {
|
||||
every { getRoomSummary(any()) } returns roomSummary
|
||||
}
|
||||
|
||||
fun set(roomSummary: RoomSummary?) {
|
||||
every { getRoomSummary(any()) } returns roomSummary
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,6 +42,7 @@ class FakeSession(
|
|||
val fakeSharedSecretStorageService: FakeSharedSecretStorageService = FakeSharedSecretStorageService(),
|
||||
val fakeRoomService: FakeRoomService = FakeRoomService(),
|
||||
val fakePushersService: FakePushersService = FakePushersService(),
|
||||
val fakeUserService: FakeUserService = FakeUserService(),
|
||||
private val fakeEventService: FakeEventService = FakeEventService(),
|
||||
val fakeSessionAccountDataService: FakeSessionAccountDataService = FakeSessionAccountDataService()
|
||||
) : Session by mockk(relaxed = true) {
|
||||
|
@ -62,6 +63,7 @@ class FakeSession(
|
|||
override fun eventService() = fakeEventService
|
||||
override fun pushersService() = fakePushersService
|
||||
override fun accountDataService() = fakeSessionAccountDataService
|
||||
override fun userService() = fakeUserService
|
||||
|
||||
fun givenVectorStore(vectorSessionStore: VectorSessionStore) {
|
||||
coEvery {
|
||||
|
@ -92,8 +94,10 @@ class FakeSession(
|
|||
/**
|
||||
* Do not forget to call mockkStatic("org.matrix.android.sdk.flow.FlowSessionKt") in the setup method of the tests.
|
||||
*/
|
||||
@SuppressWarnings("all")
|
||||
fun givenFlowSession(): FlowSession {
|
||||
val fakeFlowSession = mockk<FlowSession>()
|
||||
|
||||
every { flow() } returns fakeFlowSession
|
||||
return fakeFlowSession
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package im.vector.app.test.fakes
|
||||
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import io.mockk.InternalPlatformDsl.toStr
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
|
||||
|
@ -27,6 +28,9 @@ class FakeStringProvider {
|
|||
every { instance.getString(any()) } answers {
|
||||
"test-${args[0]}"
|
||||
}
|
||||
every { instance.getString(any(), any()) } answers {
|
||||
"test-${args[0]}-${args[1].toStr()}"
|
||||
}
|
||||
|
||||
every { instance.getQuantityString(any(), any(), any()) } answers {
|
||||
"test-${args[0]}-${args[1]}"
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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.test.fakes
|
||||
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.slot
|
||||
import org.matrix.android.sdk.api.session.user.UserService
|
||||
import org.matrix.android.sdk.api.session.user.model.User
|
||||
|
||||
class FakeUserService : UserService by mockk() {
|
||||
|
||||
private val userIdSlot = slot<String>()
|
||||
|
||||
init {
|
||||
every { getUser(capture(userIdSlot)) } answers { User(userId = userIdSlot.captured) }
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue