diff --git a/.github/workflows/danger.yml b/.github/workflows/danger.yml
index 8752f339bd..4901a84070 100644
--- a/.github/workflows/danger.yml
+++ b/.github/workflows/danger.yml
@@ -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:
diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml
index fae8d97688..c32cb65c42 100644
--- a/.github/workflows/quality.yml
+++ b/.github/workflows/quality.yml
@@ -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:
diff --git a/CHANGES.md b/CHANGES.md
index e742d79c1e..15b0a76b23 100644
--- a/CHANGES.md
+++ b/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)
=======================================
diff --git a/Gemfile.lock b/Gemfile.lock
index 276f4ae66a..33ebbc1b70 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -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)
diff --git a/build.gradle b/build.gradle
index cdbfcc44b7..53b7a983ec 100644
--- a/build.gradle
+++ b/build.gradle
@@ -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"
@@ -48,7 +48,7 @@ plugins {
id "com.google.devtools.ksp" version "1.7.22-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"
}
diff --git a/changelog.d/5546.bugfix b/changelog.d/5546.bugfix
deleted file mode 100644
index a3ff48a4a2..0000000000
--- a/changelog.d/5546.bugfix
+++ /dev/null
@@ -1 +0,0 @@
-ReplyTo are not updated if the original message is edited or deleted.
diff --git a/changelog.d/7724.bugfix b/changelog.d/7724.bugfix
deleted file mode 100644
index 685f7ad4e2..0000000000
--- a/changelog.d/7724.bugfix
+++ /dev/null
@@ -1 +0,0 @@
- Observe ViewEvents only when resumed and ensure ViewEvents are not lost.
diff --git a/changelog.d/7853.bugfix b/changelog.d/7853.bugfix
deleted file mode 100644
index 885233553e..0000000000
--- a/changelog.d/7853.bugfix
+++ /dev/null
@@ -1 +0,0 @@
-[Session manager] Missing info when a session does not support encryption
diff --git a/changelog.d/7864.wip b/changelog.d/7864.wip
deleted file mode 100644
index e1187ee1e7..0000000000
--- a/changelog.d/7864.wip
+++ /dev/null
@@ -1,2 +0,0 @@
-[Poll] Render active polls list of a room
-[Poll] Render past polls list of a room
diff --git a/changelog.d/7879.bugfix b/changelog.d/7879.bugfix
deleted file mode 100644
index be828ec2cc..0000000000
--- a/changelog.d/7879.bugfix
+++ /dev/null
@@ -1 +0,0 @@
-Reduce number of crypto database transactions when handling the sync response
diff --git a/changelog.d/7887.feature b/changelog.d/7887.feature
deleted file mode 100644
index 1f1c29761a..0000000000
--- a/changelog.d/7887.feature
+++ /dev/null
@@ -1 +0,0 @@
-"[Rich text editor] Add list formatting buttons to the rich text editor"
\ No newline at end of file
diff --git a/changelog.d/7899.bugfix b/changelog.d/7899.bugfix
deleted file mode 100644
index d95af29d8d..0000000000
--- a/changelog.d/7899.bugfix
+++ /dev/null
@@ -1 +0,0 @@
-[Voice Broadcast] Stop listening if we reach the last received chunk and there is no last sequence number
diff --git a/changelog.d/7900.feature b/changelog.d/7900.feature
new file mode 100644
index 0000000000..c3cce1e0e6
--- /dev/null
+++ b/changelog.d/7900.feature
@@ -0,0 +1 @@
+Render ended polls
diff --git a/changelog.d/7913.bugfix b/changelog.d/7913.bugfix
deleted file mode 100644
index 32b821f14d..0000000000
--- a/changelog.d/7913.bugfix
+++ /dev/null
@@ -1 +0,0 @@
-Handle network error on API `rooms/{roomId}/threads`
diff --git a/changelog.d/7930.feature b/changelog.d/7930.feature
new file mode 100644
index 0000000000..7eb779e6ec
--- /dev/null
+++ b/changelog.d/7930.feature
@@ -0,0 +1 @@
+"[Rich text editor] Update list item bullet appearance"
\ No newline at end of file
diff --git a/dependencies.gradle b/dependencies.gradle
index e970457e7c..25785e984e 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -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,7 +27,7 @@ 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 sentry = "6.11.0"
def fragment = "1.5.5"
// 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
@@ -60,7 +60,7 @@ ext.libs = [
'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 +86,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 +101,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",
diff --git a/fastlane/metadata/android/en-US/changelogs/40105200.txt b/fastlane/metadata/android/en-US/changelogs/40105200.txt
new file mode 100644
index 0000000000..6f549d094a
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/40105200.txt
@@ -0,0 +1,2 @@
+Main changes in this version: Mainly bugfixing!
+Full changelog: https://github.com/vector-im/element-android/releases
diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml
index 2ce6f13b97..658b08e36d 100644
--- a/library/ui-strings/src/main/res/values/strings.xml
+++ b/library/ui-strings/src/main/res/values/strings.xml
@@ -3181,7 +3181,8 @@
- Final result based on %1$d votes
End poll
- winner option
+
+ winner option
End this poll?
This will stop people from being able to vote and will display the final results of the poll.
End poll
@@ -3195,6 +3196,7 @@
Voters see results as soon as they have voted
Closed poll
Results are only revealed when you end the poll
+ Ended the poll.
Active polls
There are no active polls in this room
Past polls
@@ -3512,6 +3514,9 @@
sent a video.
sent a sticker.
created a poll.
+ ended a poll.
+ Poll
+ Ended poll
Access Token
Your access token gives full access to your account. Do not share it with anyone.
diff --git a/library/ui-styles/src/main/res/values/styles_edit_text.xml b/library/ui-styles/src/main/res/values/styles_edit_text.xml
index 94f4d86160..6b282a7674 100644
--- a/library/ui-styles/src/main/res/values/styles_edit_text.xml
+++ b/library/ui-styles/src/main/res/values/styles_edit_text.xml
@@ -22,6 +22,7 @@
- false
- 15sp
- ?vctr_message_text_color
+ - 20sp
diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle
index d132158615..ef6cb8cddb 100644
--- a/matrix-sdk-android/build.gradle
+++ b/matrix-sdk-android/build.gradle
@@ -62,7 +62,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()}\""
diff --git a/matrix-sdk-android/src/androidTest/AndroidManifest.xml b/matrix-sdk-android/src/androidTest/AndroidManifest.xml
index 40360fcd19..859ebbd238 100644
--- a/matrix-sdk-android/src/androidTest/AndroidManifest.xml
+++ b/matrix-sdk-android/src/androidTest/AndroidManifest.xml
@@ -1,6 +1,5 @@
+ xmlns:tools="http://schemas.android.com/tools">
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
index 9a928c61fb..40c69ceb66 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
@@ -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("") ?: false
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageEndPollContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageEndPollContent.kt
index f0511903d0..6e31320b13 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageEndPollContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageEndPollContent.kt
@@ -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
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt
index e97a5be303..f6b7675d4f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt
@@ -36,6 +36,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"
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt
index 9053425a39..6320ea964d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt
@@ -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 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()
+ in EventType.POLL_END.values -> (getLastEditNewContent() ?: root.getClearContent()).toModel()
in EventType.STATE_ROOM_BEACON_INFO.values -> (getLastEditNewContent() ?: root.getClearContent()).toModel()
in EventType.BEACON_LOCATION_DATA.values -> (getLastEditNewContent() ?: root.getClearContent()).toModel()
else -> (getLastEditNewContent() ?: root.getClearContent()).toModel()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt
index 468e998407..0a8c58de16 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt
@@ -69,7 +69,7 @@ internal class DefaultLoginWizard(
)
} else {
PasswordLoginParams.userIdentifier(
- user = login,
+ user = login.trim(),
password = password,
deviceDisplayName = initialDeviceName,
deviceId = deviceId
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushRulesResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushRulesResponse.kt
index 5f35c919fc..e359410f17 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushRulesResponse.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushRulesResponse.kt
@@ -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
)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/SavePushRulesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/SavePushRulesTask.kt
index 88c78aa460..4a46f56a70 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/SavePushRulesTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/SavePushRulesTask.kt
@@ -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 }
diff --git a/tools/release/releaseScript.sh b/tools/release/releaseScript.sh
index 553c02101c..f9f5303546 100755
--- a/tools/release/releaseScript.sh
+++ b/tools/release/releaseScript.sh
@@ -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
diff --git a/vector-app/build.gradle b/vector-app/build.gradle
index e157f0704a..11119a75cc 100644
--- a/vector-app/build.gradle
+++ b/vector-app/build.gradle
@@ -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'
diff --git a/vector/build.gradle b/vector/build.gradle
index 91d2a8c46a..2224634194 100644
--- a/vector/build.gradle
+++ b/vector/build.gradle
@@ -132,7 +132,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
diff --git a/vector/src/main/java/im/vector/app/core/extensions/TimelineEvent.kt b/vector/src/main/java/im/vector/app/core/extensions/TimelineEvent.kt
index c94f9cd921..89bd28fc93 100644
--- a/vector/src/main/java/im/vector/app/core/extensions/TimelineEvent.kt
+++ b/vector/src/main/java/im/vector/app/core/extensions/TimelineEvent.kt
@@ -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()
}
diff --git a/vector/src/main/java/im/vector/app/core/utils/SharedEvent.kt b/vector/src/main/java/im/vector/app/core/utils/SharedEvent.kt
index e712769c48..081a4f6192 100644
--- a/vector/src/main/java/im/vector/app/core/utils/SharedEvent.kt
+++ b/vector/src/main/java/im/vector/app/core/utils/SharedEvent.kt
@@ -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 {
+interface SharedEvents {
fun stream(consumerId: String): Flow
}
-class EventQueue(capacity: Int) : SharedEvents {
+class EventQueue(capacity: Int) : SharedEvents {
private val innerQueue = MutableSharedFlow>(replay = capacity)
@@ -33,7 +35,12 @@ class EventQueue(capacity: Int) : SharedEvents {
innerQueue.tryEmit(OneTimeEvent(event))
}
- override fun stream(consumerId: String): Flow = innerQueue.filterNotHandledBy(consumerId)
+ override fun stream(consumerId: String): Flow = innerQueue
+ .onEach {
+ // Ensure that buffered Events will not be sent again to new subscribers.
+ innerQueue.resetReplayCache()
+ }
+ .filterNotHandledBy(consumerId)
}
/**
@@ -42,7 +49,7 @@ class EventQueue(capacity: Int) : SharedEvents {
*
* Keeps track of who has already handled its content.
*/
-private class OneTimeEvent(private val content: T) {
+private class OneTimeEvent(private val content: T) {
private val handlers = CopyOnWriteArraySet()
@@ -53,6 +60,6 @@ private class OneTimeEvent(private val content: T) {
fun getIfNotHandled(asker: String): T? = if (handlers.add(asker)) content else null
}
-private fun Flow>.filterNotHandledBy(consumerId: String): Flow = transform { event ->
+private fun Flow>.filterNotHandledBy(consumerId: String): Flow = transform { event ->
event.getIfNotHandled(consumerId)?.let { emit(it) }
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/PlainTextComposerLayout.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/PlainTextComposerLayout.kt
index 8f4dd9b71d..cf127d834f 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/PlainTextComposerLayout.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/PlainTextComposerLayout.kt
@@ -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
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanReplyEventUseCase.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanReplyEventUseCase.kt
index a9df059cc1..fdd94d1559 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanReplyEventUseCase.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanReplyEventUseCase.kt
@@ -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
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
index a6d7e8386f..646cfa50d2 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
@@ -498,6 +498,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
}
@@ -529,8 +530,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
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
index 42e031a3c4..219ccbe11c 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
@@ -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() ?: return null
+
+ return buildPollItem(
+ pollContent,
+ informationData,
+ highlight,
+ callback,
+ attributes,
+ isEnded = true
+ )
+ }
+
private fun createPollQuestion(
informationData: MessageInformationData,
question: String,
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt
index ae3ea143a7..61b2385d1d 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt
@@ -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)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/EventDetailsFormatter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/EventDetailsFormatter.kt
index 2233a53eda..1d3f016951 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/EventDetailsFormatter.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/EventDetailsFormatter.kt
@@ -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".
*/
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt
index 57a4388f74..3ee309425a 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt
@@ -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
@@ -54,7 +52,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 {
@@ -99,20 +98,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 ->
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/PollResponseDataFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/PollResponseDataFactory.kt
new file mode 100644
index 0000000000..533397b4d8
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/PollResponseDataFactory.kt
@@ -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
+ }
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt
index 51e961f247..2dcb6cc6d8 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt
@@ -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
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt
index 3a9d21dfc4..072c3dcd27 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt
@@ -85,7 +85,7 @@ abstract class MessageTextItem : AbsMessageItem() {
if (useBigFont) {
holder.messageView.textSize = 44F
} else {
- holder.messageView.textSize = 14F
+ holder.messageView.textSize = 15.5F
}
if (searchForPills) {
message?.charSequence?.findPillsAndProcess(coroutineScope) {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt
index 54be4092ed..6fe19e9762 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt
@@ -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() {
@EpoxyAttribute
lateinit var optionViewStates: List
+ @EpoxyAttribute
+ var ended: Boolean = false
+
override fun getViewStubId() = STUB_ID
override fun bind(holder: Holder) {
@@ -75,6 +79,8 @@ abstract class PollItem : AbsMessageItem() {
it.setOnClickListener { onPollItemClick(optionViewState) }
}
}
+
+ holder.endedPollTextView.isVisible = ended
}
private fun onPollItemClick(optionViewState: PollOptionViewState) {
@@ -89,6 +95,7 @@ abstract class PollItem : AbsMessageItem() {
val questionTextView by bind(R.id.questionTextView)
val optionsContainer by bind(R.id.optionsContainer)
val votesStatusTextView by bind(R.id.optionsVotesStatusTextView)
+ val endedPollTextView by bind(R.id.endedPollTextView)
}
companion object {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollOptionView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollOptionView.kt
index 20aa6e3af2..e8d636e20b 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollOptionView.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollOptionView.kt
@@ -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)
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCase.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCase.kt
index 2197d89a2c..ff814d4cbc 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCase.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCase.kt
@@ -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() -> {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt
index c207a5f67e..6e34aeeca2 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt
@@ -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
diff --git a/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt b/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt
index 27a02d87a8..4da022d4bb 100644
--- a/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/login/LoginViewModel.kt
@@ -686,7 +686,7 @@ class LoginViewModel @AssistedInject constructor(
currentJob = viewModelScope.launch {
try {
safeLoginWizard.login(
- action.username,
+ action.username.trim(),
action.password,
action.initialDeviceName
)
diff --git a/vector/src/main/res/layout/composer_rich_text_layout.xml b/vector/src/main/res/layout/composer_rich_text_layout.xml
index 7cc2d48cda..8992b632c0 100644
--- a/vector/src/main/res/layout/composer_rich_text_layout.xml
+++ b/vector/src/main/res/layout/composer_rich_text_layout.xml
@@ -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" />
-
-
@@ -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" />
-
\ No newline at end of file
+
diff --git a/vector/src/main/res/layout/item_timeline_event_poll.xml b/vector/src/main/res/layout/item_timeline_event_poll.xml
index 393b736260..9151fc68cf 100644
--- a/vector/src/main/res/layout/item_timeline_event_poll.xml
+++ b/vector/src/main/res/layout/item_timeline_event_poll.xml
@@ -2,9 +2,21 @@
+ android:layout_height="wrap_content"
+ android:minWidth="@dimen/chat_bubble_fixed_size">
+
+
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
)
diff --git a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCaseTest.kt
index f612861511..c38afe20ec 100644
--- a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCaseTest.kt
+++ b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/render/ProcessBodyOfReplyToEventUseCaseTest.kt
@@ -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
diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeDrawableProvider.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeDrawableProvider.kt
new file mode 100644
index 0000000000..26fa7af3f5
--- /dev/null
+++ b/vector/src/test/java/im/vector/app/test/fakes/FakeDrawableProvider.kt
@@ -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()
+
+ init {
+ every { instance.getDrawable(any()) } returns mockk()
+ every { instance.getDrawable(any(), any()) } returns mockk()
+ }
+}
diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeHomeLayoutPreferencesStore.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeHomeLayoutPreferencesStore.kt
new file mode 100644
index 0000000000..bd5dd20d37
--- /dev/null
+++ b/vector/src/test/java/im/vector/app/test/fakes/FakeHomeLayoutPreferencesStore.kt
@@ -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()
+ private val _areFiltersEnabledFlow = MutableSharedFlow()
+ private val _isAZOrderingEnabledFlow = MutableSharedFlow()
+
+ val instance = mockk(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)
+ }
+}
diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeRoomService.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeRoomService.kt
index 506e96ba11..e957266383 100644
--- a/vector/src/test/java/im/vector/app/test/fakes/FakeRoomService.kt
+++ b/vector/src/test/java/im/vector/app/test/fakes/FakeRoomService.kt
@@ -30,4 +30,8 @@ class FakeRoomService(
fun getRoomSummaryReturns(roomSummary: RoomSummary?) {
every { getRoomSummary(any()) } returns roomSummary
}
+
+ fun set(roomSummary: RoomSummary?) {
+ every { getRoomSummary(any()) } returns roomSummary
+ }
}
diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt
index e368fbbcf2..a05dce9c54 100644
--- a/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt
+++ b/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt
@@ -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()
+
every { flow() } returns fakeFlowSession
return fakeFlowSession
}
diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeStringProvider.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeStringProvider.kt
index 28d9f7c732..83f8607261 100644
--- a/vector/src/test/java/im/vector/app/test/fakes/FakeStringProvider.kt
+++ b/vector/src/test/java/im/vector/app/test/fakes/FakeStringProvider.kt
@@ -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]}"
diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeUserService.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeUserService.kt
new file mode 100644
index 0000000000..065796934c
--- /dev/null
+++ b/vector/src/test/java/im/vector/app/test/fakes/FakeUserService.kt
@@ -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()
+
+ init {
+ every { getUser(capture(userIdSlot)) } answers { User(userId = userIdSlot.captured) }
+ }
+}