From 9df8009ae3fd02ce3622e227e16376d76afe6696 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Mon, 4 Jul 2022 21:07:20 +0200 Subject: [PATCH 001/189] Implement the current spec for event match conditions This fixes that people randomly get pinged on every reply to a user names @roomba:server.tld. fixes #2541 Signed-off-by: Nicolas Werner --- .../session/pushrules/EventMatchCondition.kt | 31 ++++++++----------- .../session/pushrules/rest/PushCondition.kt | 2 +- .../pushrules/PushRulesConditionTest.kt | 19 ++++++++++-- 3 files changed, 31 insertions(+), 21 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/EventMatchCondition.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/EventMatchCondition.kt index 8875807b8a..4b11bfcdd7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/EventMatchCondition.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/EventMatchCondition.kt @@ -31,18 +31,14 @@ class EventMatchCondition( * The glob-style pattern to match against. Patterns with no special glob characters should * be treated as having asterisks prepended and appended when testing the condition. */ - val pattern: String, - /** - * true to match only words. In this case pattern will not be considered as a glob - */ - val wordsOnly: Boolean + val pattern: String ) : Condition { override fun isSatisfied(event: Event, conditionResolver: ConditionResolver): Boolean { return conditionResolver.resolveEventMatchCondition(event, this) } - override fun technicalDescription() = "'$key' matches '$pattern', words only '$wordsOnly'" + override fun technicalDescription() = "'$key' matches '$pattern'" fun isSatisfied(event: Event): Boolean { // TODO encrypted events? @@ -50,21 +46,20 @@ class EventMatchCondition( ?: return false val value = extractField(rawJson, key) ?: return false - // Patterns with no special glob characters should be treated as having asterisks prepended - // and appended when testing the condition. + // The match is performed case-insensitively, and must match the entire value of + // the event field given by `key` (though see below regarding `content.body`). The + // exact meaning of "case insensitive" is defined by the implementation of the + // homeserver. + // + // As a special case, if `key` is `content.body`, then `pattern` must instead + // match any substring of the value of the property which starts and ends at a + // word boundary. return try { - if (wordsOnly) { + if (key == 'content.body') { value.caseInsensitiveFind(pattern) } else { - val modPattern = if (pattern.hasSpecialGlobChar()) { - // Regex.containsMatchIn() is way faster without leading and trailing - // stars, that don't make any difference for the evaluation result - pattern.removePrefix("*").removeSuffix("*").simpleGlobToRegExp() - } else { - pattern.simpleGlobToRegExp() - } - val regex = Regex(modPattern, RegexOption.DOT_MATCHES_ALL) - regex.containsMatchIn(value) + val regex = Regex(pattern.simpleGlobToRegExp(), setOf(RegexOption.DOT_MATCHES_ALL, RegexOption.IGNORE_CASE)) + regex.matches(value) } } catch (e: Throwable) { // e.g PatternSyntaxException diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/PushCondition.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/PushCondition.kt index ec0936e4c8..c2b45619e5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/PushCondition.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/PushCondition.kt @@ -63,7 +63,7 @@ data class PushCondition( return when (Kind.fromString(kind)) { Kind.EventMatch -> { if (key != null && pattern != null) { - EventMatchCondition(key, pattern, rule.ruleId == RuleIds.RULE_ID_CONTAIN_USER_NAME) + EventMatchCondition(key, pattern) } else { Timber.e("Malformed Event match condition") null diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/session/pushrules/PushRulesConditionTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/session/pushrules/PushRulesConditionTest.kt index c4a3404e80..ef6230dbbc 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/session/pushrules/PushRulesConditionTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/session/pushrules/PushRulesConditionTest.kt @@ -67,7 +67,7 @@ class PushRulesConditionTest : MatrixTest { ) assert(condition.isSatisfied(simpleTextEvent)) - assert(!condition.isSatisfied(simpleRoomMemberEvent)) + assertFalse(condition.isSatisfied(simpleRoomMemberEvent)) } @Test @@ -98,7 +98,7 @@ class PushRulesConditionTest : MatrixTest { val condition = EventMatchCondition("content.body", "cake", false) assert(condition.isSatisfied(createSimpleTextEvent("How was the cake?"))) - assert(condition.isSatisfied(createSimpleTextEvent("Howwasthecake?"))) + assertFalse(condition.isSatisfied(createSimpleTextEvent("Howwasthecake?"))) } @Test @@ -124,6 +124,21 @@ class PushRulesConditionTest : MatrixTest { assert(condition.isSatisfied(createSimpleTextEvent("BEN"))) } + @Test + fun test_eventmatch_at_room_condition() { + val condition = EventMatchCondition("content.body", "@room", true) + + assertFalse(condition.isSatisfied(createSimpleTextEvent("@roomba"))) + assertFalse(condition.isSatisfied(createSimpleTextEvent("room benoit"))) + assertFalse(condition.isSatisfied(createSimpleTextEvent("abc@roomba"))) + + assert(condition.isSatisfied(createSimpleTextEvent("@room"))) + assert(condition.isSatisfied(createSimpleTextEvent("@room, ben"))) + assert(condition.isSatisfied(createSimpleTextEvent("@ROOM"))) + assert(condition.isSatisfied(createSimpleTextEvent("Use:@room"))) + assert(condition.isSatisfied(createSimpleTextEvent("Don't ping @room!"))) + } + @Test fun test_notice_condition() { val conditionEqual = EventMatchCondition("content.msgtype", "m.notice", false) From 48fc634825422e9eca5b4b839ff60d06148f2414 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Mon, 4 Jul 2022 21:33:21 +0200 Subject: [PATCH 002/189] Add changelog and fix condition not matching globs on body Signed-off-by: Nicolas Werner --- changelog.d/6457.bugfix | 1 + .../android/sdk/api/session/pushrules/EventMatchCondition.kt | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 changelog.d/6457.bugfix diff --git a/changelog.d/6457.bugfix b/changelog.d/6457.bugfix new file mode 100644 index 0000000000..89ba075378 --- /dev/null +++ b/changelog.d/6457.bugfix @@ -0,0 +1 @@ +Fix that replies to @roomba would be highlighted as a room ping. Contributed by Nico. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/EventMatchCondition.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/EventMatchCondition.kt index 4b11bfcdd7..fbedb0b2a0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/EventMatchCondition.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/EventMatchCondition.kt @@ -56,7 +56,8 @@ class EventMatchCondition( // word boundary. return try { if (key == 'content.body') { - value.caseInsensitiveFind(pattern) + val regex = Regex("(\\W|^)"+pattern.simpleGlobToRegExp() + "(\\W|$)", setOf(RegexOption.DOT_MATCHES_ALL, RegexOption.IGNORE_CASE)) + regex.containsMatchIn(value) } else { val regex = Regex(pattern.simpleGlobToRegExp(), setOf(RegexOption.DOT_MATCHES_ALL, RegexOption.IGNORE_CASE)) regex.matches(value) From b24b1a188490115f767c48f61c79d4fe8ca40863 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Mon, 4 Jul 2022 21:35:59 +0200 Subject: [PATCH 003/189] Add negative test for globs on body only matching full words Signed-off-by: Nicolas Werner --- .../android/sdk/api/session/pushrules/PushRulesConditionTest.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/session/pushrules/PushRulesConditionTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/session/pushrules/PushRulesConditionTest.kt index ef6230dbbc..7d64217843 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/session/pushrules/PushRulesConditionTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/session/pushrules/PushRulesConditionTest.kt @@ -106,6 +106,7 @@ class PushRulesConditionTest : MatrixTest { val condition = EventMatchCondition("content.body", "cake*lie", false) assert(condition.isSatisfied(createSimpleTextEvent("How was the cakeisalie?"))) + assertFalse(condition.isSatisfied(createSimpleTextEvent("How was the notcakeisalie?"))) } @Test From e980f6bb2f8bc1a54074c49754a2a014b7d44424 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Mon, 4 Jul 2022 21:51:49 +0200 Subject: [PATCH 004/189] kotlin is not dart Signed-off-by: Nicolas Werner --- .../android/sdk/api/session/pushrules/EventMatchCondition.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/EventMatchCondition.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/EventMatchCondition.kt index fbedb0b2a0..293ae6c043 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/EventMatchCondition.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/EventMatchCondition.kt @@ -55,8 +55,8 @@ class EventMatchCondition( // match any substring of the value of the property which starts and ends at a // word boundary. return try { - if (key == 'content.body') { - val regex = Regex("(\\W|^)"+pattern.simpleGlobToRegExp() + "(\\W|$)", setOf(RegexOption.DOT_MATCHES_ALL, RegexOption.IGNORE_CASE)) + if (key == "content.body") { + val regex = Regex("(\\W|^)" + pattern.simpleGlobToRegExp() + "(\\W|$)", setOf(RegexOption.DOT_MATCHES_ALL, RegexOption.IGNORE_CASE)) regex.containsMatchIn(value) } else { val regex = Regex(pattern.simpleGlobToRegExp(), setOf(RegexOption.DOT_MATCHES_ALL, RegexOption.IGNORE_CASE)) From 365ec8ef722f1efb769c3e79e6825ae812ff4e07 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Mon, 4 Jul 2022 21:57:56 +0200 Subject: [PATCH 005/189] Remove unused imports Signed-off-by: Nicolas Werner --- .../android/sdk/api/session/pushrules/EventMatchCondition.kt | 2 -- .../android/sdk/api/session/pushrules/rest/PushCondition.kt | 1 - 2 files changed, 3 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/EventMatchCondition.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/EventMatchCondition.kt index 293ae6c043..e5448322e6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/EventMatchCondition.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/EventMatchCondition.kt @@ -17,8 +17,6 @@ package org.matrix.android.sdk.api.session.pushrules import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.internal.di.MoshiProvider -import org.matrix.android.sdk.internal.util.caseInsensitiveFind -import org.matrix.android.sdk.internal.util.hasSpecialGlobChar import org.matrix.android.sdk.internal.util.simpleGlobToRegExp import timber.log.Timber diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/PushCondition.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/PushCondition.kt index c2b45619e5..0e175b47b9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/PushCondition.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/PushCondition.kt @@ -22,7 +22,6 @@ import org.matrix.android.sdk.api.session.pushrules.ContainsDisplayNameCondition import org.matrix.android.sdk.api.session.pushrules.EventMatchCondition import org.matrix.android.sdk.api.session.pushrules.Kind import org.matrix.android.sdk.api.session.pushrules.RoomMemberCountCondition -import org.matrix.android.sdk.api.session.pushrules.RuleIds import org.matrix.android.sdk.api.session.pushrules.SenderNotificationPermissionCondition import timber.log.Timber From 144d6c99a6b46989419abe2443e125a356746c05 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Mon, 4 Jul 2022 22:07:52 +0200 Subject: [PATCH 006/189] Fix rule parameter not needed anymore --- .../android/sdk/api/session/pushrules/rest/PushCondition.kt | 2 +- .../android/sdk/internal/session/pushrules/PushRuleFinder.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/PushCondition.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/PushCondition.kt index 0e175b47b9..1b53801d0b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/PushCondition.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/PushCondition.kt @@ -58,7 +58,7 @@ data class PushCondition( val iz: String? = null ) { - fun asExecutableCondition(rule: PushRule): Condition? { + fun asExecutableCondition(): Condition? { return when (Kind.fromString(kind)) { Kind.EventMatch -> { if (key != null && pattern != null) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/PushRuleFinder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/PushRuleFinder.kt index b9d06a934d..15e803bb91 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/PushRuleFinder.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/PushRuleFinder.kt @@ -28,7 +28,7 @@ internal class PushRuleFinder @Inject constructor( return rules.firstOrNull { rule -> // All conditions must hold true for an event in order to apply the action for the event. rule.enabled && rule.conditions?.all { - it.asExecutableCondition(rule)?.isSatisfied(event, conditionResolver) ?: false + it.asExecutableCondition()?.isSatisfied(event, conditionResolver) ?: false } ?: false } } From f4f9851edd88a618c3a44f187f7cde29e4dc7c3b Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Mon, 4 Jul 2022 22:21:22 +0200 Subject: [PATCH 007/189] Remove rule param also in files outside of the sdk --- .../java/im/vector/app/features/settings/push/PushRuleItem.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/settings/push/PushRuleItem.kt b/vector/src/main/java/im/vector/app/features/settings/push/PushRuleItem.kt index 5a1dd055bd..57b2595590 100644 --- a/vector/src/main/java/im/vector/app/features/settings/push/PushRuleItem.kt +++ b/vector/src/main/java/im/vector/app/features/settings/push/PushRuleItem.kt @@ -73,7 +73,7 @@ abstract class PushRuleItem : VectorEpoxyModel(R.layout.ite pushRule.conditions?.forEachIndexed { i, condition -> if (i > 0) description.append("\n") description.append( - condition.asExecutableCondition(pushRule)?.technicalDescription() + condition.asExecutableCondition()?.technicalDescription() ?: "UNSUPPORTED" ) } From bc20ad5cf1614bc2478e3287c04a7e269d771065 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Mon, 4 Jul 2022 22:53:24 +0200 Subject: [PATCH 008/189] Fix tests still passing the word match bool Signed-off-by: Nicolas Werner --- .../session/pushrules/PushRulesConditionTest.kt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/session/pushrules/PushRulesConditionTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/session/pushrules/PushRulesConditionTest.kt index 7d64217843..3ddf940241 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/session/pushrules/PushRulesConditionTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/session/pushrules/PushRulesConditionTest.kt @@ -49,7 +49,7 @@ class PushRulesConditionTest : MatrixTest { @Test fun test_eventmatch_type_condition() { - val condition = EventMatchCondition("type", "m.room.message", false) + val condition = EventMatchCondition("type", "m.room.message") val simpleTextEvent = createSimpleTextEvent("Yo wtf?") @@ -72,7 +72,7 @@ class PushRulesConditionTest : MatrixTest { @Test fun test_eventmatch_path_condition() { - val condition = EventMatchCondition("content.msgtype", "m.text", false) + val condition = EventMatchCondition("content.msgtype", "m.text") val simpleTextEvent = createSimpleTextEvent("Yo wtf?") @@ -89,13 +89,13 @@ class PushRulesConditionTest : MatrixTest { ).toContent(), originServerTs = 0 ).apply { - assert(EventMatchCondition("content.membership", "invite", false).isSatisfied(this)) + assert(EventMatchCondition("content.membership", "invite").isSatisfied(this)) } } @Test fun test_eventmatch_cake_condition() { - val condition = EventMatchCondition("content.body", "cake", false) + val condition = EventMatchCondition("content.body", "cake") assert(condition.isSatisfied(createSimpleTextEvent("How was the cake?"))) assertFalse(condition.isSatisfied(createSimpleTextEvent("Howwasthecake?"))) @@ -103,7 +103,7 @@ class PushRulesConditionTest : MatrixTest { @Test fun test_eventmatch_cakelie_condition() { - val condition = EventMatchCondition("content.body", "cake*lie", false) + val condition = EventMatchCondition("content.body", "cake*lie") assert(condition.isSatisfied(createSimpleTextEvent("How was the cakeisalie?"))) assertFalse(condition.isSatisfied(createSimpleTextEvent("How was the notcakeisalie?"))) @@ -111,7 +111,7 @@ class PushRulesConditionTest : MatrixTest { @Test fun test_eventmatch_words_only_condition() { - val condition = EventMatchCondition("content.body", "ben", true) + val condition = EventMatchCondition("content.body", "ben") assertFalse(condition.isSatisfied(createSimpleTextEvent("benoit"))) assertFalse(condition.isSatisfied(createSimpleTextEvent("Hello benoit"))) @@ -127,7 +127,7 @@ class PushRulesConditionTest : MatrixTest { @Test fun test_eventmatch_at_room_condition() { - val condition = EventMatchCondition("content.body", "@room", true) + val condition = EventMatchCondition("content.body", "@room") assertFalse(condition.isSatisfied(createSimpleTextEvent("@roomba"))) assertFalse(condition.isSatisfied(createSimpleTextEvent("room benoit"))) @@ -142,7 +142,7 @@ class PushRulesConditionTest : MatrixTest { @Test fun test_notice_condition() { - val conditionEqual = EventMatchCondition("content.msgtype", "m.notice", false) + val conditionEqual = EventMatchCondition("content.msgtype", "m.notice") Event( type = "m.room.message", From 4a383523e5d4d782ab8f8d8214f742c5c66919f8 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Thu, 21 Jul 2022 12:18:55 +0200 Subject: [PATCH 009/189] Bring back the body match optimization Signed-off-by: Nicolas Werner --- .../sdk/api/session/pushrules/EventMatchCondition.kt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/EventMatchCondition.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/EventMatchCondition.kt index e5448322e6..15d5cd3153 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/EventMatchCondition.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/EventMatchCondition.kt @@ -54,7 +54,14 @@ class EventMatchCondition( // word boundary. return try { if (key == "content.body") { - val regex = Regex("(\\W|^)" + pattern.simpleGlobToRegExp() + "(\\W|$)", setOf(RegexOption.DOT_MATCHES_ALL, RegexOption.IGNORE_CASE)) + val modPattern = if (pattern.startsWith("*") && pattern.endsWith("*")) { + // Regex.containsMatchIn() is way faster without leading and trailing + // stars, that don't make any difference for the evaluation result + pattern.removePrefix("*").removeSuffix("*").simpleGlobToRegExp() + } else { + "(\\W|^)" + pattern.simpleGlobToRegExp() + "(\\W|$)" + } + val regex = Regex(modPattern, setOf(RegexOption.DOT_MATCHES_ALL, RegexOption.IGNORE_CASE)) regex.containsMatchIn(value) } else { val regex = Regex(pattern.simpleGlobToRegExp(), setOf(RegexOption.DOT_MATCHES_ALL, RegexOption.IGNORE_CASE)) From fe4e0eb0d9fb876b0539160a77e6385b27175274 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Wed, 18 Jan 2023 17:38:56 +0300 Subject: [PATCH 010/189] Render poll question when rendering reply to a poll end event. --- .../session/room/send/LocalEchoEventFactory.kt | 15 ++++++++++++++- .../session/room/send/LocalEchoRepository.kt | 12 ++++++++++++ .../render/ProcessBodyOfReplyToEventUseCase.kt | 18 +++++++++++++++++- 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt index d974c597ac..25965cd291 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt @@ -612,7 +612,7 @@ internal class LocalEchoEventFactory @Inject constructor( val userId = eventReplied.root.senderId ?: return null val userLink = permalinkFactory.createPermalink(userId, false) ?: return null - val body = bodyForReply(eventReplied.getLastMessageContent(), eventReplied.isReply(), isRedactedEvent) + val body = bodyForReply(eventReplied, isRedactedEvent) // As we always supply formatted body for replies we should force the MarkdownParser to produce html. val finalReplyTextFormatted = replyTextFormatted?.toString() ?: markdownParser.parse(replyText, force = true, advanced = autoMarkdown).takeFormatted() @@ -724,6 +724,18 @@ internal class LocalEchoEventFactory @Inject constructor( } } + private fun bodyForReply(timelineEvent: TimelineEvent, isRedactedEvent: Boolean = false): TextContent { + val content = when (timelineEvent.root.getClearType()) { + in EventType.POLL_END.values -> { + localEchoRepository + .getRelatedPollEvent(timelineEvent) + ?.getLastMessageContent() + } + else -> timelineEvent.getLastMessageContent() + } + return bodyForReply(content, timelineEvent.isReply(), isRedactedEvent) + } + /** * Returns a TextContent used for the fallback event representation in a reply message. * In case of an edit of a reply the last content is not @@ -754,6 +766,7 @@ internal class LocalEchoEventFactory @Inject constructor( MessageType.MSGTYPE_POLL_START -> { return TextContent((content as? MessagePollContent)?.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "") } + MessageType.MSGTYPE_POLL_END -> return TextContent("Ended poll") else -> { return if (isRedactedEvent) { TextContent("message removed.") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoRepository.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoRepository.kt index 394cb8944f..4a5b394144 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoRepository.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoRepository.kt @@ -25,6 +25,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageType import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import org.matrix.android.sdk.api.session.room.timeline.getRelationContent import org.matrix.android.sdk.internal.database.RealmSessionProvider import org.matrix.android.sdk.internal.database.asyncTransaction import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper @@ -228,4 +229,15 @@ internal class LocalEchoRepository @Inject constructor( EventEntity.where(realm, eventId = rootThreadEventId).findFirst()?.threadSummaryLatestMessage?.eventId } ?: rootThreadEventId } + + fun getRelatedPollEvent(timelineEvent: TimelineEvent): TimelineEvent? { + val roomId = timelineEvent.roomId + val pollEventId = timelineEvent.getRelationContent()?.eventId ?: return null + + return realmSessionProvider.withRealm { realm -> + TimelineEventEntity.where(realm, roomId = roomId, eventId = pollEventId).findFirst()?.let { + timelineEventMapper.map(it) + } + } + } } 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 ff814d4cbc..d8c69de138 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 @@ -20,6 +20,7 @@ import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.resources.StringProvider import org.matrix.android.sdk.api.session.events.model.getPollQuestion +import org.matrix.android.sdk.api.session.events.model.getRelationContent 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 @@ -32,7 +33,9 @@ import org.matrix.android.sdk.api.session.events.model.isVideoMessage import org.matrix.android.sdk.api.session.events.model.isVoiceMessage 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.model.message.MessagePollContent import org.matrix.android.sdk.api.session.room.model.relation.ReplyToContent +import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent import javax.inject.Inject private const val IN_REPLY_TO = "In reply to" @@ -100,10 +103,23 @@ class ProcessBodyOfReplyToEventUseCase @Inject constructor( repliedToEvent.isPollEnd() -> stringProvider.getString(R.string.message_reply_to_sender_ended_poll) else -> "" } + val repliedText = when { + repliedToEvent.isPollEnd() -> { + val eventId = repliedToEvent.getRelationContent()?.eventId + val relatedPollContent = activeSessionHolder + .getSafeActiveSession() + ?.getRoom(repliedToEvent.roomId.orEmpty()) + ?.getTimelineEvent(eventId.orEmpty()) + ?.getLastMessageContent() as? MessagePollContent + + relatedPollContent?.getBestPollCreationInfo()?.question?.getBestQuestion() + } + else -> repliedToEvent.getPollQuestion() + } matrixFormattedBody.replaceRange( afterBreakingLineIndex, endOfBlockQuoteIndex, - repliedToEvent.getPollQuestion() ?: fallbackText + repliedText ?: fallbackText ) } repliedToEvent.isLiveLocation() -> { From 642ed613094cb82be2b5bbbc1ac65633d98473d2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Jan 2023 23:02:43 +0000 Subject: [PATCH 011/189] Bump paparazzi from 1.1.0 to 1.2.0 Bumps `paparazzi` from 1.1.0 to 1.2.0. Updates `paparazzi` from 1.1.0 to 1.2.0 - [Release notes](https://github.com/cashapp/paparazzi/releases) - [Changelog](https://github.com/cashapp/paparazzi/blob/master/CHANGELOG.md) - [Commits](https://github.com/cashapp/paparazzi/compare/1.1.0...1.2.0) Updates `paparazzi-gradle-plugin` from 1.1.0 to 1.2.0 - [Release notes](https://github.com/cashapp/paparazzi/releases) - [Changelog](https://github.com/cashapp/paparazzi/blob/master/CHANGELOG.md) - [Commits](https://github.com/cashapp/paparazzi/compare/1.1.0...1.2.0) --- updated-dependencies: - dependency-name: app.cash.paparazzi:paparazzi dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: app.cash.paparazzi:paparazzi-gradle-plugin dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index 4977543822..5ab063bf98 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -35,7 +35,7 @@ def mockk = "1.12.3" // We need to use 1.12.3 to have mocking in androidTest unt def espresso = "3.5.1" def androidxTest = "1.5.0" def androidxOrchestrator = "1.4.2" -def paparazzi = "1.1.0" +def paparazzi = "1.2.0" ext.libs = [ gradle : [ From bc3c253067165f98e2608de462ec7dea26092852 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 24 Jan 2023 14:44:52 +0100 Subject: [PATCH 012/189] Fix bad import. --- .../timeline/render/ProcessBodyOfReplyToEventUseCaseTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 c38afe20ec..2ae35eb000 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 @@ -16,7 +16,7 @@ package im.vector.app.features.home.room.detail.timeline.render -import android.annotation.StringRes +import androidx.annotation.StringRes import im.vector.app.R import im.vector.app.test.fakes.FakeActiveSessionHolder import im.vector.app.test.fakes.FakeStringProvider From 6c5bc48c850c587b0d2b1d17823ad3e6174857c3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 24 Jan 2023 23:02:47 +0000 Subject: [PATCH 013/189] Bump material from 1.7.0 to 1.8.0 Bumps [material](https://github.com/material-components/material-components-android) from 1.7.0 to 1.8.0. - [Release notes](https://github.com/material-components/material-components-android/releases) - [Commits](https://github.com/material-components/material-components-android/commits) --- updated-dependencies: - dependency-name: com.google.android.material:material dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index bc6ad1f931..2903a9a1a7 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -82,7 +82,7 @@ ext.libs = [ 'transition' : "androidx.transition:transition:1.2.0", ], google : [ - 'material' : "com.google.android.material:material:1.7.0", + 'material' : "com.google.android.material:material:1.8.0", 'firebaseBom' : "com.google.firebase:firebase-bom:$firebaseBom", 'messaging' : "com.google.firebase:firebase-messaging", 'appdistributionApi' : "com.google.firebase:firebase-appdistribution-api-ktx:$appDistribution", From 488c5703a21a48a8aadd94b8ecf164d4fae6afb5 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 25 Jan 2023 10:43:16 +0100 Subject: [PATCH 014/189] Record again the screenshot after bumping paparazzi from 1.1.0 to 1.2.0 --- ..._PaparazziExampleScreenshotTest_example paparazzi test.png | 4 ++-- ...r.app.screenshot_RoomItemScreenshotTest_item room test.png | 4 ++-- ...emScreenshotTest_item room two line and highlight test.png | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/vector/src/test/snapshots/images/im.vector.app.screenshot_PaparazziExampleScreenshotTest_example paparazzi test.png b/vector/src/test/snapshots/images/im.vector.app.screenshot_PaparazziExampleScreenshotTest_example paparazzi test.png index bba610b25f..68a0daa6be 100644 --- a/vector/src/test/snapshots/images/im.vector.app.screenshot_PaparazziExampleScreenshotTest_example paparazzi test.png +++ b/vector/src/test/snapshots/images/im.vector.app.screenshot_PaparazziExampleScreenshotTest_example paparazzi test.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:113cf006e9a881f19b79462297cf276aea2b82268182f9ecc297d4b31640b507 -size 11174 +oid sha256:625451c18bb83d6c07f919e0203b41be0dfea571fc19b659752cf43a9b890ff5 +size 11394 diff --git a/vector/src/test/snapshots/images/im.vector.app.screenshot_RoomItemScreenshotTest_item room test.png b/vector/src/test/snapshots/images/im.vector.app.screenshot_RoomItemScreenshotTest_item room test.png index 1e87449b3c..a22363b0bf 100644 --- a/vector/src/test/snapshots/images/im.vector.app.screenshot_RoomItemScreenshotTest_item room test.png +++ b/vector/src/test/snapshots/images/im.vector.app.screenshot_RoomItemScreenshotTest_item room test.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d33e82c6647bab9dcb3745d8c5a5448d60049279c365b9f64816eb9c958360d2 -size 15015 +oid sha256:466c86edae63935a00d28f29054a4d4c4d8cfb34f1f5e7b3ff25c66bc88090cd +size 15046 diff --git a/vector/src/test/snapshots/images/im.vector.app.screenshot_RoomItemScreenshotTest_item room two line and highlight test.png b/vector/src/test/snapshots/images/im.vector.app.screenshot_RoomItemScreenshotTest_item room two line and highlight test.png index 83fcb8d000..963b12bd6e 100644 --- a/vector/src/test/snapshots/images/im.vector.app.screenshot_RoomItemScreenshotTest_item room two line and highlight test.png +++ b/vector/src/test/snapshots/images/im.vector.app.screenshot_RoomItemScreenshotTest_item room two line and highlight test.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:91a106e2a3f7310ac05425a2413ccec0aaa07720609d77a2ecd9a9d0d602b296 -size 17232 +oid sha256:5deafa919d2b0753a2f2fd622b7a4457bba06aba2918a406933c2f6f213d8916 +size 17265 From cb093e0496f01f252591591b5b9b7d1dc2a45f09 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 25 Jan 2023 14:06:48 +0100 Subject: [PATCH 015/189] version++ --- matrix-sdk-android/build.gradle | 2 +- vector-app/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 9c9d2dd0dc..33afbfe02c 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.22\"" + buildConfigField "String", "SDK_VERSION", "\"1.5.24\"" buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\"" buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\"" diff --git a/vector-app/build.gradle b/vector-app/build.gradle index 824f651b4d..b2c2e79bcb 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 = 22 +ext.versionPatch = 24 static def getGitTimestamp() { def cmd = 'git show -s --format=%ct' From bb4ec4f5422384a0655baf73aaddcbc13204373b Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Wed, 25 Jan 2023 14:45:17 +0100 Subject: [PATCH 016/189] Call push rule /actions api before the /enabled api --- .../session/pushers/UpdatePushRuleActionsTask.kt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleActionsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleActionsTask.kt index 454b9cdd80..ec6b5d5268 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleActionsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleActionsTask.kt @@ -34,10 +34,16 @@ internal interface UpdatePushRuleActionsTask : Task Date: Wed, 25 Jan 2023 14:52:30 +0100 Subject: [PATCH 017/189] Adding changelog entry --- changelog.d/8005.sdk | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/8005.sdk diff --git a/changelog.d/8005.sdk b/changelog.d/8005.sdk new file mode 100644 index 0000000000..1849776d50 --- /dev/null +++ b/changelog.d/8005.sdk @@ -0,0 +1 @@ +[Push rules] Call /actions api before /enabled api From edc04ea49d53b40192ee81c8e11c66acd965b234 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Mon, 16 Jan 2023 09:52:47 +0100 Subject: [PATCH 018/189] Adding changelog entry --- changelog.d/7864.wip | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7864.wip diff --git a/changelog.d/7864.wip b/changelog.d/7864.wip new file mode 100644 index 0000000000..da04806b8b --- /dev/null +++ b/changelog.d/7864.wip @@ -0,0 +1 @@ +[Poll] History list: unmock data From c7f6ece825d4d6a7da1bbcbfb14fa4f296761b78 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Mon, 16 Jan 2023 10:43:35 +0100 Subject: [PATCH 019/189] Introducing a new room service for poll history --- .../android/sdk/api/session/room/Room.kt | 6 ++ .../session/room/poll/LoadedPollsStatus.kt | 25 +++++++ .../session/room/poll/PollHistoryService.kt | 55 ++++++++++++++++ .../sdk/internal/session/room/DefaultRoom.kt | 3 + .../sdk/internal/session/room/RoomFactory.kt | 3 + .../room/poll/DefaultPollHistoryService.kt | 66 +++++++++++++++++++ 6 files changed, 158 insertions(+) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/poll/LoadedPollsStatus.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/poll/PollHistoryService.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt index 8031fcaeea..de360c89c6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt @@ -28,6 +28,7 @@ import org.matrix.android.sdk.api.session.room.model.LocalRoomSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.relation.RelationService import org.matrix.android.sdk.api.session.room.notification.RoomPushRuleService +import org.matrix.android.sdk.api.session.room.poll.PollHistoryService import org.matrix.android.sdk.api.session.room.read.ReadService import org.matrix.android.sdk.api.session.room.reporting.ReportingService import org.matrix.android.sdk.api.session.room.send.DraftService @@ -181,4 +182,9 @@ interface Room { * Get the LocationSharingService associated to this Room. */ fun locationSharingService(): LocationSharingService + + /** + * Get the PollHistoryService associated to this Room. + */ + fun pollHistoryService(): PollHistoryService } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/poll/LoadedPollsStatus.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/poll/LoadedPollsStatus.kt new file mode 100644 index 0000000000..d9347ff1d6 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/poll/LoadedPollsStatus.kt @@ -0,0 +1,25 @@ +/* + * 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 org.matrix.android.sdk.api.session.room.poll + +/** + * Status to indicate loading of polls for a room. + */ +data class LoadedPollsStatus( + val canLoadMore: Boolean, + val nbLoadedDays: Int, +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/poll/PollHistoryService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/poll/PollHistoryService.kt new file mode 100644 index 0000000000..e0e1477913 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/poll/PollHistoryService.kt @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2023 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.room.poll + +import androidx.lifecycle.LiveData +import org.matrix.android.sdk.api.session.room.model.PollResponseAggregatedSummary + +/** + * Expose methods to get history of polls in rooms. + */ +interface PollHistoryService { + + val loadingPeriodInDays: Int + + /** + * Ask to load more polls starting from last loaded polls for a period defined by + * [loadingPeriodInDays]. + */ + suspend fun loadMore(): LoadedPollsStatus + + /** + * Indicate whether loading more polls is possible. If not possible, + * it indicates the end of the room has been reached in the past. + */ + fun canLoadMore(): Boolean + + /** + * Get the current status of the loaded polls. + */ + fun getLoadedPollsStatus(): LoadedPollsStatus + + /** + * Sync polls from last loaded polls until now. + */ + suspend fun syncPolls() + + /** + * Get currently loaded list of polls. See [loadMore]. + */ + fun getPolls(): LiveData> +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt index 262c111b73..3252dff0f7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt @@ -30,6 +30,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.RoomType import org.matrix.android.sdk.api.session.room.model.relation.RelationService import org.matrix.android.sdk.api.session.room.notification.RoomPushRuleService +import org.matrix.android.sdk.api.session.room.poll.PollHistoryService import org.matrix.android.sdk.api.session.room.read.ReadService import org.matrix.android.sdk.api.session.room.reporting.ReportingService import org.matrix.android.sdk.api.session.room.send.DraftService @@ -72,6 +73,7 @@ internal class DefaultRoom( private val roomVersionService: RoomVersionService, private val viaParameterFinder: ViaParameterFinder, private val locationSharingService: LocationSharingService, + private val pollHistoryService: PollHistoryService, override val coroutineDispatchers: MatrixCoroutineDispatchers ) : Room { @@ -116,4 +118,5 @@ internal class DefaultRoom( override fun roomAccountDataService() = roomAccountDataService override fun roomVersionService() = roomVersionService override fun locationSharingService() = locationSharingService + override fun pollHistoryService() = pollHistoryService } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt index ffe7679575..86c414863d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt @@ -28,6 +28,7 @@ import org.matrix.android.sdk.internal.session.room.draft.DefaultDraftService import org.matrix.android.sdk.internal.session.room.location.DefaultLocationSharingService import org.matrix.android.sdk.internal.session.room.membership.DefaultMembershipService import org.matrix.android.sdk.internal.session.room.notification.DefaultRoomPushRuleService +import org.matrix.android.sdk.internal.session.room.poll.DefaultPollHistoryService import org.matrix.android.sdk.internal.session.room.read.DefaultReadService import org.matrix.android.sdk.internal.session.room.relation.DefaultRelationService import org.matrix.android.sdk.internal.session.room.reporting.DefaultReportingService @@ -71,6 +72,7 @@ internal class DefaultRoomFactory @Inject constructor( private val roomAccountDataServiceFactory: DefaultRoomAccountDataService.Factory, private val viaParameterFinder: ViaParameterFinder, private val locationSharingServiceFactory: DefaultLocationSharingService.Factory, + private val pollHistoryServiceFactory: DefaultPollHistoryService.Factory, private val coroutineDispatchers: MatrixCoroutineDispatchers ) : RoomFactory { @@ -99,6 +101,7 @@ internal class DefaultRoomFactory @Inject constructor( roomVersionService = roomVersionServiceFactory.create(roomId), viaParameterFinder = viaParameterFinder, locationSharingService = locationSharingServiceFactory.create(roomId), + pollHistoryService = pollHistoryServiceFactory.create(roomId), coroutineDispatchers = coroutineDispatchers ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt new file mode 100644 index 0000000000..6779fbc101 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2023 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.poll + +import androidx.lifecycle.LiveData +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import org.matrix.android.sdk.api.session.room.model.PollResponseAggregatedSummary +import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus +import org.matrix.android.sdk.api.session.room.poll.PollHistoryService +import timber.log.Timber + +private const val LOADING_PERIOD_IN_DAYS = 30 + +// TODO add unit tests +internal class DefaultPollHistoryService @AssistedInject constructor( + @Assisted private val roomId: String, +) : PollHistoryService { + + @AssistedFactory + interface Factory { + fun create(roomId: String): DefaultPollHistoryService + } + + init { + Timber.d("init with roomId: $roomId") + } + + override val loadingPeriodInDays: Int + get() = LOADING_PERIOD_IN_DAYS + + override suspend fun loadMore(): LoadedPollsStatus { + TODO("Not yet implemented") + } + + override fun canLoadMore(): Boolean { + TODO("Not yet implemented") + } + + override fun getLoadedPollsStatus(): LoadedPollsStatus { + TODO("Not yet implemented") + } + + override suspend fun syncPolls() { + TODO("Not yet implemented") + } + + override fun getPolls(): LiveData> { + TODO("Not yet implemented") + } +} From 1ab6faf2d25a90e95b2d6de7b81ee8ab8f2ed093 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Mon, 16 Jan 2023 14:57:51 +0100 Subject: [PATCH 020/189] Adding PollHistoryStatusEntity --- .../database/model/PollHistoryStatusEntity.kt | 69 +++++++++++++++++++ .../database/model/SessionRealmModule.kt | 1 + 2 files changed, 70 insertions(+) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PollHistoryStatusEntity.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PollHistoryStatusEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PollHistoryStatusEntity.kt new file mode 100644 index 0000000000..4eb4d1a92a --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PollHistoryStatusEntity.kt @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2023 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.database.model + +import io.realm.RealmObject +import io.realm.annotations.PrimaryKey + +/** + * Keeps track of the loading process of the poll history. + */ +internal open class PollHistoryStatusEntity( + /** + * The related room id. + */ + @PrimaryKey + var roomId: String = "", + + /** + * Timestamp of the in progress poll sync target in backward direction in milliseconds. + */ + var currentTimestampTargetBackwardMs: Long? = null, + + /** + * Timestamp of the last completed poll sync target in backward direction in milliseconds. + */ + var lastTimestampTargetBackwardMs: Long? = null, + + /** + * Indicate whether all polls in a room have been synced for the current timestamp target in backward direction. + */ + var currentTimestampTargetBackwardReached: Boolean = false, + + /** + * Indicate whether all polls in a room have been synced in backward direction. + */ + var isEndOfPollsBackward: Boolean = false, + + /** + * Indicate whether at least one poll sync has been fully completed backward for the given room. + */ + var hasCompletedASyncBackward: Boolean = false, + + /** + * Token of the end of the last synced chunk in backward direction. + */ + var tokenEndBackward: String? = null, + + /** + * Token of the start of the last synced chunk in forward direction. + */ + var tokenStartForward: String? = null, +) : RealmObject() { + + companion object +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt index 93fe1bd1d2..af8dfd7ece 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt @@ -73,6 +73,7 @@ import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntit UserPresenceEntity::class, ThreadSummaryEntity::class, ThreadListPageEntity::class, + PollHistoryStatusEntity::class, ] ) internal class SessionRealmModule From 9d921286311c36a3278f1b073265662ffce1117a Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Mon, 16 Jan 2023 16:07:29 +0100 Subject: [PATCH 021/189] Starting to implement LoadMorePollsTask with update of poll history status --- .../query/PollHistoryStatusEntityQueries.kt | 31 +++++++++ .../sdk/internal/session/room/RoomModule.kt | 5 ++ .../room/poll/DefaultPollHistoryService.kt | 13 +++- .../session/room/poll/LoadMorePollsTask.kt | 67 +++++++++++++++++++ .../polls/list/data/RoomPollDataSource.kt | 18 ++++- 5 files changed, 131 insertions(+), 3 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/PollHistoryStatusEntityQueries.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/LoadMorePollsTask.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/PollHistoryStatusEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/PollHistoryStatusEntityQueries.kt new file mode 100644 index 0000000000..1396eb897b --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/PollHistoryStatusEntityQueries.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.database.query + +import io.realm.Realm +import io.realm.kotlin.createObject +import io.realm.kotlin.where +import org.matrix.android.sdk.internal.database.model.PollHistoryStatusEntity +import org.matrix.android.sdk.internal.database.model.PollHistoryStatusEntityFields + +internal fun PollHistoryStatusEntity.Companion.get(realm: Realm, roomId: String): PollHistoryStatusEntity? { + return realm.where().equalTo(PollHistoryStatusEntityFields.ROOM_ID, roomId).findFirst() +} + +internal fun PollHistoryStatusEntity.Companion.getOrCreate(realm: Realm, roomId: String): PollHistoryStatusEntity { + return get(realm, roomId) ?: realm.createObject(roomId) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt index c28d24995f..dab2d340b6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt @@ -89,6 +89,8 @@ import org.matrix.android.sdk.internal.session.room.peeking.DefaultPeekRoomTask import org.matrix.android.sdk.internal.session.room.peeking.DefaultResolveRoomStateTask import org.matrix.android.sdk.internal.session.room.peeking.PeekRoomTask import org.matrix.android.sdk.internal.session.room.peeking.ResolveRoomStateTask +import org.matrix.android.sdk.internal.session.room.poll.DefaultLoadMorePollsTask +import org.matrix.android.sdk.internal.session.room.poll.LoadMorePollsTask import org.matrix.android.sdk.internal.session.room.read.DefaultMarkAllRoomsReadTask import org.matrix.android.sdk.internal.session.room.read.DefaultSetReadMarkersTask import org.matrix.android.sdk.internal.session.room.read.MarkAllRoomsReadTask @@ -359,4 +361,7 @@ internal abstract class RoomModule { @Binds abstract fun bindFetchPollResponseEventsTask(task: DefaultFetchPollResponseEventsTask): FetchPollResponseEventsTask + + @Binds + abstract fun bindLoadMorePollsTask(task: DefaultLoadMorePollsTask): LoadMorePollsTask } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt index 6779fbc101..e476eaf144 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt @@ -23,13 +23,17 @@ import dagger.assisted.AssistedInject import org.matrix.android.sdk.api.session.room.model.PollResponseAggregatedSummary import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus import org.matrix.android.sdk.api.session.room.poll.PollHistoryService +import org.matrix.android.sdk.internal.util.time.Clock import timber.log.Timber private const val LOADING_PERIOD_IN_DAYS = 30 +private const val EVENTS_PAGE_SIZE = 200 // TODO add unit tests internal class DefaultPollHistoryService @AssistedInject constructor( @Assisted private val roomId: String, + private val clock: Clock, + private val loadMorePollsTask: LoadMorePollsTask, ) : PollHistoryService { @AssistedFactory @@ -45,7 +49,14 @@ internal class DefaultPollHistoryService @AssistedInject constructor( get() = LOADING_PERIOD_IN_DAYS override suspend fun loadMore(): LoadedPollsStatus { - TODO("Not yet implemented") + // TODO when to set currentTimestampMs and who is responsible for it? + val params = LoadMorePollsTask.Params( + roomId = roomId, + currentTimestampMs = clock.epochMillis(), + loadingPeriodInDays = loadingPeriodInDays, + eventsPageSize = EVENTS_PAGE_SIZE, + ) + return loadMorePollsTask.execute(params) } override fun canLoadMore(): Boolean { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/LoadMorePollsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/LoadMorePollsTask.kt new file mode 100644 index 0000000000..53841ae3f5 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/LoadMorePollsTask.kt @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2023 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.poll + +import com.zhuinden.monarchy.Monarchy +import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus +import org.matrix.android.sdk.internal.database.model.PollHistoryStatusEntity +import org.matrix.android.sdk.internal.database.query.getOrCreate +import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.task.Task +import org.matrix.android.sdk.internal.util.awaitTransaction +import javax.inject.Inject + +internal interface LoadMorePollsTask : Task { + data class Params( + val roomId: String, + val currentTimestampMs: Long, + val loadingPeriodInDays: Int, + val eventsPageSize: Int, + ) +} + +private const val MILLISECONDS_PER_DAY = 24 * 60 * 60_000 + +internal class DefaultLoadMorePollsTask @Inject constructor( + @SessionDatabase private val monarchy: Monarchy, +) : LoadMorePollsTask { + + override suspend fun execute(params: LoadMorePollsTask.Params): LoadedPollsStatus { + updatePollHistoryStatus(params) + + return LoadedPollsStatus( + canLoadMore = true, + nbLoadedDays = 10, + ) + } + + private suspend fun updatePollHistoryStatus(params: LoadMorePollsTask.Params) { + monarchy.awaitTransaction { realm -> + val status = PollHistoryStatusEntity.getOrCreate(realm, params.roomId) + val currentTargetTimestamp = status.currentTimestampTargetBackwardMs + val loadingPeriodMs = MILLISECONDS_PER_DAY * params.loadingPeriodInDays + if (currentTargetTimestamp == null) { + // first load, compute the target timestamp + status.currentTimestampTargetBackwardMs = params.currentTimestampMs - loadingPeriodMs + } else if (status.currentTimestampTargetBackwardReached) { + // previous load has finished, update the target timestamp + status.currentTimestampTargetBackwardMs = currentTargetTimestamp - loadingPeriodMs + status.currentTimestampTargetBackwardReached = false + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollDataSource.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollDataSource.kt index c0efb1efa1..e4f59da0d7 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollDataSource.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollDataSource.kt @@ -16,23 +16,35 @@ package im.vector.app.features.roomprofile.polls.list.data +import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState import im.vector.app.features.roomprofile.polls.list.ui.PollSummary import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.asSharedFlow +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.room.poll.PollHistoryService import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton @Singleton -class RoomPollDataSource @Inject constructor() { +class RoomPollDataSource @Inject constructor( + private val activeSessionHolder: ActiveSessionHolder, +) { private val pollsFlow = MutableSharedFlow>(replay = 1) private val polls = mutableListOf() private var fakeLoadCounter = 0 + private fun getPollHistoryService(roomId: String): PollHistoryService? { + return activeSessionHolder + .getSafeActiveSession() + ?.getRoom(roomId) + ?.pollHistoryService() + } + // TODO // unmock using SDK service + add unit tests // after unmock, expose domain layer model (entity) and do the mapping to PollSummary in the UI layer @@ -54,8 +66,10 @@ class RoomPollDataSource @Inject constructor() { } suspend fun loadMorePolls(roomId: String): LoadedPollsStatus { + getPollHistoryService(roomId)?.loadMore() + // TODO - // unmock using SDK service + add unit tests + // remove mocked data + add unit tests delay(3000) fakeLoadCounter++ when (fakeLoadCounter) { From aa736e2bfcac92c93a67958eedf6c267c2601c24 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Tue, 17 Jan 2023 10:02:35 +0100 Subject: [PATCH 022/189] Set page size to 250 --- .../sdk/internal/session/room/poll/DefaultPollHistoryService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt index e476eaf144..4a3dff7b39 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt @@ -27,7 +27,7 @@ import org.matrix.android.sdk.internal.util.time.Clock import timber.log.Timber private const val LOADING_PERIOD_IN_DAYS = 30 -private const val EVENTS_PAGE_SIZE = 200 +private const val EVENTS_PAGE_SIZE = 250 // TODO add unit tests internal class DefaultPollHistoryService @AssistedInject constructor( From 54737895773d4120a18dba1e8414b565620f3e1b Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Tue, 17 Jan 2023 16:59:07 +0100 Subject: [PATCH 023/189] Removing non necessary fields that can be computed using other existing fields --- .../database/model/PollHistoryStatusEntity.kt | 29 ++++++++++++------- .../PollResponseAggregatedSummaryEntity.kt | 5 +--- .../session/room/poll/LoadMorePollsTask.kt | 16 ++++++---- 3 files changed, 30 insertions(+), 20 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PollHistoryStatusEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PollHistoryStatusEntity.kt index 4eb4d1a92a..0f28d51ffb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PollHistoryStatusEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PollHistoryStatusEntity.kt @@ -39,21 +39,11 @@ internal open class PollHistoryStatusEntity( */ var lastTimestampTargetBackwardMs: Long? = null, - /** - * Indicate whether all polls in a room have been synced for the current timestamp target in backward direction. - */ - var currentTimestampTargetBackwardReached: Boolean = false, - /** * Indicate whether all polls in a room have been synced in backward direction. */ var isEndOfPollsBackward: Boolean = false, - /** - * Indicate whether at least one poll sync has been fully completed backward for the given room. - */ - var hasCompletedASyncBackward: Boolean = false, - /** * Token of the end of the last synced chunk in backward direction. */ @@ -66,4 +56,23 @@ internal open class PollHistoryStatusEntity( ) : RealmObject() { companion object + + /** + * Indicate whether at least one poll sync has been fully completed backward for the given room. + */ + val hasCompletedASyncBackward: Boolean + get() = lastTimestampTargetBackwardMs != null + + /** + * Indicate whether all polls in a room have been synced for the current timestamp target in backward direction. + */ + val currentTimestampTargetBackwardReached: Boolean + get() = checkIfCurrentTimestampTargetBackwardIsReached() + + private fun checkIfCurrentTimestampTargetBackwardIsReached(): Boolean { + val currentTarget = currentTimestampTargetBackwardMs + val lastTarget = lastTimestampTargetBackwardMs + // last timestamp target should be older or equal to the current target + return currentTarget != null && lastTarget != null && lastTarget <= currentTarget + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PollResponseAggregatedSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PollResponseAggregatedSummaryEntity.kt index 906e329f6f..e74f8e2ce9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PollResponseAggregatedSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PollResponseAggregatedSummaryEntity.kt @@ -36,7 +36,4 @@ internal open class PollResponseAggregatedSummaryEntity( var sourceLocalEchoEvents: RealmList = RealmList(), // list of related event ids which are encrypted due to decryption failure var encryptedRelatedEventIds: RealmList = RealmList(), -) : RealmObject() { - - companion object -} +) : RealmObject() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/LoadMorePollsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/LoadMorePollsTask.kt index 53841ae3f5..c29983bff3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/LoadMorePollsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/LoadMorePollsTask.kt @@ -43,6 +43,10 @@ internal class DefaultLoadMorePollsTask @Inject constructor( override suspend fun execute(params: LoadMorePollsTask.Params): LoadedPollsStatus { updatePollHistoryStatus(params) + // TODO fetch events in a loop using current poll history status + // decrypt events and filter in only polls to store them in local + // parse the response to update poll history status + // unmock and check how it behaves when cancelling the process: it should resume where it was stopped return LoadedPollsStatus( canLoadMore = true, nbLoadedDays = 10, @@ -52,15 +56,15 @@ internal class DefaultLoadMorePollsTask @Inject constructor( private suspend fun updatePollHistoryStatus(params: LoadMorePollsTask.Params) { monarchy.awaitTransaction { realm -> val status = PollHistoryStatusEntity.getOrCreate(realm, params.roomId) - val currentTargetTimestamp = status.currentTimestampTargetBackwardMs - val loadingPeriodMs = MILLISECONDS_PER_DAY * params.loadingPeriodInDays - if (currentTargetTimestamp == null) { + val currentTargetTimestampMs = status.currentTimestampTargetBackwardMs + val lastTargetTimestampMs = status.lastTimestampTargetBackwardMs + val loadingPeriodMs: Long = MILLISECONDS_PER_DAY * params.loadingPeriodInDays.toLong() + if (currentTargetTimestampMs == null) { // first load, compute the target timestamp status.currentTimestampTargetBackwardMs = params.currentTimestampMs - loadingPeriodMs - } else if (status.currentTimestampTargetBackwardReached) { + } else if (lastTargetTimestampMs != null && status.currentTimestampTargetBackwardReached) { // previous load has finished, update the target timestamp - status.currentTimestampTargetBackwardMs = currentTargetTimestamp - loadingPeriodMs - status.currentTimestampTargetBackwardReached = false + status.currentTimestampTargetBackwardMs = lastTargetTimestampMs - loadingPeriodMs } } } From 3e118f24adb94aedb04f9d9753d07e89ace95ba6 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Wed, 18 Jan 2023 14:25:40 +0100 Subject: [PATCH 024/189] Loading events in a loop --- .../session/room/poll/LoadedPollsStatus.kt | 4 +- .../session/room/poll/PollHistoryService.kt | 6 - .../database/model/PollHistoryStatusEntity.kt | 35 ++++- .../sdk/internal/session/room/RoomAPI.kt | 2 +- .../room/poll/DefaultPollHistoryService.kt | 5 - .../session/room/poll/LoadMorePollsTask.kt | 90 ++++++++++-- .../session/room/poll/PollConstants.kt | 21 +++ .../roomprofile/polls/RoomPollsViewModel.kt | 4 +- .../roomprofile/polls/RoomPollsViewState.kt | 2 +- ...adedPollsStatus.kt => PollHistoryError.kt} | 7 +- .../polls/list/data/RoomPollDataSource.kt | 129 ++---------------- .../polls/list/data/RoomPollRepository.kt | 1 + .../domain/GetLoadedPollsStatusUseCase.kt | 2 +- .../polls/list/domain/LoadMorePollsUseCase.kt | 2 +- .../polls/list/ui/RoomPollsListFragment.kt | 4 +- .../polls/RoomPollsViewModelTest.kt | 6 +- .../domain/GetLoadedPollsStatusUseCaseTest.kt | 4 +- .../list/domain/LoadMorePollsUseCaseTest.kt | 13 +- 18 files changed, 169 insertions(+), 168 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/PollConstants.kt rename vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/{LoadedPollsStatus.kt => PollHistoryError.kt} (87%) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/poll/LoadedPollsStatus.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/poll/LoadedPollsStatus.kt index d9347ff1d6..f4a7dcc6c2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/poll/LoadedPollsStatus.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/poll/LoadedPollsStatus.kt @@ -17,9 +17,9 @@ package org.matrix.android.sdk.api.session.room.poll /** - * Status to indicate loading of polls for a room. + * Represent the status of the loaded polls for a room. */ data class LoadedPollsStatus( val canLoadMore: Boolean, - val nbLoadedDays: Int, + val nbSyncedDays: Int, ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/poll/PollHistoryService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/poll/PollHistoryService.kt index e0e1477913..ad53febc50 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/poll/PollHistoryService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/poll/PollHistoryService.kt @@ -32,12 +32,6 @@ interface PollHistoryService { */ suspend fun loadMore(): LoadedPollsStatus - /** - * Indicate whether loading more polls is possible. If not possible, - * it indicates the end of the room has been reached in the past. - */ - fun canLoadMore(): Boolean - /** * Get the current status of the loaded polls. */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PollHistoryStatusEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PollHistoryStatusEntity.kt index 0f28d51ffb..a1c270e56e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PollHistoryStatusEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PollHistoryStatusEntity.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.database.model import io.realm.RealmObject import io.realm.annotations.PrimaryKey +import org.matrix.android.sdk.internal.session.room.poll.PollConstants /** * Keeps track of the loading process of the poll history. @@ -35,9 +36,9 @@ internal open class PollHistoryStatusEntity( var currentTimestampTargetBackwardMs: Long? = null, /** - * Timestamp of the last completed poll sync target in backward direction in milliseconds. + * Timestamp of the oldest event synced in milliseconds. */ - var lastTimestampTargetBackwardMs: Long? = null, + var oldestTimestampReachedMs: Long? = null, /** * Indicate whether all polls in a room have been synced in backward direction. @@ -57,11 +58,25 @@ internal open class PollHistoryStatusEntity( companion object + /** + * Create a new instance of the entity with the same content. + */ + fun copy(): PollHistoryStatusEntity { + return PollHistoryStatusEntity( + roomId = roomId, + currentTimestampTargetBackwardMs = currentTimestampTargetBackwardMs, + oldestTimestampReachedMs = oldestTimestampReachedMs, + isEndOfPollsBackward = isEndOfPollsBackward, + tokenEndBackward = tokenEndBackward, + tokenStartForward = tokenStartForward, + ) + } + /** * Indicate whether at least one poll sync has been fully completed backward for the given room. */ val hasCompletedASyncBackward: Boolean - get() = lastTimestampTargetBackwardMs != null + get() = oldestTimestampReachedMs != null /** * Indicate whether all polls in a room have been synced for the current timestamp target in backward direction. @@ -71,8 +86,20 @@ internal open class PollHistoryStatusEntity( private fun checkIfCurrentTimestampTargetBackwardIsReached(): Boolean { val currentTarget = currentTimestampTargetBackwardMs - val lastTarget = lastTimestampTargetBackwardMs + val lastTarget = oldestTimestampReachedMs // last timestamp target should be older or equal to the current target return currentTarget != null && lastTarget != null && lastTarget <= currentTarget } + + /** + * Compute the number of days of history currently synced. + */ + fun getNbSyncedDays(currentMs: Long): Int { + val oldestTimestamp = oldestTimestampReachedMs + return if (oldestTimestamp == null) { + 0 + } else { + ((currentMs - oldestTimestamp).coerceAtLeast(0) / PollConstants.MILLISECONDS_PER_DAY).toInt() + } + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt index aa4bdb1dd4..cf57e90c25 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt @@ -89,7 +89,7 @@ internal interface RoomAPI { @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/messages") suspend fun getRoomMessagesFrom( @Path("roomId") roomId: String, - @Query("from") from: String, + @Query("from") from: String?, @Query("dir") dir: String, @Query("limit") limit: Int?, @Query("filter") filter: String?, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt index 4a3dff7b39..74f59b6782 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt @@ -49,7 +49,6 @@ internal class DefaultPollHistoryService @AssistedInject constructor( get() = LOADING_PERIOD_IN_DAYS override suspend fun loadMore(): LoadedPollsStatus { - // TODO when to set currentTimestampMs and who is responsible for it? val params = LoadMorePollsTask.Params( roomId = roomId, currentTimestampMs = clock.epochMillis(), @@ -59,10 +58,6 @@ internal class DefaultPollHistoryService @AssistedInject constructor( return loadMorePollsTask.execute(params) } - override fun canLoadMore(): Boolean { - TODO("Not yet implemented") - } - override fun getLoadedPollsStatus(): LoadedPollsStatus { TODO("Not yet implemented") } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/LoadMorePollsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/LoadMorePollsTask.kt index c29983bff3..03b6c31fec 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/LoadMorePollsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/LoadMorePollsTask.kt @@ -21,6 +21,12 @@ import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus import org.matrix.android.sdk.internal.database.model.PollHistoryStatusEntity import org.matrix.android.sdk.internal.database.query.getOrCreate import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver +import org.matrix.android.sdk.internal.network.executeRequest +import org.matrix.android.sdk.internal.session.room.RoomAPI +import org.matrix.android.sdk.internal.session.room.poll.PollConstants.MILLISECONDS_PER_DAY +import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection +import org.matrix.android.sdk.internal.session.room.timeline.PaginationResponse import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.util.awaitTransaction import javax.inject.Inject @@ -34,30 +40,38 @@ internal interface LoadMorePollsTask : Task + private fun shouldFetchMoreEventsBackward(status: PollHistoryStatusEntity): Boolean { + return status.currentTimestampTargetBackwardReached.not() && status.isEndOfPollsBackward.not() + } + + private suspend fun updatePollHistoryStatus(params: LoadMorePollsTask.Params): PollHistoryStatusEntity { + return monarchy.awaitTransaction { realm -> val status = PollHistoryStatusEntity.getOrCreate(realm, params.roomId) val currentTargetTimestampMs = status.currentTimestampTargetBackwardMs - val lastTargetTimestampMs = status.lastTimestampTargetBackwardMs + val lastTargetTimestampMs = status.oldestTimestampReachedMs val loadingPeriodMs: Long = MILLISECONDS_PER_DAY * params.loadingPeriodInDays.toLong() if (currentTargetTimestampMs == null) { // first load, compute the target timestamp @@ -66,6 +80,60 @@ internal class DefaultLoadMorePollsTask @Inject constructor( // previous load has finished, update the target timestamp status.currentTimestampTargetBackwardMs = lastTargetTimestampMs - loadingPeriodMs } + // return a copy of the Realm object + status.copy() + } + } + + private suspend fun fetchMorePollEventsBackward( + params: LoadMorePollsTask.Params, + status: PollHistoryStatusEntity + ): PollHistoryStatusEntity { + val chunk = executeRequest(globalErrorReceiver) { + roomAPI.getRoomMessagesFrom( + roomId = params.roomId, + from = status.tokenEndBackward, + dir = PaginationDirection.BACKWARDS.value, + limit = params.eventsPageSize, + filter = null + ) + } + + // TODO decrypt events and filter in only polls to store them in local: see to mutualize with FetchPollResponseEventsTask + + return updatePollHistoryStatus(roomId = params.roomId, paginationResponse = chunk) + } + + private suspend fun updatePollHistoryStatus(roomId: String, paginationResponse: PaginationResponse): PollHistoryStatusEntity { + return monarchy.awaitTransaction { realm -> + val status = PollHistoryStatusEntity.getOrCreate(realm, roomId) + val tokenStartForward = status.tokenStartForward + + if (tokenStartForward == null) { + // save the start token for next forward call + status.tokenEndBackward = paginationResponse.start + } + + val oldestEventTimestamp = paginationResponse.events + .minByOrNull { it.originServerTs ?: Long.MAX_VALUE } + ?.originServerTs + + val currentTargetTimestamp = status.currentTimestampTargetBackwardMs + + if (paginationResponse.end == null) { + // start of the timeline is reached, there are no more events + status.isEndOfPollsBackward = true + status.oldestTimestampReachedMs = oldestEventTimestamp + } else if(oldestEventTimestamp != null && currentTargetTimestamp != null && oldestEventTimestamp <= currentTargetTimestamp) { + // target has been reached + status.oldestTimestampReachedMs = oldestEventTimestamp + status.tokenEndBackward = paginationResponse.end + } else { + status.tokenEndBackward = paginationResponse.end + } + + // return a copy of the Realm object + status.copy() } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/PollConstants.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/PollConstants.kt new file mode 100644 index 0000000000..bbc230610c --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/PollConstants.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2023 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.poll + +object PollConstants { + const val MILLISECONDS_PER_DAY = 24 * 60 * 60_000 +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt index b634881f70..b72486402b 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt @@ -58,7 +58,7 @@ class RoomPollsViewModel @AssistedInject constructor( setState { copy( canLoadMore = loadedPollsStatus.canLoadMore, - nbLoadedDays = loadedPollsStatus.nbLoadedDays + nbSyncedDays = loadedPollsStatus.nbSyncedDays, ) } } @@ -96,7 +96,7 @@ class RoomPollsViewModel @AssistedInject constructor( setState { copy( canLoadMore = status.canLoadMore, - nbLoadedDays = status.nbLoadedDays, + nbSyncedDays = status.nbSyncedDays, ) } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt index fa985c5c76..4a5c138b6a 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt @@ -25,7 +25,7 @@ data class RoomPollsViewState( val polls: List = emptyList(), val isLoadingMore: Boolean = false, val canLoadMore: Boolean = true, - val nbLoadedDays: Int = 0, + val nbSyncedDays: Int = 0, val isSyncing: Boolean = false, ) : MavericksState { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/LoadedPollsStatus.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/PollHistoryError.kt similarity index 87% rename from vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/LoadedPollsStatus.kt rename to vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/PollHistoryError.kt index c3971bb289..37b7d934bb 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/LoadedPollsStatus.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/PollHistoryError.kt @@ -16,7 +16,6 @@ package im.vector.app.features.roomprofile.polls.list.data -data class LoadedPollsStatus( - val canLoadMore: Boolean, - val nbLoadedDays: Int, -) +sealed class PollHistoryError : Exception() { + object LoadingError : PollHistoryError() +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollDataSource.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollDataSource.kt index e4f59da0d7..72ca464951 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollDataSource.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollDataSource.kt @@ -17,25 +17,23 @@ package im.vector.app.features.roomprofile.polls.list.data import im.vector.app.core.di.ActiveSessionHolder -import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState import im.vector.app.features.roomprofile.polls.list.ui.PollSummary import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.asSharedFlow import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus import org.matrix.android.sdk.api.session.room.poll.PollHistoryService import timber.log.Timber import javax.inject.Inject -import javax.inject.Singleton -@Singleton +// TODO add unit tests class RoomPollDataSource @Inject constructor( private val activeSessionHolder: ActiveSessionHolder, ) { private val pollsFlow = MutableSharedFlow>(replay = 1) - private val polls = mutableListOf() private var fakeLoadCounter = 0 private fun getPollHistoryService(roomId: String): PollHistoryService? { @@ -46,7 +44,7 @@ class RoomPollDataSource @Inject constructor( } // TODO - // unmock using SDK service + add unit tests + // unmock using SDK service // after unmock, expose domain layer model (entity) and do the mapping to PollSummary in the UI layer fun getPolls(roomId: String): Flow> { Timber.d("roomId=$roomId") @@ -55,9 +53,10 @@ class RoomPollDataSource @Inject constructor( fun getLoadedPollsStatus(roomId: String): LoadedPollsStatus { Timber.d("roomId=$roomId") + // TODO unmock using SDK return LoadedPollsStatus( canLoadMore = canLoadMore(), - nbLoadedDays = fakeLoadCounter * 30, + nbSyncedDays = fakeLoadCounter * 30, ) } @@ -66,123 +65,13 @@ class RoomPollDataSource @Inject constructor( } suspend fun loadMorePolls(roomId: String): LoadedPollsStatus { - getPollHistoryService(roomId)?.loadMore() - - // TODO - // remove mocked data + add unit tests - delay(3000) - fakeLoadCounter++ - when (fakeLoadCounter) { - 1 -> polls.addAll(getActivePollsPart1() + getEndedPollsPart1()) - 2 -> polls.addAll(getActivePollsPart2() + getEndedPollsPart2()) - else -> Unit - } - pollsFlow.emit(polls) - return getLoadedPollsStatus(roomId) - } - - private fun getActivePollsPart1(): List { - return listOf( - PollSummary.ActivePoll( - id = "id1", - // 2022/06/28 UTC+1 - creationTimestamp = 1656367200000, - title = "Which charity would you like to support?" - ), - PollSummary.ActivePoll( - id = "id2", - // 2022/06/26 UTC+1 - creationTimestamp = 1656194400000, - title = "Which sport should the pupils do this year?" - ), - ) - } - - private fun getActivePollsPart2(): List { - return listOf( - PollSummary.ActivePoll( - id = "id3", - // 2022/06/24 UTC+1 - creationTimestamp = 1656021600000, - title = "What type of food should we have at the party?" - ), - PollSummary.ActivePoll( - id = "id4", - // 2022/06/22 UTC+1 - creationTimestamp = 1655848800000, - title = "What film should we show at the end of the year party?" - ), - ) - } - - private fun getEndedPollsPart1(): List { - return listOf( - PollSummary.EndedPoll( - id = "id1-ended", - // 2022/06/28 UTC+1 - creationTimestamp = 1656367200000, - title = "Which charity would you like to support?", - totalVotes = 22, - winnerOptions = listOf( - PollOptionViewState.PollEnded( - optionId = "id1", - optionAnswer = "Cancer research", - voteCount = 13, - votePercentage = 13 / 22.0, - isWinner = true, - ) - ), - ), - ) - } - - private fun getEndedPollsPart2(): List { - return listOf( - PollSummary.EndedPoll( - id = "id2-ended", - // 2022/06/26 UTC+1 - creationTimestamp = 1656194400000, - title = "Where should we do the offsite?", - totalVotes = 92, - winnerOptions = listOf( - PollOptionViewState.PollEnded( - optionId = "id1", - optionAnswer = "Hawaii", - voteCount = 43, - votePercentage = 43 / 92.0, - isWinner = true, - ) - ), - ), - PollSummary.EndedPoll( - id = "id3-ended", - // 2022/06/24 UTC+1 - creationTimestamp = 1656021600000, - title = "What type of food should we have at the party?", - totalVotes = 22, - winnerOptions = listOf( - PollOptionViewState.PollEnded( - optionId = "id1", - optionAnswer = "Brazilian", - voteCount = 13, - votePercentage = 13 / 22.0, - isWinner = true, - ) - ), - ), - ) + return getPollHistoryService(roomId)?.loadMore() ?: throw PollHistoryError.LoadingError } suspend fun syncPolls(roomId: String) { Timber.d("roomId=$roomId") - // TODO - // unmock using SDK service + add unit tests - if (fakeLoadCounter == 0) { - // fake first load - loadMorePolls(roomId) - } else { - // fake sync - delay(3000) - } + // TODO unmock using SDK service + // fake sync + delay(1000) } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollRepository.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollRepository.kt index d3577df6c1..6f9b780464 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollRepository.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollRepository.kt @@ -18,6 +18,7 @@ package im.vector.app.features.roomprofile.polls.list.data import im.vector.app.features.roomprofile.polls.list.ui.PollSummary import kotlinx.coroutines.flow.Flow +import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus import javax.inject.Inject class RoomPollRepository @Inject constructor( diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/domain/GetLoadedPollsStatusUseCase.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/domain/GetLoadedPollsStatusUseCase.kt index 55324b253f..2bac26f79c 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/domain/GetLoadedPollsStatusUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/domain/GetLoadedPollsStatusUseCase.kt @@ -16,8 +16,8 @@ package im.vector.app.features.roomprofile.polls.list.domain -import im.vector.app.features.roomprofile.polls.list.data.LoadedPollsStatus import im.vector.app.features.roomprofile.polls.list.data.RoomPollRepository +import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus import javax.inject.Inject class GetLoadedPollsStatusUseCase @Inject constructor( diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/domain/LoadMorePollsUseCase.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/domain/LoadMorePollsUseCase.kt index df3270552d..fce222cae6 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/domain/LoadMorePollsUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/domain/LoadMorePollsUseCase.kt @@ -16,8 +16,8 @@ package im.vector.app.features.roomprofile.polls.list.domain -import im.vector.app.features.roomprofile.polls.list.data.LoadedPollsStatus import im.vector.app.features.roomprofile.polls.list.data.RoomPollRepository +import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus import javax.inject.Inject class LoadMorePollsUseCase @Inject constructor( diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListFragment.kt index 5920eb046e..1c33959824 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/RoomPollsListFragment.kt @@ -78,7 +78,7 @@ abstract class RoomPollsListFragment : views.roomPollsList.configureWith(roomPollsController) views.roomPollsEmptyTitle.text = getEmptyListTitle( canLoadMore = viewState.canLoadMore, - nbLoadedDays = viewState.nbLoadedDays, + nbLoadedDays = viewState.nbSyncedDays, ) } @@ -117,7 +117,7 @@ abstract class RoomPollsListFragment : roomPollsController.setData(viewState) views.roomPollsEmptyTitle.text = getEmptyListTitle( canLoadMore = viewState.canLoadMore, - nbLoadedDays = viewState.nbLoadedDays, + nbLoadedDays = viewState.nbSyncedDays, ) views.roomPollsEmptyTitle.isVisible = !viewState.isSyncing && viewState.hasNoPolls() views.roomPollsLoadMoreWhenEmpty.isVisible = viewState.hasNoPollsAndCanLoadMore() diff --git a/vector/src/test/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModelTest.kt b/vector/src/test/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModelTest.kt index efb905c97f..1cac603ae2 100644 --- a/vector/src/test/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModelTest.kt @@ -68,7 +68,7 @@ class RoomPollsViewModelTest { val expectedViewState = initialState.copy( polls = polls, canLoadMore = loadedPollsStatus.canLoadMore, - nbLoadedDays = loadedPollsStatus.nbLoadedDays, + nbSyncedDays = loadedPollsStatus.nbLoadedDays, ) // When @@ -116,7 +116,7 @@ class RoomPollsViewModelTest { val stateAfterInit = initialState.copy( polls = polls, canLoadMore = loadedPollsStatus.canLoadMore, - nbLoadedDays = loadedPollsStatus.nbLoadedDays, + nbSyncedDays = loadedPollsStatus.nbLoadedDays, ) // When @@ -128,7 +128,7 @@ class RoomPollsViewModelTest { .assertStatesChanges( stateAfterInit, { copy(isLoadingMore = true) }, - { copy(canLoadMore = newLoadedPollsStatus.canLoadMore, nbLoadedDays = newLoadedPollsStatus.nbLoadedDays) }, + { copy(canLoadMore = newLoadedPollsStatus.canLoadMore, nbSyncedDays = newLoadedPollsStatus.nbLoadedDays) }, { copy(isLoadingMore = false) }, ) .finish() diff --git a/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/domain/GetLoadedPollsStatusUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/domain/GetLoadedPollsStatusUseCaseTest.kt index c87a15fb02..12c23797f0 100644 --- a/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/domain/GetLoadedPollsStatusUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/domain/GetLoadedPollsStatusUseCaseTest.kt @@ -16,13 +16,13 @@ package im.vector.app.features.roomprofile.polls.list.domain -import im.vector.app.features.roomprofile.polls.list.data.LoadedPollsStatus import im.vector.app.features.roomprofile.polls.list.data.RoomPollRepository import io.mockk.every import io.mockk.mockk import io.mockk.verify import org.amshove.kluent.shouldBeEqualTo import org.junit.Test +import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus class GetLoadedPollsStatusUseCaseTest { @@ -38,7 +38,7 @@ class GetLoadedPollsStatusUseCaseTest { val aRoomId = "roomId" val expectedStatus = LoadedPollsStatus( canLoadMore = true, - nbLoadedDays = 10, + nbSyncedDays = 10, ) every { fakeRoomPollRepository.getLoadedPollsStatus(aRoomId) } returns expectedStatus diff --git a/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/domain/LoadMorePollsUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/domain/LoadMorePollsUseCaseTest.kt index 16405d98c3..4c769de222 100644 --- a/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/domain/LoadMorePollsUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/domain/LoadMorePollsUseCaseTest.kt @@ -17,11 +17,13 @@ package im.vector.app.features.roomprofile.polls.list.domain import im.vector.app.features.roomprofile.polls.list.data.RoomPollRepository -import io.mockk.coJustRun +import io.mockk.coEvery import io.mockk.coVerify import io.mockk.mockk import kotlinx.coroutines.test.runTest +import org.amshove.kluent.shouldBeEqualTo import org.junit.Test +import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus class LoadMorePollsUseCaseTest { @@ -35,12 +37,17 @@ class LoadMorePollsUseCaseTest { fun `given repo when execute then correct method of repo is called`() = runTest { // Given val aRoomId = "roomId" - coJustRun { fakeRoomPollRepository.loadMorePolls(aRoomId) } + val loadedPollsStatus = LoadedPollsStatus( + canLoadMore = true, + nbSyncedDays = 10, + ) + coEvery { fakeRoomPollRepository.loadMorePolls(aRoomId) } returns loadedPollsStatus // When - loadMorePollsUseCase.execute(aRoomId) + val result = loadMorePollsUseCase.execute(aRoomId) // Then + result shouldBeEqualTo loadedPollsStatus coVerify { fakeRoomPollRepository.loadMorePolls(aRoomId) } } } From 10be07590df0d4120e0e7f23dcece4efbc103edb Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Wed, 18 Jan 2023 14:53:14 +0100 Subject: [PATCH 025/189] Get loaded polls status use case --- .../session/room/poll/PollHistoryService.kt | 2 +- .../sdk/internal/session/room/RoomModule.kt | 5 ++ .../room/poll/DefaultPollHistoryService.kt | 9 +++- .../room/poll/GetLoadedPollsStatusTask.kt | 48 +++++++++++++++++++ .../roomprofile/polls/RoomPollsViewModel.kt | 19 +++----- .../polls/list/data/PollHistoryError.kt | 2 +- .../polls/list/data/RoomPollDataSource.kt | 19 ++------ .../polls/list/data/RoomPollRepository.kt | 2 +- .../domain/GetLoadedPollsStatusUseCase.kt | 2 +- .../polls/list/domain/SyncPollsUseCase.kt | 5 +- .../polls/RoomPollsViewModelTest.kt | 16 +++---- 11 files changed, 88 insertions(+), 41 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/GetLoadedPollsStatusTask.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/poll/PollHistoryService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/poll/PollHistoryService.kt index ad53febc50..866c3e6b81 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/poll/PollHistoryService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/poll/PollHistoryService.kt @@ -35,7 +35,7 @@ interface PollHistoryService { /** * Get the current status of the loaded polls. */ - fun getLoadedPollsStatus(): LoadedPollsStatus + suspend fun getLoadedPollsStatus(): LoadedPollsStatus /** * Sync polls from last loaded polls until now. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt index dab2d340b6..17e57aa2ee 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt @@ -89,7 +89,9 @@ import org.matrix.android.sdk.internal.session.room.peeking.DefaultPeekRoomTask import org.matrix.android.sdk.internal.session.room.peeking.DefaultResolveRoomStateTask import org.matrix.android.sdk.internal.session.room.peeking.PeekRoomTask import org.matrix.android.sdk.internal.session.room.peeking.ResolveRoomStateTask +import org.matrix.android.sdk.internal.session.room.poll.DefaultGetLoadedPollsStatusTask import org.matrix.android.sdk.internal.session.room.poll.DefaultLoadMorePollsTask +import org.matrix.android.sdk.internal.session.room.poll.GetLoadedPollsStatusTask import org.matrix.android.sdk.internal.session.room.poll.LoadMorePollsTask import org.matrix.android.sdk.internal.session.room.read.DefaultMarkAllRoomsReadTask import org.matrix.android.sdk.internal.session.room.read.DefaultSetReadMarkersTask @@ -364,4 +366,7 @@ internal abstract class RoomModule { @Binds abstract fun bindLoadMorePollsTask(task: DefaultLoadMorePollsTask): LoadMorePollsTask + + @Binds + abstract fun bindGetLoadedPollsStatusTask(task: DefaultGetLoadedPollsStatusTask): GetLoadedPollsStatusTask } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt index 74f59b6782..c4ba89f4a6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt @@ -34,6 +34,7 @@ internal class DefaultPollHistoryService @AssistedInject constructor( @Assisted private val roomId: String, private val clock: Clock, private val loadMorePollsTask: LoadMorePollsTask, + private val getLoadedPollsStatusTask: GetLoadedPollsStatusTask, ) : PollHistoryService { @AssistedFactory @@ -58,8 +59,12 @@ internal class DefaultPollHistoryService @AssistedInject constructor( return loadMorePollsTask.execute(params) } - override fun getLoadedPollsStatus(): LoadedPollsStatus { - TODO("Not yet implemented") + override suspend fun getLoadedPollsStatus(): LoadedPollsStatus { + val params = GetLoadedPollsStatusTask.Params( + roomId = roomId, + currentTimestampMs = clock.epochMillis(), + ) + return getLoadedPollsStatusTask.execute(params) } override suspend fun syncPolls() { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/GetLoadedPollsStatusTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/GetLoadedPollsStatusTask.kt new file mode 100644 index 0000000000..118c81a451 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/GetLoadedPollsStatusTask.kt @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2023 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.poll + +import com.zhuinden.monarchy.Monarchy +import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus +import org.matrix.android.sdk.internal.database.model.PollHistoryStatusEntity +import org.matrix.android.sdk.internal.database.query.getOrCreate +import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.task.Task +import org.matrix.android.sdk.internal.util.awaitTransaction +import javax.inject.Inject + +internal interface GetLoadedPollsStatusTask : Task { + data class Params( + val roomId: String, + val currentTimestampMs: Long, + ) +} + +internal class DefaultGetLoadedPollsStatusTask @Inject constructor( + @SessionDatabase private val monarchy: Monarchy, +) : GetLoadedPollsStatusTask { + + override suspend fun execute(params: GetLoadedPollsStatusTask.Params): LoadedPollsStatus { + return monarchy.awaitTransaction { realm -> + val status = PollHistoryStatusEntity.getOrCreate(realm, params.roomId) + LoadedPollsStatus( + canLoadMore = status.isEndOfPollsBackward.not(), + nbSyncedDays = status.getNbSyncedDays(params.currentTimestampMs), + ) + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt index b72486402b..fccdef87b8 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt @@ -48,26 +48,21 @@ class RoomPollsViewModel @AssistedInject constructor( init { val roomId = initialState.roomId - updateLoadedPollStatus(roomId) syncPolls(roomId) observePolls(roomId) } - private fun updateLoadedPollStatus(roomId: String) { - val loadedPollsStatus = getLoadedPollsStatusUseCase.execute(roomId) - setState { - copy( - canLoadMore = loadedPollsStatus.canLoadMore, - nbSyncedDays = loadedPollsStatus.nbSyncedDays, - ) - } - } - private fun syncPolls(roomId: String) { viewModelScope.launch { setState { copy(isSyncing = true) } val result = runCatching { - syncPollsUseCase.execute(roomId) + val loadedPollsStatus = syncPollsUseCase.execute(roomId) + setState { + copy( + canLoadMore = loadedPollsStatus.canLoadMore, + nbSyncedDays = loadedPollsStatus.nbSyncedDays, + ) + } } if (result.isFailure) { _viewEvents.post(RoomPollsViewEvent.LoadingError) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/PollHistoryError.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/PollHistoryError.kt index 37b7d934bb..67d59faebd 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/PollHistoryError.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/PollHistoryError.kt @@ -17,5 +17,5 @@ package im.vector.app.features.roomprofile.polls.list.data sealed class PollHistoryError : Exception() { - object LoadingError : PollHistoryError() + object UnknownRoomError : PollHistoryError() } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollDataSource.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollDataSource.kt index 72ca464951..f22f494f49 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollDataSource.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollDataSource.kt @@ -34,13 +34,13 @@ class RoomPollDataSource @Inject constructor( ) { private val pollsFlow = MutableSharedFlow>(replay = 1) - private var fakeLoadCounter = 0 - private fun getPollHistoryService(roomId: String): PollHistoryService? { + private fun getPollHistoryService(roomId: String): PollHistoryService { return activeSessionHolder .getSafeActiveSession() ?.getRoom(roomId) ?.pollHistoryService() + ?: throw PollHistoryError.UnknownRoomError } // TODO @@ -51,21 +51,12 @@ class RoomPollDataSource @Inject constructor( return pollsFlow.asSharedFlow() } - fun getLoadedPollsStatus(roomId: String): LoadedPollsStatus { - Timber.d("roomId=$roomId") - // TODO unmock using SDK - return LoadedPollsStatus( - canLoadMore = canLoadMore(), - nbSyncedDays = fakeLoadCounter * 30, - ) - } - - private fun canLoadMore(): Boolean { - return fakeLoadCounter < 2 + suspend fun getLoadedPollsStatus(roomId: String): LoadedPollsStatus { + return getPollHistoryService(roomId).getLoadedPollsStatus() } suspend fun loadMorePolls(roomId: String): LoadedPollsStatus { - return getPollHistoryService(roomId)?.loadMore() ?: throw PollHistoryError.LoadingError + return getPollHistoryService(roomId).loadMore() } suspend fun syncPolls(roomId: String) { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollRepository.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollRepository.kt index 6f9b780464..4679af4434 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollRepository.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollRepository.kt @@ -30,7 +30,7 @@ class RoomPollRepository @Inject constructor( return roomPollDataSource.getPolls(roomId) } - fun getLoadedPollsStatus(roomId: String): LoadedPollsStatus { + suspend fun getLoadedPollsStatus(roomId: String): LoadedPollsStatus { return roomPollDataSource.getLoadedPollsStatus(roomId) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/domain/GetLoadedPollsStatusUseCase.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/domain/GetLoadedPollsStatusUseCase.kt index 2bac26f79c..d37e27ff03 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/domain/GetLoadedPollsStatusUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/domain/GetLoadedPollsStatusUseCase.kt @@ -24,7 +24,7 @@ class GetLoadedPollsStatusUseCase @Inject constructor( private val roomPollRepository: RoomPollRepository, ) { - fun execute(roomId: String): LoadedPollsStatus { + suspend fun execute(roomId: String): LoadedPollsStatus { return roomPollRepository.getLoadedPollsStatus(roomId) } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/domain/SyncPollsUseCase.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/domain/SyncPollsUseCase.kt index b6a344f7f8..7346406c84 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/domain/SyncPollsUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/domain/SyncPollsUseCase.kt @@ -17,6 +17,7 @@ package im.vector.app.features.roomprofile.polls.list.domain import im.vector.app.features.roomprofile.polls.list.data.RoomPollRepository +import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus import javax.inject.Inject /** @@ -24,9 +25,11 @@ import javax.inject.Inject */ class SyncPollsUseCase @Inject constructor( private val roomPollRepository: RoomPollRepository, + private val getLoadedPollsStatusUseCase: GetLoadedPollsStatusUseCase, ) { - suspend fun execute(roomId: String) { + suspend fun execute(roomId: String): LoadedPollsStatus { roomPollRepository.syncPolls(roomId) + return getLoadedPollsStatusUseCase.execute(roomId) } } diff --git a/vector/src/test/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModelTest.kt b/vector/src/test/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModelTest.kt index 1cac603ae2..adbf32006e 100644 --- a/vector/src/test/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModelTest.kt @@ -17,7 +17,6 @@ package im.vector.app.features.roomprofile.polls import com.airbnb.mvrx.test.MavericksTestRule -import im.vector.app.features.roomprofile.polls.list.data.LoadedPollsStatus import im.vector.app.features.roomprofile.polls.list.domain.GetLoadedPollsStatusUseCase import im.vector.app.features.roomprofile.polls.list.domain.GetPollsUseCase import im.vector.app.features.roomprofile.polls.list.domain.LoadMorePollsUseCase @@ -34,6 +33,7 @@ import io.mockk.verify import kotlinx.coroutines.flow.flowOf import org.junit.Rule import org.junit.Test +import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus private const val A_ROOM_ID = "room-id" @@ -68,7 +68,7 @@ class RoomPollsViewModelTest { val expectedViewState = initialState.copy( polls = polls, canLoadMore = loadedPollsStatus.canLoadMore, - nbSyncedDays = loadedPollsStatus.nbLoadedDays, + nbSyncedDays = loadedPollsStatus.nbSyncedDays, ) // When @@ -116,7 +116,7 @@ class RoomPollsViewModelTest { val stateAfterInit = initialState.copy( polls = polls, canLoadMore = loadedPollsStatus.canLoadMore, - nbSyncedDays = loadedPollsStatus.nbLoadedDays, + nbSyncedDays = loadedPollsStatus.nbSyncedDays, ) // When @@ -128,7 +128,7 @@ class RoomPollsViewModelTest { .assertStatesChanges( stateAfterInit, { copy(isLoadingMore = true) }, - { copy(canLoadMore = newLoadedPollsStatus.canLoadMore, nbSyncedDays = newLoadedPollsStatus.nbLoadedDays) }, + { copy(canLoadMore = newLoadedPollsStatus.canLoadMore, nbSyncedDays = newLoadedPollsStatus.nbSyncedDays) }, { copy(isLoadingMore = false) }, ) .finish() @@ -148,20 +148,20 @@ class RoomPollsViewModelTest { } private fun givenLoadMoreWithSuccess(): LoadedPollsStatus { - val loadedPollsStatus = givenALoadedPollsStatus(canLoadMore = false, nbLoadedDays = 20) + val loadedPollsStatus = givenALoadedPollsStatus(canLoadMore = false, nbSyncedDays = 20) coEvery { fakeLoadMorePollsUseCase.execute(A_ROOM_ID) } returns loadedPollsStatus return loadedPollsStatus } private fun givenGetLoadedPollsStatusSuccess(): LoadedPollsStatus { val loadedPollsStatus = givenALoadedPollsStatus() - every { fakeGetLoadedPollsStatusUseCase.execute(A_ROOM_ID) } returns loadedPollsStatus + coEvery { fakeGetLoadedPollsStatusUseCase.execute(A_ROOM_ID) } returns loadedPollsStatus return loadedPollsStatus } - private fun givenALoadedPollsStatus(canLoadMore: Boolean = true, nbLoadedDays: Int = 10) = + private fun givenALoadedPollsStatus(canLoadMore: Boolean = true, nbSyncedDays: Int = 10) = LoadedPollsStatus( canLoadMore = canLoadMore, - nbLoadedDays = nbLoadedDays, + nbSyncedDays = nbSyncedDays, ) } From e3a2000e29153e1897140ae5aa88ad9d9085bc21 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Wed, 18 Jan 2023 15:03:51 +0100 Subject: [PATCH 026/189] Calling syncPolls of SDK service --- .../session/room/poll/DefaultPollHistoryService.kt | 11 ++++------- .../roomprofile/polls/list/data/RoomPollDataSource.kt | 6 +----- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt index c4ba89f4a6..e01d91d1be 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt @@ -20,11 +20,11 @@ import androidx.lifecycle.LiveData import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import kotlinx.coroutines.delay import org.matrix.android.sdk.api.session.room.model.PollResponseAggregatedSummary import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus import org.matrix.android.sdk.api.session.room.poll.PollHistoryService import org.matrix.android.sdk.internal.util.time.Clock -import timber.log.Timber private const val LOADING_PERIOD_IN_DAYS = 30 private const val EVENTS_PAGE_SIZE = 250 @@ -42,10 +42,6 @@ internal class DefaultPollHistoryService @AssistedInject constructor( fun create(roomId: String): DefaultPollHistoryService } - init { - Timber.d("init with roomId: $roomId") - } - override val loadingPeriodInDays: Int get() = LOADING_PERIOD_IN_DAYS @@ -68,10 +64,11 @@ internal class DefaultPollHistoryService @AssistedInject constructor( } override suspend fun syncPolls() { - TODO("Not yet implemented") + // TODO unmock + delay(1000) } override fun getPolls(): LiveData> { - TODO("Not yet implemented") + TODO("listen database and update query depending on latest PollHistoryStatusEntity.oldestTimestampReachedMs") } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollDataSource.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollDataSource.kt index f22f494f49..ee3b477685 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollDataSource.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollDataSource.kt @@ -18,7 +18,6 @@ package im.vector.app.features.roomprofile.polls.list.data import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.features.roomprofile.polls.list.ui.PollSummary -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.asSharedFlow @@ -60,9 +59,6 @@ class RoomPollDataSource @Inject constructor( } suspend fun syncPolls(roomId: String) { - Timber.d("roomId=$roomId") - // TODO unmock using SDK service - // fake sync - delay(1000) + getPollHistoryService(roomId).syncPolls() } } From 7ca532a5f6ccaedd8e916ab7820e51d3c9ee58b5 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Wed, 18 Jan 2023 15:24:09 +0100 Subject: [PATCH 027/189] Filter and store poll events --- .../sdk/internal/session/room/RoomModule.kt | 5 ++ .../room/event/FilterAndStoreEventsTask.kt | 80 +++++++++++++++++++ .../session/room/poll/LoadMorePollsTask.kt | 24 ++++-- .../poll/FetchPollResponseEventsTask.kt | 56 +++---------- 4 files changed, 114 insertions(+), 51 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/event/FilterAndStoreEventsTask.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt index 17e57aa2ee..56925d55d5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt @@ -59,6 +59,8 @@ import org.matrix.android.sdk.internal.session.room.directory.DefaultSetRoomDire import org.matrix.android.sdk.internal.session.room.directory.GetPublicRoomTask import org.matrix.android.sdk.internal.session.room.directory.GetRoomDirectoryVisibilityTask import org.matrix.android.sdk.internal.session.room.directory.SetRoomDirectoryVisibilityTask +import org.matrix.android.sdk.internal.session.room.event.DefaultFilterAndStoreEventsTask +import org.matrix.android.sdk.internal.session.room.event.FilterAndStoreEventsTask import org.matrix.android.sdk.internal.session.room.location.CheckIfExistingActiveLiveTask import org.matrix.android.sdk.internal.session.room.location.DefaultCheckIfExistingActiveLiveTask import org.matrix.android.sdk.internal.session.room.location.DefaultGetActiveBeaconInfoForUserTask @@ -369,4 +371,7 @@ internal abstract class RoomModule { @Binds abstract fun bindGetLoadedPollsStatusTask(task: DefaultGetLoadedPollsStatusTask): GetLoadedPollsStatusTask + + @Binds + abstract fun bindFilterAndStoreEventsTask(task: DefaultFilterAndStoreEventsTask): FilterAndStoreEventsTask } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/event/FilterAndStoreEventsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/event/FilterAndStoreEventsTask.kt new file mode 100644 index 0000000000..aa836c8491 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/event/FilterAndStoreEventsTask.kt @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2023 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.event + +import com.zhuinden.monarchy.Monarchy +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.room.send.SendState +import org.matrix.android.sdk.internal.crypto.EventDecryptor +import org.matrix.android.sdk.internal.database.mapper.toEntity +import org.matrix.android.sdk.internal.database.model.EventEntity +import org.matrix.android.sdk.internal.database.model.EventInsertType +import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore +import org.matrix.android.sdk.internal.database.query.where +import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.task.Task +import org.matrix.android.sdk.internal.util.awaitTransaction +import org.matrix.android.sdk.internal.util.time.Clock +import javax.inject.Inject + +internal interface FilterAndStoreEventsTask : Task { + data class Params( + val roomId: String, + val events: List, + val filterPredicate: (Event) -> Boolean, + ) +} + +internal class DefaultFilterAndStoreEventsTask @Inject constructor( + @SessionDatabase private val monarchy: Monarchy, + private val clock: Clock, + private val eventDecryptor: EventDecryptor, +) : FilterAndStoreEventsTask { + + override suspend fun execute(params: FilterAndStoreEventsTask.Params) { + val filteredEvents = params.events + .map { decryptEventIfNeeded(it) } + .filter { params.filterPredicate(it) } + + addMissingEventsInDB(params.roomId, filteredEvents) + } + + private suspend fun addMissingEventsInDB(roomId: String, events: List) { + monarchy.awaitTransaction { realm -> + val eventIdsToCheck = events.mapNotNull { it.eventId }.filter { it.isNotEmpty() } + if (eventIdsToCheck.isNotEmpty()) { + val existingIds = EventEntity.where(realm, eventIdsToCheck).findAll().toList().map { it.eventId } + + events.filterNot { it.eventId in existingIds } + .map { it.toEntity(roomId = roomId, sendState = SendState.SYNCED, ageLocalTs = computeLocalTs(it)) } + .forEach { it.copyToRealmOrIgnore(realm, EventInsertType.PAGINATION) } + } + } + } + + private suspend fun decryptEventIfNeeded(event: Event): Event { + if (event.isEncrypted()) { + eventDecryptor.decryptEventAndSaveResult(event, timeline = "") + } + + event.ageLocalTs = computeLocalTs(event) + + return event + } + + private fun computeLocalTs(event: Event) = clock.epochMillis() - (event.unsignedData?.age ?: 0) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/LoadMorePollsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/LoadMorePollsTask.kt index 03b6c31fec..7870897ace 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/LoadMorePollsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/LoadMorePollsTask.kt @@ -17,6 +17,8 @@ package org.matrix.android.sdk.internal.session.room.poll import com.zhuinden.monarchy.Monarchy +import org.matrix.android.sdk.api.session.events.model.isPoll +import org.matrix.android.sdk.api.session.events.model.isPollResponse import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus import org.matrix.android.sdk.internal.database.model.PollHistoryStatusEntity import org.matrix.android.sdk.internal.database.query.getOrCreate @@ -24,6 +26,7 @@ import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.room.RoomAPI +import org.matrix.android.sdk.internal.session.room.event.FilterAndStoreEventsTask import org.matrix.android.sdk.internal.session.room.poll.PollConstants.MILLISECONDS_PER_DAY import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection import org.matrix.android.sdk.internal.session.room.timeline.PaginationResponse @@ -44,6 +47,7 @@ internal class DefaultLoadMorePollsTask @Inject constructor( @SessionDatabase private val monarchy: Monarchy, private val roomAPI: RoomAPI, private val globalErrorReceiver: GlobalErrorReceiver, + private val filterAndStoreEventsTask: FilterAndStoreEventsTask, ) : LoadMorePollsTask { override suspend fun execute(params: LoadMorePollsTask.Params): LoadedPollsStatus { @@ -53,9 +57,10 @@ internal class DefaultLoadMorePollsTask @Inject constructor( currentPollHistoryStatus = fetchMorePollEventsBackward(params, currentPollHistoryStatus) } // TODO - // unmock and check how it behaves when cancelling the process: it should resume where it was stopped + // check how it behaves when cancelling the process: it should resume where it was stopped // check the network calls done using Flipper // check forward of error in case of call api failure + // test on large room return LoadedPollsStatus( canLoadMore = currentPollHistoryStatus.isEndOfPollsBackward.not(), @@ -89,7 +94,7 @@ internal class DefaultLoadMorePollsTask @Inject constructor( params: LoadMorePollsTask.Params, status: PollHistoryStatusEntity ): PollHistoryStatusEntity { - val chunk = executeRequest(globalErrorReceiver) { + val response = executeRequest(globalErrorReceiver) { roomAPI.getRoomMessagesFrom( roomId = params.roomId, from = status.tokenEndBackward, @@ -99,9 +104,18 @@ internal class DefaultLoadMorePollsTask @Inject constructor( ) } - // TODO decrypt events and filter in only polls to store them in local: see to mutualize with FetchPollResponseEventsTask + filterAndStorePollEvents(roomId = params.roomId, paginationResponse = response) - return updatePollHistoryStatus(roomId = params.roomId, paginationResponse = chunk) + return updatePollHistoryStatus(roomId = params.roomId, paginationResponse = response) + } + + private suspend fun filterAndStorePollEvents(roomId: String, paginationResponse: PaginationResponse) { + val filterTaskParams = FilterAndStoreEventsTask.Params( + roomId = roomId, + events = paginationResponse.events, + filterPredicate = { it.isPoll() || it.isPollResponse() } + ) + filterAndStoreEventsTask.execute(filterTaskParams) } private suspend fun updatePollHistoryStatus(roomId: String, paginationResponse: PaginationResponse): PollHistoryStatusEntity { @@ -124,7 +138,7 @@ internal class DefaultLoadMorePollsTask @Inject constructor( // start of the timeline is reached, there are no more events status.isEndOfPollsBackward = true status.oldestTimestampReachedMs = oldestEventTimestamp - } else if(oldestEventTimestamp != null && currentTargetTimestamp != null && oldestEventTimestamp <= currentTargetTimestamp) { + } else if (oldestEventTimestamp != null && currentTargetTimestamp != null && oldestEventTimestamp <= currentTargetTimestamp) { // target has been reached status.oldestTimestampReachedMs = oldestEventTimestamp status.tokenEndBackward = paginationResponse.end diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/poll/FetchPollResponseEventsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/poll/FetchPollResponseEventsTask.kt index e7dd8c57eb..347c9fbf12 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/poll/FetchPollResponseEventsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/poll/FetchPollResponseEventsTask.kt @@ -17,25 +17,14 @@ package org.matrix.android.sdk.internal.session.room.relation.poll import androidx.annotation.VisibleForTesting -import com.zhuinden.monarchy.Monarchy -import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.RelationType import org.matrix.android.sdk.api.session.events.model.isPollResponse -import org.matrix.android.sdk.api.session.room.send.SendState -import org.matrix.android.sdk.internal.crypto.EventDecryptor -import org.matrix.android.sdk.internal.database.mapper.toEntity -import org.matrix.android.sdk.internal.database.model.EventEntity -import org.matrix.android.sdk.internal.database.model.EventInsertType -import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore -import org.matrix.android.sdk.internal.database.query.where -import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.room.RoomAPI +import org.matrix.android.sdk.internal.session.room.event.FilterAndStoreEventsTask import org.matrix.android.sdk.internal.session.room.relation.RelationsResponse import org.matrix.android.sdk.internal.task.Task -import org.matrix.android.sdk.internal.util.awaitTransaction -import org.matrix.android.sdk.internal.util.time.Clock import javax.inject.Inject @VisibleForTesting @@ -54,10 +43,9 @@ internal interface FetchPollResponseEventsTask : Task = runCatching { var nextBatch: String? = fetchAndProcessRelatedEventsFrom(params) @@ -70,11 +58,12 @@ internal class DefaultFetchPollResponseEventsTask @Inject constructor( private suspend fun fetchAndProcessRelatedEventsFrom(params: FetchPollResponseEventsTask.Params, from: String? = null): String? { val response = getRelatedEvents(params, from) - val filteredEvents = response.chunks - .map { decryptEventIfNeeded(it) } - .filter { it.isPollResponse() } - - addMissingEventsInDB(params.roomId, filteredEvents) + val filterTaskParams = FilterAndStoreEventsTask.Params( + roomId = params.roomId, + events = response.chunks, + filterPredicate = { it.isPollResponse() } + ) + filterAndStoreEventsTask.execute(filterTaskParams) return response.nextBatch } @@ -90,29 +79,4 @@ internal class DefaultFetchPollResponseEventsTask @Inject constructor( ) } } - - private suspend fun addMissingEventsInDB(roomId: String, events: List) { - monarchy.awaitTransaction { realm -> - val eventIdsToCheck = events.mapNotNull { it.eventId }.filter { it.isNotEmpty() } - if (eventIdsToCheck.isNotEmpty()) { - val existingIds = EventEntity.where(realm, eventIdsToCheck).findAll().toList().map { it.eventId } - - events.filterNot { it.eventId in existingIds } - .map { it.toEntity(roomId = roomId, sendState = SendState.SYNCED, ageLocalTs = computeLocalTs(it)) } - .forEach { it.copyToRealmOrIgnore(realm, EventInsertType.PAGINATION) } - } - } - } - - private suspend fun decryptEventIfNeeded(event: Event): Event { - if (event.isEncrypted()) { - eventDecryptor.decryptEventAndSaveResult(event, timeline = "") - } - - event.ageLocalTs = computeLocalTs(event) - - return event - } - - private fun computeLocalTs(event: Event) = clock.epochMillis() - (event.unsignedData?.age ?: 0) } From 96252ec2af9cd42f27dffce189886a1774a12336 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu, 19 Jan 2023 16:10:33 +0100 Subject: [PATCH 028/189] Observation of the local events to render UI --- .../session/room/poll/PollHistoryService.kt | 6 +- .../room/poll/DefaultPollHistoryService.kt | 51 ++++++++++- .../session/room/poll/LoadMorePollsTask.kt | 4 +- .../factory/PollItemViewStateFactory.kt | 1 + .../roomprofile/polls/RoomPollsViewModel.kt | 6 +- .../polls/list/data/RoomPollDataSource.kt | 16 +--- .../polls/list/data/RoomPollRepository.kt | 5 +- .../polls/list/domain/GetPollsUseCase.kt | 6 +- .../polls/list/ui/PollSummaryMapper.kt | 84 +++++++++++++++++++ 9 files changed, 153 insertions(+), 26 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapper.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/poll/PollHistoryService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/poll/PollHistoryService.kt index 866c3e6b81..bd0d6f4ab5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/poll/PollHistoryService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/poll/PollHistoryService.kt @@ -17,7 +17,7 @@ package org.matrix.android.sdk.api.session.room.poll import androidx.lifecycle.LiveData -import org.matrix.android.sdk.api.session.room.model.PollResponseAggregatedSummary +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent /** * Expose methods to get history of polls in rooms. @@ -43,7 +43,7 @@ interface PollHistoryService { suspend fun syncPolls() /** - * Get currently loaded list of polls. See [loadMore]. + * Get currently loaded list of poll events. See [loadMore]. */ - fun getPolls(): LiveData> + fun getPollEvents(): LiveData> } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt index e01d91d1be..add6dd4b90 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt @@ -17,14 +17,28 @@ package org.matrix.android.sdk.internal.session.room.poll import androidx.lifecycle.LiveData +import androidx.lifecycle.Transformations +import com.zhuinden.monarchy.Monarchy import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import io.realm.kotlin.where import kotlinx.coroutines.delay +import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.room.model.PollResponseAggregatedSummary import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus import org.matrix.android.sdk.api.session.room.poll.PollHistoryService +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import org.matrix.android.sdk.internal.database.mapper.PollResponseAggregatedSummaryEntityMapper +import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper +import org.matrix.android.sdk.internal.database.model.PollHistoryStatusEntity +import org.matrix.android.sdk.internal.database.model.PollHistoryStatusEntityFields +import org.matrix.android.sdk.internal.database.model.PollResponseAggregatedSummaryEntity +import org.matrix.android.sdk.internal.database.model.TimelineEventEntity +import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields +import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.util.time.Clock +import timber.log.Timber private const val LOADING_PERIOD_IN_DAYS = 30 private const val EVENTS_PAGE_SIZE = 250 @@ -32,9 +46,11 @@ private const val EVENTS_PAGE_SIZE = 250 // TODO add unit tests internal class DefaultPollHistoryService @AssistedInject constructor( @Assisted private val roomId: String, + @SessionDatabase private val monarchy: Monarchy, private val clock: Clock, private val loadMorePollsTask: LoadMorePollsTask, private val getLoadedPollsStatusTask: GetLoadedPollsStatusTask, + private val timelineEventMapper: TimelineEventMapper, ) : PollHistoryService { @AssistedFactory @@ -68,7 +84,38 @@ internal class DefaultPollHistoryService @AssistedInject constructor( delay(1000) } - override fun getPolls(): LiveData> { - TODO("listen database and update query depending on latest PollHistoryStatusEntity.oldestTimestampReachedMs") + override fun getPollEvents(): LiveData> { + val pollHistoryStatusLiveData = getPollHistoryStatus() + + return Transformations.switchMap(pollHistoryStatusLiveData) { results -> + val oldestTimestamp = results.firstOrNull()?.oldestTimestampReachedMs ?: clock.epochMillis() + Timber.d("oldestTimestamp=$oldestTimestamp") + + monarchy.findAllMappedWithChanges( + { realm -> + val pollTypes = EventType.POLL_START.values.toTypedArray() + realm.where() + .equalTo(TimelineEventEntityFields.ROOM_ID, roomId) + .`in`(TimelineEventEntityFields.ROOT.TYPE, pollTypes) + .greaterThan(TimelineEventEntityFields.ROOT.ORIGIN_SERVER_TS, oldestTimestamp) + }, + { result -> + timelineEventMapper.map(result, buildReadReceipts = false) + } + ) + } + } + + private fun getPollHistoryStatus(): LiveData> { + return monarchy.findAllMappedWithChanges( + { realm -> + realm.where() + .equalTo(PollHistoryStatusEntityFields.ROOM_ID, roomId) + }, + { result -> + // make a copy of the Realm object since it will be used in another transformations + result.copy() + } + ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/LoadMorePollsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/LoadMorePollsTask.kt index 7870897ace..94b73ff211 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/LoadMorePollsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/LoadMorePollsTask.kt @@ -137,7 +137,9 @@ internal class DefaultLoadMorePollsTask @Inject constructor( if (paginationResponse.end == null) { // start of the timeline is reached, there are no more events status.isEndOfPollsBackward = true - status.oldestTimestampReachedMs = oldestEventTimestamp + if(oldestEventTimestamp != null && oldestEventTimestamp > 0) { + status.oldestTimestampReachedMs = oldestEventTimestamp + } } else if (oldestEventTimestamp != null && currentTargetTimestamp != null && oldestEventTimestamp <= currentTargetTimestamp) { // target has been reached status.oldestTimestampReachedMs = oldestEventTimestamp diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt index 7abc51fa51..e4f0fc3ba5 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt @@ -92,6 +92,7 @@ class PollItemViewStateFactory @Inject constructor( question = question, votesStatus = totalVotesText, canVote = false, + // TODO extract into helper method or mapper optionViewStates = pollCreationInfo?.answers?.map { answer -> val voteSummary = pollResponseSummary?.getVoteSummaryOfAnOption(answer.id ?: "") PollOptionViewState.PollEnded( diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt index fccdef87b8..83b71878cd 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt @@ -23,20 +23,21 @@ import dagger.assisted.AssistedInject import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.VectorViewModel -import im.vector.app.features.roomprofile.polls.list.domain.GetLoadedPollsStatusUseCase import im.vector.app.features.roomprofile.polls.list.domain.GetPollsUseCase import im.vector.app.features.roomprofile.polls.list.domain.LoadMorePollsUseCase import im.vector.app.features.roomprofile.polls.list.domain.SyncPollsUseCase +import im.vector.app.features.roomprofile.polls.list.ui.PollSummaryMapper import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch class RoomPollsViewModel @AssistedInject constructor( @Assisted initialState: RoomPollsViewState, private val getPollsUseCase: GetPollsUseCase, - private val getLoadedPollsStatusUseCase: GetLoadedPollsStatusUseCase, private val loadMorePollsUseCase: LoadMorePollsUseCase, private val syncPollsUseCase: SyncPollsUseCase, + private val pollSummaryMapper: PollSummaryMapper, ) : VectorViewModel(initialState) { @AssistedFactory @@ -73,6 +74,7 @@ class RoomPollsViewModel @AssistedInject constructor( private fun observePolls(roomId: String) { getPollsUseCase.execute(roomId) + .map { it.map { event -> pollSummaryMapper.map(event) } } .onEach { setState { copy(polls = it) } } .launchIn(viewModelScope) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollDataSource.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollDataSource.kt index ee3b477685..86f77afc29 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollDataSource.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollDataSource.kt @@ -16,15 +16,13 @@ package im.vector.app.features.roomprofile.polls.list.data +import androidx.lifecycle.asFlow import im.vector.app.core.di.ActiveSessionHolder -import im.vector.app.features.roomprofile.polls.list.ui.PollSummary import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.asSharedFlow import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus import org.matrix.android.sdk.api.session.room.poll.PollHistoryService -import timber.log.Timber +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import javax.inject.Inject // TODO add unit tests @@ -32,8 +30,6 @@ class RoomPollDataSource @Inject constructor( private val activeSessionHolder: ActiveSessionHolder, ) { - private val pollsFlow = MutableSharedFlow>(replay = 1) - private fun getPollHistoryService(roomId: String): PollHistoryService { return activeSessionHolder .getSafeActiveSession() @@ -42,12 +38,8 @@ class RoomPollDataSource @Inject constructor( ?: throw PollHistoryError.UnknownRoomError } - // TODO - // unmock using SDK service - // after unmock, expose domain layer model (entity) and do the mapping to PollSummary in the UI layer - fun getPolls(roomId: String): Flow> { - Timber.d("roomId=$roomId") - return pollsFlow.asSharedFlow() + fun getPolls(roomId: String): Flow> { + return getPollHistoryService(roomId).getPollEvents().asFlow() } suspend fun getLoadedPollsStatus(roomId: String): LoadedPollsStatus { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollRepository.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollRepository.kt index 4679af4434..ff29ffbdc0 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollRepository.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollRepository.kt @@ -16,17 +16,16 @@ package im.vector.app.features.roomprofile.polls.list.data -import im.vector.app.features.roomprofile.polls.list.ui.PollSummary import kotlinx.coroutines.flow.Flow import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import javax.inject.Inject class RoomPollRepository @Inject constructor( private val roomPollDataSource: RoomPollDataSource, ) { - // TODO after unmock, expose domain layer model (entity) and do the mapping to PollSummary in the UI layer - fun getPolls(roomId: String): Flow> { + fun getPolls(roomId: String): Flow> { return roomPollDataSource.getPolls(roomId) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/domain/GetPollsUseCase.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/domain/GetPollsUseCase.kt index be2afb226f..0f6316efde 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/domain/GetPollsUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/domain/GetPollsUseCase.kt @@ -17,17 +17,17 @@ package im.vector.app.features.roomprofile.polls.list.domain import im.vector.app.features.roomprofile.polls.list.data.RoomPollRepository -import im.vector.app.features.roomprofile.polls.list.ui.PollSummary import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import javax.inject.Inject class GetPollsUseCase @Inject constructor( private val roomPollRepository: RoomPollRepository, ) { - fun execute(roomId: String): Flow> { + fun execute(roomId: String): Flow> { return roomPollRepository.getPolls(roomId) - .map { it.sortedByDescending { poll -> poll.creationTimestamp } } + .map { it.sortedByDescending { event -> event.root.originServerTs } } } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapper.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapper.kt new file mode 100644 index 0000000000..821620e842 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapper.kt @@ -0,0 +1,84 @@ +/* + * 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.roomprofile.polls.list.ui + +import im.vector.app.core.extensions.getVectorLastMessageContent +import im.vector.app.features.home.room.detail.timeline.helper.PollResponseDataFactory +import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState +import im.vector.app.features.home.room.detail.timeline.item.PollResponseData +import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import javax.inject.Inject + +// TODO add unit tests +class PollSummaryMapper @Inject constructor( + private val pollResponseDataFactory: PollResponseDataFactory, +) { + + fun map(timelineEvent: TimelineEvent): PollSummary { + val content = timelineEvent.getVectorLastMessageContent() + val pollResponseData = pollResponseDataFactory.create(timelineEvent) + val eventId = timelineEvent.root.eventId.orEmpty() + val creationTimestamp = timelineEvent.root.originServerTs ?: 0 + if (eventId.isNotEmpty() && creationTimestamp > 0 && content is MessagePollContent && pollResponseData != null) { + return convertToPollSummary( + eventId = eventId, + creationTimestamp = creationTimestamp, + messagePollContent = content, + pollResponseData = pollResponseData + ) + } else { + throw IllegalStateException("expected MessagePollContent") + } + } + + private fun convertToPollSummary( + eventId: String, + creationTimestamp: Long, + messagePollContent: MessagePollContent, + pollResponseData: PollResponseData + ): PollSummary { + val pollCreationInfo = messagePollContent.getBestPollCreationInfo() + val pollTitle = pollCreationInfo?.question?.getBestQuestion().orEmpty() + return if (pollResponseData.isClosed) { + val winnerVoteCount = pollResponseData.winnerVoteCount + PollSummary.EndedPoll( + id = eventId, + creationTimestamp = creationTimestamp, + title = pollTitle, + totalVotes = pollResponseData.totalVotes, + // TODO mutualise this with PollItemViewStateFactory + winnerOptions = pollCreationInfo?.answers?.map { answer -> + val voteSummary = pollResponseData.getVoteSummaryOfAnOption(answer.id ?: "") + PollOptionViewState.PollEnded( + optionId = answer.id ?: "", + optionAnswer = answer.getBestAnswer() ?: "", + voteCount = voteSummary?.total ?: 0, + votePercentage = voteSummary?.percentage ?: 0.0, + isWinner = winnerVoteCount != 0 && voteSummary?.total == winnerVoteCount + ) + } ?: emptyList() + ) + } else { + PollSummary.ActivePoll( + id = eventId, + creationTimestamp = creationTimestamp, + title = pollTitle, + ) + } + } +} From 91904a3e8f099141ef834623040bec56650cbb35 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu, 19 Jan 2023 17:22:37 +0100 Subject: [PATCH 029/189] Create a dedicated factory for PollOptionViewState --- .../factory/PollItemViewStateFactory.kt | 53 ++---------- .../factory/PollOptionViewStateFactory.kt | 84 +++++++++++++++++++ .../polls/list/ui/PollSummaryMapper.kt | 18 +--- 3 files changed, 95 insertions(+), 60 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollOptionViewStateFactory.kt diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt index e4f0fc3ba5..3c1a1cfd85 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt @@ -19,7 +19,6 @@ package im.vector.app.features.home.room.detail.timeline.factory import im.vector.app.R import im.vector.app.core.resources.StringProvider import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData -import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState import im.vector.app.features.home.room.detail.timeline.item.PollResponseData import im.vector.app.features.poll.PollViewState import org.matrix.android.sdk.api.extensions.orFalse @@ -29,6 +28,7 @@ import javax.inject.Inject class PollItemViewStateFactory @Inject constructor( private val stringProvider: StringProvider, + private val pollOptionViewStateFactory: PollOptionViewStateFactory, ) { fun create( @@ -40,7 +40,6 @@ class PollItemViewStateFactory @Inject constructor( val question = pollCreationInfo?.question?.getBestQuestion().orEmpty() val pollResponseSummary = informationData.pollResponseAggregatedSummary - val winnerVoteCount = pollResponseSummary?.winnerVoteCount val totalVotes = pollResponseSummary?.totalVotes ?: 0 return when { @@ -48,7 +47,7 @@ class PollItemViewStateFactory @Inject constructor( createSendingPollViewState(question, pollCreationInfo) } informationData.pollResponseAggregatedSummary?.isClosed.orFalse() -> { - createEndedPollViewState(question, pollCreationInfo, pollResponseSummary, totalVotes, winnerVoteCount) + createEndedPollViewState(question, pollCreationInfo, pollResponseSummary, totalVotes) } pollContent.getBestPollCreationInfo()?.isUndisclosed().orFalse() -> { createUndisclosedPollViewState(question, pollCreationInfo, pollResponseSummary) @@ -67,12 +66,7 @@ class PollItemViewStateFactory @Inject constructor( question = question, votesStatus = stringProvider.getString(R.string.poll_no_votes_cast), canVote = false, - optionViewStates = pollCreationInfo?.answers?.map { answer -> - PollOptionViewState.PollSending( - optionId = answer.id ?: "", - optionAnswer = answer.getBestAnswer() ?: "" - ) - }, + optionViewStates = pollOptionViewStateFactory.createPollSendingOptions(pollCreationInfo), ) } @@ -81,7 +75,6 @@ class PollItemViewStateFactory @Inject constructor( pollCreationInfo: PollCreationInfo?, pollResponseSummary: PollResponseData?, totalVotes: Int, - winnerVoteCount: Int?, ): PollViewState { val totalVotesText = if (pollResponseSummary?.hasEncryptedRelatedEvents.orFalse()) { stringProvider.getString(R.string.unable_to_decrypt_some_events_in_poll) @@ -92,17 +85,7 @@ class PollItemViewStateFactory @Inject constructor( question = question, votesStatus = totalVotesText, canVote = false, - // TODO extract into helper method or mapper - optionViewStates = pollCreationInfo?.answers?.map { answer -> - val voteSummary = pollResponseSummary?.getVoteSummaryOfAnOption(answer.id ?: "") - PollOptionViewState.PollEnded( - optionId = answer.id ?: "", - optionAnswer = answer.getBestAnswer() ?: "", - voteCount = voteSummary?.total ?: 0, - votePercentage = voteSummary?.percentage ?: 0.0, - isWinner = winnerVoteCount != 0 && voteSummary?.total == winnerVoteCount - ) - }, + optionViewStates = pollOptionViewStateFactory.createPollEndedOptions(pollCreationInfo, pollResponseSummary), ) } @@ -115,14 +98,7 @@ class PollItemViewStateFactory @Inject constructor( question = question, votesStatus = stringProvider.getString(R.string.poll_undisclosed_not_ended), canVote = true, - optionViewStates = pollCreationInfo?.answers?.map { answer -> - val isMyVote = pollResponseSummary?.myVote == answer.id - PollOptionViewState.PollUndisclosed( - optionId = answer.id ?: "", - optionAnswer = answer.getBestAnswer() ?: "", - isSelected = isMyVote - ) - }, + optionViewStates = pollOptionViewStateFactory.createPollUndisclosedOptions(pollCreationInfo, pollResponseSummary), ) } @@ -141,17 +117,7 @@ class PollItemViewStateFactory @Inject constructor( question = question, votesStatus = totalVotesText, canVote = true, - optionViewStates = pollCreationInfo?.answers?.map { answer -> - val isMyVote = pollResponseSummary?.myVote == answer.id - val voteSummary = pollResponseSummary?.getVoteSummaryOfAnOption(answer.id ?: "") - PollOptionViewState.PollVoted( - optionId = answer.id ?: "", - optionAnswer = answer.getBestAnswer() ?: "", - voteCount = voteSummary?.total ?: 0, - votePercentage = voteSummary?.percentage ?: 0.0, - isSelected = isMyVote - ) - }, + optionViewStates = pollOptionViewStateFactory.createPollVotedOptions(pollCreationInfo, pollResponseSummary), ) } @@ -169,12 +135,7 @@ class PollItemViewStateFactory @Inject constructor( question = question, votesStatus = totalVotesText, canVote = true, - optionViewStates = pollCreationInfo?.answers?.map { answer -> - PollOptionViewState.PollReady( - optionId = answer.id ?: "", - optionAnswer = answer.getBestAnswer() ?: "" - ) - }, + optionViewStates = pollOptionViewStateFactory.createPollReadyOptions(pollCreationInfo), ) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollOptionViewStateFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollOptionViewStateFactory.kt new file mode 100644 index 0000000000..3000164f74 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollOptionViewStateFactory.kt @@ -0,0 +1,84 @@ +/* + * 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.factory + +import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState +import im.vector.app.features.home.room.detail.timeline.item.PollResponseData +import org.matrix.android.sdk.api.session.room.model.message.PollCreationInfo +import javax.inject.Inject + +// TODO add unit tests +class PollOptionViewStateFactory @Inject constructor() { + + fun createPollEndedOptions(pollCreationInfo: PollCreationInfo?, pollResponseData: PollResponseData?): List { + val winnerVoteCount = pollResponseData?.winnerVoteCount + + return pollCreationInfo?.answers?.map { answer -> + val voteSummary = pollResponseData?.getVoteSummaryOfAnOption(answer.id ?: "") + PollOptionViewState.PollEnded( + optionId = answer.id.orEmpty(), + optionAnswer = answer.getBestAnswer().orEmpty(), + voteCount = voteSummary?.total ?: 0, + votePercentage = voteSummary?.percentage ?: 0.0, + isWinner = winnerVoteCount != 0 && voteSummary?.total == winnerVoteCount + ) + } ?: emptyList() + } + + fun createPollSendingOptions(pollCreationInfo: PollCreationInfo?): List { + return pollCreationInfo?.answers?.map { answer -> + PollOptionViewState.PollSending( + optionId = answer.id.orEmpty(), + optionAnswer = answer.getBestAnswer().orEmpty(), + ) + } ?: emptyList() + } + + fun createPollUndisclosedOptions(pollCreationInfo: PollCreationInfo?, pollResponseData: PollResponseData?): List { + return pollCreationInfo?.answers?.map { answer -> + val isMyVote = pollResponseData?.myVote == answer.id + PollOptionViewState.PollUndisclosed( + optionId = answer.id.orEmpty(), + optionAnswer = answer.getBestAnswer().orEmpty(), + isSelected = isMyVote + ) + } ?: emptyList() + } + + fun createPollVotedOptions(pollCreationInfo: PollCreationInfo?, pollResponseData: PollResponseData?): List { + return pollCreationInfo?.answers?.map { answer -> + val isMyVote = pollResponseData?.myVote == answer.id + val voteSummary = pollResponseData?.getVoteSummaryOfAnOption(answer.id ?: "") + PollOptionViewState.PollVoted( + optionId = answer.id.orEmpty(), + optionAnswer = answer.getBestAnswer().orEmpty(), + voteCount = voteSummary?.total ?: 0, + votePercentage = voteSummary?.percentage ?: 0.0, + isSelected = isMyVote + ) + } ?: emptyList() + } + + fun createPollReadyOptions(pollCreationInfo: PollCreationInfo?): List { + return pollCreationInfo?.answers?.map { answer -> + PollOptionViewState.PollReady( + optionId = answer.id ?: "", + optionAnswer = answer.getBestAnswer() ?: "" + ) + } ?: emptyList() + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapper.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapper.kt index 821620e842..f16e41e944 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapper.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapper.kt @@ -17,8 +17,8 @@ package im.vector.app.features.roomprofile.polls.list.ui import im.vector.app.core.extensions.getVectorLastMessageContent +import im.vector.app.features.home.room.detail.timeline.factory.PollOptionViewStateFactory import im.vector.app.features.home.room.detail.timeline.helper.PollResponseDataFactory -import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState import im.vector.app.features.home.room.detail.timeline.item.PollResponseData import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent @@ -27,6 +27,7 @@ import javax.inject.Inject // TODO add unit tests class PollSummaryMapper @Inject constructor( private val pollResponseDataFactory: PollResponseDataFactory, + private val pollOptionViewStateFactory: PollOptionViewStateFactory, ) { fun map(timelineEvent: TimelineEvent): PollSummary { @@ -42,7 +43,7 @@ class PollSummaryMapper @Inject constructor( pollResponseData = pollResponseData ) } else { - throw IllegalStateException("expected MessagePollContent") + throw IllegalStateException("missing mandatory info about poll event with id=$eventId") } } @@ -55,23 +56,12 @@ class PollSummaryMapper @Inject constructor( val pollCreationInfo = messagePollContent.getBestPollCreationInfo() val pollTitle = pollCreationInfo?.question?.getBestQuestion().orEmpty() return if (pollResponseData.isClosed) { - val winnerVoteCount = pollResponseData.winnerVoteCount PollSummary.EndedPoll( id = eventId, creationTimestamp = creationTimestamp, title = pollTitle, totalVotes = pollResponseData.totalVotes, - // TODO mutualise this with PollItemViewStateFactory - winnerOptions = pollCreationInfo?.answers?.map { answer -> - val voteSummary = pollResponseData.getVoteSummaryOfAnOption(answer.id ?: "") - PollOptionViewState.PollEnded( - optionId = answer.id ?: "", - optionAnswer = answer.getBestAnswer() ?: "", - voteCount = voteSummary?.total ?: 0, - votePercentage = voteSummary?.percentage ?: 0.0, - isWinner = winnerVoteCount != 0 && voteSummary?.total == winnerVoteCount - ) - } ?: emptyList() + winnerOptions = pollOptionViewStateFactory.createPollEndedOptions(pollCreationInfo, pollResponseData) ) } else { PollSummary.ActivePoll( From 3ba2c47d1eab7bec68aa04dfda007e97ad51efbb Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu, 19 Jan 2023 17:30:06 +0100 Subject: [PATCH 030/189] Load more poll during sync if there is no completed backward load --- .../sdk/api/session/room/poll/LoadedPollsStatus.kt | 1 + .../session/room/poll/GetLoadedPollsStatusTask.kt | 1 + .../internal/session/room/poll/LoadMorePollsTask.kt | 1 + .../roomprofile/polls/list/domain/SyncPollsUseCase.kt | 11 +++++++++-- 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/poll/LoadedPollsStatus.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/poll/LoadedPollsStatus.kt index f4a7dcc6c2..efc01e2cdf 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/poll/LoadedPollsStatus.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/poll/LoadedPollsStatus.kt @@ -22,4 +22,5 @@ package org.matrix.android.sdk.api.session.room.poll data class LoadedPollsStatus( val canLoadMore: Boolean, val nbSyncedDays: Int, + val hasCompletedASyncBackward: Boolean, ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/GetLoadedPollsStatusTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/GetLoadedPollsStatusTask.kt index 118c81a451..98b1e5931a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/GetLoadedPollsStatusTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/GetLoadedPollsStatusTask.kt @@ -42,6 +42,7 @@ internal class DefaultGetLoadedPollsStatusTask @Inject constructor( LoadedPollsStatus( canLoadMore = status.isEndOfPollsBackward.not(), nbSyncedDays = status.getNbSyncedDays(params.currentTimestampMs), + hasCompletedASyncBackward = status.hasCompletedASyncBackward, ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/LoadMorePollsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/LoadMorePollsTask.kt index 94b73ff211..0858d2ae91 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/LoadMorePollsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/LoadMorePollsTask.kt @@ -65,6 +65,7 @@ internal class DefaultLoadMorePollsTask @Inject constructor( return LoadedPollsStatus( canLoadMore = currentPollHistoryStatus.isEndOfPollsBackward.not(), nbSyncedDays = currentPollHistoryStatus.getNbSyncedDays(params.currentTimestampMs), + hasCompletedASyncBackward = currentPollHistoryStatus.hasCompletedASyncBackward, ) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/domain/SyncPollsUseCase.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/domain/SyncPollsUseCase.kt index 7346406c84..7d58fb7694 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/domain/SyncPollsUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/domain/SyncPollsUseCase.kt @@ -21,15 +21,22 @@ import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus import javax.inject.Inject /** - * Sync the polls of a given room from last manual loading (see LoadMorePollsUseCase) until now. + * Sync the polls of a given room from last manual loading if any (see LoadMorePollsUseCase) until now. + * Resume or start loading more to have at least a complete load. */ class SyncPollsUseCase @Inject constructor( private val roomPollRepository: RoomPollRepository, private val getLoadedPollsStatusUseCase: GetLoadedPollsStatusUseCase, + private val loadMorePollsUseCase: LoadMorePollsUseCase, ) { suspend fun execute(roomId: String): LoadedPollsStatus { roomPollRepository.syncPolls(roomId) - return getLoadedPollsStatusUseCase.execute(roomId) + val loadedStatus = getLoadedPollsStatusUseCase.execute(roomId) + return if (loadedStatus.hasCompletedASyncBackward) { + loadedStatus + } else { + loadMorePollsUseCase.execute(roomId) + } } } From 6b6dea0c457ae0dd5a26bf4b20a6158ddf17dbb8 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu, 19 Jan 2023 17:48:19 +0100 Subject: [PATCH 031/189] Store in DB events which failed to be decrypted --- .../internal/session/room/event/FilterAndStoreEventsTask.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/event/FilterAndStoreEventsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/event/FilterAndStoreEventsTask.kt index aa836c8491..e6e169b9b4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/event/FilterAndStoreEventsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/event/FilterAndStoreEventsTask.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.room.event import com.zhuinden.monarchy.Monarchy 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.room.send.SendState import org.matrix.android.sdk.internal.crypto.EventDecryptor import org.matrix.android.sdk.internal.database.mapper.toEntity @@ -48,7 +49,9 @@ internal class DefaultFilterAndStoreEventsTask @Inject constructor( override suspend fun execute(params: FilterAndStoreEventsTask.Params) { val filteredEvents = params.events .map { decryptEventIfNeeded(it) } - .filter { params.filterPredicate(it) } + // we also filter in the encrypted events since it means there was decryption error for them + // and they may be decrypted later + .filter { params.filterPredicate(it) || it.getClearType() == EventType.ENCRYPTED } addMissingEventsInDB(params.roomId, filteredEvents) } From a3077dfaa783b46b5f677a37b527356860c46552 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri, 20 Jan 2023 09:46:13 +0100 Subject: [PATCH 032/189] Fix mapping to PollSummary: case of poll without any votes --- .../roomprofile/polls/RoomPollsViewModel.kt | 3 ++- .../roomprofile/polls/list/ui/PollSummaryMapper.kt | 14 ++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt index 83b71878cd..4b1a79f561 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt @@ -29,6 +29,7 @@ import im.vector.app.features.roomprofile.polls.list.domain.SyncPollsUseCase import im.vector.app.features.roomprofile.polls.list.ui.PollSummaryMapper import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch @@ -74,7 +75,7 @@ class RoomPollsViewModel @AssistedInject constructor( private fun observePolls(roomId: String) { getPollsUseCase.execute(roomId) - .map { it.map { event -> pollSummaryMapper.map(event) } } + .map { it.mapNotNull { event -> pollSummaryMapper.map(event) } } .onEach { setState { copy(polls = it) } } .launchIn(viewModelScope) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapper.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapper.kt index f16e41e944..0bd7cd33af 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapper.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapper.kt @@ -22,6 +22,7 @@ import im.vector.app.features.home.room.detail.timeline.helper.PollResponseDataF import im.vector.app.features.home.room.detail.timeline.item.PollResponseData import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import timber.log.Timber import javax.inject.Inject // TODO add unit tests @@ -30,20 +31,21 @@ class PollSummaryMapper @Inject constructor( private val pollOptionViewStateFactory: PollOptionViewStateFactory, ) { - fun map(timelineEvent: TimelineEvent): PollSummary { + fun map(timelineEvent: TimelineEvent): PollSummary? { val content = timelineEvent.getVectorLastMessageContent() val pollResponseData = pollResponseDataFactory.create(timelineEvent) val eventId = timelineEvent.root.eventId.orEmpty() val creationTimestamp = timelineEvent.root.originServerTs ?: 0 - if (eventId.isNotEmpty() && creationTimestamp > 0 && content is MessagePollContent && pollResponseData != null) { - return convertToPollSummary( + return if (eventId.isNotEmpty() && creationTimestamp > 0 && content is MessagePollContent) { + convertToPollSummary( eventId = eventId, creationTimestamp = creationTimestamp, messagePollContent = content, pollResponseData = pollResponseData ) } else { - throw IllegalStateException("missing mandatory info about poll event with id=$eventId") + Timber.w("missing mandatory info about poll event with id=$eventId") + null } } @@ -51,11 +53,11 @@ class PollSummaryMapper @Inject constructor( eventId: String, creationTimestamp: Long, messagePollContent: MessagePollContent, - pollResponseData: PollResponseData + pollResponseData: PollResponseData? ): PollSummary { val pollCreationInfo = messagePollContent.getBestPollCreationInfo() val pollTitle = pollCreationInfo?.question?.getBestQuestion().orEmpty() - return if (pollResponseData.isClosed) { + return if (pollResponseData?.isClosed == true) { PollSummary.EndedPoll( id = eventId, creationTimestamp = creationTimestamp, From 4cfd6d29fcb8e8dd86b49376ee24f424f536b7ba Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri, 20 Jan 2023 15:29:39 +0100 Subject: [PATCH 033/189] Fix query on poll events for encrypted rooms --- .../room/poll/DefaultPollHistoryService.kt | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt index add6dd4b90..0d4e54f114 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt @@ -25,15 +25,12 @@ import dagger.assisted.AssistedInject import io.realm.kotlin.where import kotlinx.coroutines.delay import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.room.model.PollResponseAggregatedSummary import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus import org.matrix.android.sdk.api.session.room.poll.PollHistoryService import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent -import org.matrix.android.sdk.internal.database.mapper.PollResponseAggregatedSummaryEntityMapper import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper import org.matrix.android.sdk.internal.database.model.PollHistoryStatusEntity import org.matrix.android.sdk.internal.database.model.PollHistoryStatusEntityFields -import org.matrix.android.sdk.internal.database.model.PollResponseAggregatedSummaryEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields import org.matrix.android.sdk.internal.di.SessionDatabase @@ -90,19 +87,26 @@ internal class DefaultPollHistoryService @AssistedInject constructor( return Transformations.switchMap(pollHistoryStatusLiveData) { results -> val oldestTimestamp = results.firstOrNull()?.oldestTimestampReachedMs ?: clock.epochMillis() Timber.d("oldestTimestamp=$oldestTimestamp") + getPollStartEventsAfter(oldestTimestamp) + } + } - monarchy.findAllMappedWithChanges( - { realm -> - val pollTypes = EventType.POLL_START.values.toTypedArray() - realm.where() - .equalTo(TimelineEventEntityFields.ROOM_ID, roomId) - .`in`(TimelineEventEntityFields.ROOT.TYPE, pollTypes) - .greaterThan(TimelineEventEntityFields.ROOT.ORIGIN_SERVER_TS, oldestTimestamp) - }, - { result -> - timelineEventMapper.map(result, buildReadReceipts = false) - } - ) + private fun getPollStartEventsAfter(timestampMs: Long): LiveData> { + val eventsLiveData = monarchy.findAllMappedWithChanges( + { realm -> + val pollTypes = (EventType.POLL_START.values + EventType.ENCRYPTED).toTypedArray() + realm.where() + .equalTo(TimelineEventEntityFields.ROOM_ID, roomId) + .`in`(TimelineEventEntityFields.ROOT.TYPE, pollTypes) + .greaterThan(TimelineEventEntityFields.ROOT.ORIGIN_SERVER_TS, timestampMs) + }, + { result -> + timelineEventMapper.map(result, buildReadReceipts = false) + } + ) + + return Transformations.map(eventsLiveData) { events -> + events.filter { it.root.getClearType() in EventType.POLL_START.values } } } From 492b8a012ddd1ef9c0d5cd8dbe33c59c25c8621a Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri, 20 Jan 2023 17:38:55 +0100 Subject: [PATCH 034/189] Use Timeline interface to paginate --- .../session/room/poll/PollHistoryService.kt | 6 ++ .../database/model/PollHistoryStatusEntity.kt | 36 +++---- .../sdk/internal/session/room/RoomFactory.kt | 5 +- .../room/poll/DefaultPollHistoryService.kt | 25 ++++- .../session/room/poll/LoadMorePollsTask.kt | 99 +++++++++---------- .../roomprofile/polls/RoomPollsViewModel.kt | 8 +- .../polls/list/data/RoomPollDataSource.kt | 4 + .../polls/list/data/RoomPollRepository.kt | 4 + .../list/domain/DisposePollHistoryUseCase.kt | 32 ++++++ 9 files changed, 143 insertions(+), 76 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/list/domain/DisposePollHistoryUseCase.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/poll/PollHistoryService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/poll/PollHistoryService.kt index bd0d6f4ab5..b62f5a1969 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/poll/PollHistoryService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/poll/PollHistoryService.kt @@ -26,6 +26,12 @@ interface PollHistoryService { val loadingPeriodInDays: Int + /** + * This must be called when you don't need the service anymore. + * It ensures the underlying database get closed. + */ + fun dispose() + /** * Ask to load more polls starting from last loaded polls for a period defined by * [loadingPeriodInDays]. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PollHistoryStatusEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PollHistoryStatusEntity.kt index a1c270e56e..35075ffa0e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PollHistoryStatusEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PollHistoryStatusEntity.kt @@ -36,24 +36,24 @@ internal open class PollHistoryStatusEntity( var currentTimestampTargetBackwardMs: Long? = null, /** - * Timestamp of the oldest event synced in milliseconds. + * Timestamp of the oldest event synced once target has been reached in milliseconds. */ - var oldestTimestampReachedMs: Long? = null, + var oldestTimestampTargetReachedMs: Long? = null, + + /** + * Id of the oldest event synced. + */ + var oldestEventIdReached: String? = null, + + /** + * Id of the most recent event synced. + */ + var mostRecentEventIdReached: String? = null, /** * Indicate whether all polls in a room have been synced in backward direction. */ var isEndOfPollsBackward: Boolean = false, - - /** - * Token of the end of the last synced chunk in backward direction. - */ - var tokenEndBackward: String? = null, - - /** - * Token of the start of the last synced chunk in forward direction. - */ - var tokenStartForward: String? = null, ) : RealmObject() { companion object @@ -65,10 +65,10 @@ internal open class PollHistoryStatusEntity( return PollHistoryStatusEntity( roomId = roomId, currentTimestampTargetBackwardMs = currentTimestampTargetBackwardMs, - oldestTimestampReachedMs = oldestTimestampReachedMs, + oldestTimestampTargetReachedMs = oldestTimestampTargetReachedMs, + oldestEventIdReached = oldestEventIdReached, + mostRecentEventIdReached = mostRecentEventIdReached, isEndOfPollsBackward = isEndOfPollsBackward, - tokenEndBackward = tokenEndBackward, - tokenStartForward = tokenStartForward, ) } @@ -76,7 +76,7 @@ internal open class PollHistoryStatusEntity( * Indicate whether at least one poll sync has been fully completed backward for the given room. */ val hasCompletedASyncBackward: Boolean - get() = oldestTimestampReachedMs != null + get() = oldestTimestampTargetReachedMs != null /** * Indicate whether all polls in a room have been synced for the current timestamp target in backward direction. @@ -86,7 +86,7 @@ internal open class PollHistoryStatusEntity( private fun checkIfCurrentTimestampTargetBackwardIsReached(): Boolean { val currentTarget = currentTimestampTargetBackwardMs - val lastTarget = oldestTimestampReachedMs + val lastTarget = oldestTimestampTargetReachedMs // last timestamp target should be older or equal to the current target return currentTarget != null && lastTarget != null && lastTarget <= currentTarget } @@ -95,7 +95,7 @@ internal open class PollHistoryStatusEntity( * Compute the number of days of history currently synced. */ fun getNbSyncedDays(currentMs: Long): Int { - val oldestTimestamp = oldestTimestampReachedMs + val oldestTimestamp = oldestTimestampTargetReachedMs return if (oldestTimestamp == null) { 0 } else { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt index 86c414863d..a3fa11dedb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt @@ -77,11 +77,12 @@ internal class DefaultRoomFactory @Inject constructor( ) : RoomFactory { override fun create(roomId: String): Room { + val timelineService = timelineServiceFactory.create(roomId) return DefaultRoom( roomId = roomId, roomSummaryDataSource = roomSummaryDataSource, roomCryptoService = roomCryptoServiceFactory.create(roomId), - timelineService = timelineServiceFactory.create(roomId), + timelineService = timelineService, threadsService = threadsServiceFactory.create(roomId), threadsLocalService = threadsLocalServiceFactory.create(roomId), sendService = sendServiceFactory.create(roomId), @@ -101,7 +102,7 @@ internal class DefaultRoomFactory @Inject constructor( roomVersionService = roomVersionServiceFactory.create(roomId), viaParameterFinder = viaParameterFinder, locationSharingService = locationSharingServiceFactory.create(roomId), - pollHistoryService = pollHistoryServiceFactory.create(roomId), + pollHistoryService = pollHistoryServiceFactory.create(roomId, timelineService), coroutineDispatchers = coroutineDispatchers ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt index 0d4e54f114..0f57b81cb8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt @@ -28,6 +28,8 @@ import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus import org.matrix.android.sdk.api.session.room.poll.PollHistoryService import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import org.matrix.android.sdk.api.session.room.timeline.TimelineService +import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper import org.matrix.android.sdk.internal.database.model.PollHistoryStatusEntity import org.matrix.android.sdk.internal.database.model.PollHistoryStatusEntityFields @@ -43,6 +45,7 @@ private const val EVENTS_PAGE_SIZE = 250 // TODO add unit tests internal class DefaultPollHistoryService @AssistedInject constructor( @Assisted private val roomId: String, + @Assisted private val timelineService: TimelineService, @SessionDatabase private val monarchy: Monarchy, private val clock: Clock, private val loadMorePollsTask: LoadMorePollsTask, @@ -52,14 +55,30 @@ internal class DefaultPollHistoryService @AssistedInject constructor( @AssistedFactory interface Factory { - fun create(roomId: String): DefaultPollHistoryService + fun create(roomId: String, timelineService: TimelineService): DefaultPollHistoryService } override val loadingPeriodInDays: Int get() = LOADING_PERIOD_IN_DAYS + private val timeline by lazy { + // TODO check if we need to add a way to avoid using the current filter in rooms + val settings = TimelineSettings( + initialSize = EVENTS_PAGE_SIZE, + buildReadReceipts = false, + rootThreadEventId = null, + useLiveSenderInfo = false, + ) + timelineService.createTimeline(eventId = null, settings = settings).also { it.start() } + } + + override fun dispose() { + timeline.dispose() + } + override suspend fun loadMore(): LoadedPollsStatus { val params = LoadMorePollsTask.Params( + timeline = timeline, roomId = roomId, currentTimestampMs = clock.epochMillis(), loadingPeriodInDays = loadingPeriodInDays, @@ -78,6 +97,8 @@ internal class DefaultPollHistoryService @AssistedInject constructor( override suspend fun syncPolls() { // TODO unmock + // TODO when sync forward, jump to most recent event Id + paginate forward + jump to oldest eventId after + // TODO avoid possibility to call sync and loadMore at the same time from the service API, how? delay(1000) } @@ -85,7 +106,7 @@ internal class DefaultPollHistoryService @AssistedInject constructor( val pollHistoryStatusLiveData = getPollHistoryStatus() return Transformations.switchMap(pollHistoryStatusLiveData) { results -> - val oldestTimestamp = results.firstOrNull()?.oldestTimestampReachedMs ?: clock.epochMillis() + val oldestTimestamp = results.firstOrNull()?.oldestTimestampTargetReachedMs ?: clock.epochMillis() Timber.d("oldestTimestamp=$oldestTimestamp") getPollStartEventsAfter(oldestTimestamp) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/LoadMorePollsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/LoadMorePollsTask.kt index 0858d2ae91..f22447f19c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/LoadMorePollsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/LoadMorePollsTask.kt @@ -17,25 +17,20 @@ package org.matrix.android.sdk.internal.session.room.poll import com.zhuinden.monarchy.Monarchy -import org.matrix.android.sdk.api.session.events.model.isPoll -import org.matrix.android.sdk.api.session.events.model.isPollResponse import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus +import org.matrix.android.sdk.api.session.room.timeline.Timeline +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.internal.database.model.PollHistoryStatusEntity import org.matrix.android.sdk.internal.database.query.getOrCreate import org.matrix.android.sdk.internal.di.SessionDatabase -import org.matrix.android.sdk.internal.network.GlobalErrorReceiver -import org.matrix.android.sdk.internal.network.executeRequest -import org.matrix.android.sdk.internal.session.room.RoomAPI -import org.matrix.android.sdk.internal.session.room.event.FilterAndStoreEventsTask import org.matrix.android.sdk.internal.session.room.poll.PollConstants.MILLISECONDS_PER_DAY -import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection -import org.matrix.android.sdk.internal.session.room.timeline.PaginationResponse import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.util.awaitTransaction import javax.inject.Inject internal interface LoadMorePollsTask : Task { data class Params( + val timeline: Timeline, val roomId: String, val currentTimestampMs: Long, val loadingPeriodInDays: Int, @@ -45,16 +40,15 @@ internal interface LoadMorePollsTask : Task val status = PollHistoryStatusEntity.getOrCreate(realm, params.roomId) val currentTargetTimestampMs = status.currentTimestampTargetBackwardMs - val lastTargetTimestampMs = status.oldestTimestampReachedMs + val lastTargetTimestampMs = status.oldestTimestampTargetReachedMs val loadingPeriodMs: Long = MILLISECONDS_PER_DAY * params.loadingPeriodInDays.toLong() if (currentTargetTimestampMs == null) { // first load, compute the target timestamp @@ -91,62 +85,61 @@ internal class DefaultLoadMorePollsTask @Inject constructor( } } - private suspend fun fetchMorePollEventsBackward( - params: LoadMorePollsTask.Params, - status: PollHistoryStatusEntity - ): PollHistoryStatusEntity { - val response = executeRequest(globalErrorReceiver) { - roomAPI.getRoomMessagesFrom( - roomId = params.roomId, - from = status.tokenEndBackward, - dir = PaginationDirection.BACKWARDS.value, - limit = params.eventsPageSize, - filter = null - ) - } - - filterAndStorePollEvents(roomId = params.roomId, paginationResponse = response) - - return updatePollHistoryStatus(roomId = params.roomId, paginationResponse = response) - } - - private suspend fun filterAndStorePollEvents(roomId: String, paginationResponse: PaginationResponse) { - val filterTaskParams = FilterAndStoreEventsTask.Params( - roomId = roomId, - events = paginationResponse.events, - filterPredicate = { it.isPoll() || it.isPollResponse() } + private suspend fun fetchMorePollEventsBackward(params: LoadMorePollsTask.Params): PollHistoryStatusEntity { + val events = params.timeline.awaitPaginate( + direction = Timeline.Direction.BACKWARDS, + count = params.eventsPageSize, + ) + + val paginationState = params.timeline.getPaginationState(direction = Timeline.Direction.BACKWARDS) + + return updatePollHistoryStatus( + roomId = params.roomId, + events = events, + paginationState = paginationState, ) - filterAndStoreEventsTask.execute(filterTaskParams) } - private suspend fun updatePollHistoryStatus(roomId: String, paginationResponse: PaginationResponse): PollHistoryStatusEntity { + private suspend fun updatePollHistoryStatus( + roomId: String, + events: List, + paginationState: Timeline.PaginationState, + ): PollHistoryStatusEntity { return monarchy.awaitTransaction { realm -> val status = PollHistoryStatusEntity.getOrCreate(realm, roomId) - val tokenStartForward = status.tokenStartForward + val mostRecentEventIdReached = status.mostRecentEventIdReached - if (tokenStartForward == null) { - // save the start token for next forward call - status.tokenEndBackward = paginationResponse.start + if (mostRecentEventIdReached == null) { + // save it for next forward pagination + val mostRecentEvent = events + .maxByOrNull { it.root.originServerTs ?: Long.MIN_VALUE } + ?.root + status.mostRecentEventIdReached = mostRecentEvent?.eventId } - val oldestEventTimestamp = paginationResponse.events - .minByOrNull { it.originServerTs ?: Long.MAX_VALUE } - ?.originServerTs + val oldestEvent = events + .minByOrNull { it.root.originServerTs ?: Long.MAX_VALUE } + ?.root + val oldestEventTimestamp = oldestEvent?.originServerTs + val oldestEventId = oldestEvent?.eventId val currentTargetTimestamp = status.currentTimestampTargetBackwardMs - if (paginationResponse.end == null) { + if (paginationState.hasMoreToLoad.not()) { // start of the timeline is reached, there are no more events status.isEndOfPollsBackward = true - if(oldestEventTimestamp != null && oldestEventTimestamp > 0) { - status.oldestTimestampReachedMs = oldestEventTimestamp + + if (oldestEventTimestamp != null && oldestEventTimestamp > 0) { + status.oldestTimestampTargetReachedMs = oldestEventTimestamp } } else if (oldestEventTimestamp != null && currentTargetTimestamp != null && oldestEventTimestamp <= currentTargetTimestamp) { // target has been reached - status.oldestTimestampReachedMs = oldestEventTimestamp - status.tokenEndBackward = paginationResponse.end - } else { - status.tokenEndBackward = paginationResponse.end + status.oldestTimestampTargetReachedMs = oldestEventTimestamp + } + + if(oldestEventId != null) { + // save it for next backward pagination + status.oldestEventIdReached = oldestEventId } // return a copy of the Realm object diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt index 4b1a79f561..488660f7d3 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt @@ -23,13 +23,13 @@ import dagger.assisted.AssistedInject import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.VectorViewModel +import im.vector.app.features.roomprofile.polls.list.domain.DisposePollHistoryUseCase import im.vector.app.features.roomprofile.polls.list.domain.GetPollsUseCase import im.vector.app.features.roomprofile.polls.list.domain.LoadMorePollsUseCase import im.vector.app.features.roomprofile.polls.list.domain.SyncPollsUseCase import im.vector.app.features.roomprofile.polls.list.ui.PollSummaryMapper import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch @@ -38,6 +38,7 @@ class RoomPollsViewModel @AssistedInject constructor( private val getPollsUseCase: GetPollsUseCase, private val loadMorePollsUseCase: LoadMorePollsUseCase, private val syncPollsUseCase: SyncPollsUseCase, + private val disposePollHistoryUseCase: DisposePollHistoryUseCase, private val pollSummaryMapper: PollSummaryMapper, ) : VectorViewModel(initialState) { @@ -54,6 +55,11 @@ class RoomPollsViewModel @AssistedInject constructor( observePolls(roomId) } + override fun onCleared() { + withState { disposePollHistoryUseCase.execute(it.roomId) } + super.onCleared() + } + private fun syncPolls(roomId: String) { viewModelScope.launch { setState { copy(isSyncing = true) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollDataSource.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollDataSource.kt index 86f77afc29..60b53a90b4 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollDataSource.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollDataSource.kt @@ -38,6 +38,10 @@ class RoomPollDataSource @Inject constructor( ?: throw PollHistoryError.UnknownRoomError } + fun dispose(roomId: String) { + getPollHistoryService(roomId).dispose() + } + fun getPolls(roomId: String): Flow> { return getPollHistoryService(roomId).getPollEvents().asFlow() } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollRepository.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollRepository.kt index ff29ffbdc0..d993302fb7 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollRepository.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollRepository.kt @@ -25,6 +25,10 @@ class RoomPollRepository @Inject constructor( private val roomPollDataSource: RoomPollDataSource, ) { + fun dispose(roomId: String) { + roomPollDataSource.dispose(roomId) + } + fun getPolls(roomId: String): Flow> { return roomPollDataSource.getPolls(roomId) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/domain/DisposePollHistoryUseCase.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/domain/DisposePollHistoryUseCase.kt new file mode 100644 index 0000000000..21814b5a2b --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/domain/DisposePollHistoryUseCase.kt @@ -0,0 +1,32 @@ +/* + * 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.roomprofile.polls.list.domain + +import im.vector.app.features.roomprofile.polls.list.data.RoomPollRepository +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import javax.inject.Inject + +class DisposePollHistoryUseCase @Inject constructor( + private val roomPollRepository: RoomPollRepository, +) { + + fun execute(roomId: String) { + roomPollRepository.dispose(roomId) + } +} From 05c4de6c6c6993112b92f695a30ffa1954ccacee Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Mon, 23 Jan 2023 10:40:28 +0100 Subject: [PATCH 035/189] Adding distinctBy on event ids for polls --- .../sdk/internal/session/room/poll/DefaultPollHistoryService.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt index 0f57b81cb8..2fb63ef90e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt @@ -128,6 +128,7 @@ internal class DefaultPollHistoryService @AssistedInject constructor( return Transformations.map(eventsLiveData) { events -> events.filter { it.root.getClearType() in EventType.POLL_START.values } + .distinctBy { it.eventId } } } From 073eda75a2171cbb0d8c312d648c4a65d0083f3e Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Mon, 23 Jan 2023 11:34:50 +0100 Subject: [PATCH 036/189] Catch error during mapping from domain to UI model --- .../polls/list/ui/PollSummaryMapper.kt | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapper.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapper.kt index 0bd7cd33af..9d4128e0ac 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapper.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapper.kt @@ -32,21 +32,28 @@ class PollSummaryMapper @Inject constructor( ) { fun map(timelineEvent: TimelineEvent): PollSummary? { - val content = timelineEvent.getVectorLastMessageContent() - val pollResponseData = pollResponseDataFactory.create(timelineEvent) val eventId = timelineEvent.root.eventId.orEmpty() - val creationTimestamp = timelineEvent.root.originServerTs ?: 0 - return if (eventId.isNotEmpty() && creationTimestamp > 0 && content is MessagePollContent) { - convertToPollSummary( - eventId = eventId, - creationTimestamp = creationTimestamp, - messagePollContent = content, - pollResponseData = pollResponseData - ) - } else { - Timber.w("missing mandatory info about poll event with id=$eventId") - null + val result = runCatching { + val content = timelineEvent.getVectorLastMessageContent() + val pollResponseData = pollResponseDataFactory.create(timelineEvent) + val creationTimestamp = timelineEvent.root.originServerTs ?: 0 + return if (eventId.isNotEmpty() && creationTimestamp > 0 && content is MessagePollContent) { + convertToPollSummary( + eventId = eventId, + creationTimestamp = creationTimestamp, + messagePollContent = content, + pollResponseData = pollResponseData + ) + } else { + Timber.w("missing mandatory info about poll event with id=$eventId") + null + } } + + if (result.isFailure) { + Timber.w("failed to map event with id $eventId") + } + return result.getOrNull() } private fun convertToPollSummary( From cd1f41594dc760548a6b9521aa8b2be3d2f63da5 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Mon, 23 Jan 2023 14:43:21 +0100 Subject: [PATCH 037/189] Sync polls until now when landing on screen --- .../sdk/internal/session/room/RoomModule.kt | 5 + .../room/poll/DefaultPollHistoryService.kt | 36 ++++-- .../session/room/poll/SyncPollsTask.kt | 121 ++++++++++++++++++ 3 files changed, 149 insertions(+), 13 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/SyncPollsTask.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt index 56925d55d5..673a979633 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt @@ -93,8 +93,10 @@ import org.matrix.android.sdk.internal.session.room.peeking.PeekRoomTask import org.matrix.android.sdk.internal.session.room.peeking.ResolveRoomStateTask import org.matrix.android.sdk.internal.session.room.poll.DefaultGetLoadedPollsStatusTask import org.matrix.android.sdk.internal.session.room.poll.DefaultLoadMorePollsTask +import org.matrix.android.sdk.internal.session.room.poll.DefaultSyncPollsTask import org.matrix.android.sdk.internal.session.room.poll.GetLoadedPollsStatusTask import org.matrix.android.sdk.internal.session.room.poll.LoadMorePollsTask +import org.matrix.android.sdk.internal.session.room.poll.SyncPollsTask import org.matrix.android.sdk.internal.session.room.read.DefaultMarkAllRoomsReadTask import org.matrix.android.sdk.internal.session.room.read.DefaultSetReadMarkersTask import org.matrix.android.sdk.internal.session.room.read.MarkAllRoomsReadTask @@ -374,4 +376,7 @@ internal abstract class RoomModule { @Binds abstract fun bindFilterAndStoreEventsTask(task: DefaultFilterAndStoreEventsTask): FilterAndStoreEventsTask + + @Binds + abstract fun bindSyncPollsTask(task: DefaultSyncPollsTask): SyncPollsTask } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt index 2fb63ef90e..52ea76b168 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt @@ -23,7 +23,8 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import io.realm.kotlin.where -import kotlinx.coroutines.delay +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus import org.matrix.android.sdk.api.session.room.poll.PollHistoryService @@ -50,6 +51,7 @@ internal class DefaultPollHistoryService @AssistedInject constructor( private val clock: Clock, private val loadMorePollsTask: LoadMorePollsTask, private val getLoadedPollsStatusTask: GetLoadedPollsStatusTask, + private val syncPollsTask: SyncPollsTask, private val timelineEventMapper: TimelineEventMapper, ) : PollHistoryService { @@ -71,20 +73,23 @@ internal class DefaultPollHistoryService @AssistedInject constructor( ) timelineService.createTimeline(eventId = null, settings = settings).also { it.start() } } + private val timelineMutex = Mutex() override fun dispose() { timeline.dispose() } override suspend fun loadMore(): LoadedPollsStatus { - val params = LoadMorePollsTask.Params( - timeline = timeline, - roomId = roomId, - currentTimestampMs = clock.epochMillis(), - loadingPeriodInDays = loadingPeriodInDays, - eventsPageSize = EVENTS_PAGE_SIZE, - ) - return loadMorePollsTask.execute(params) + return timelineMutex.withLock { + val params = LoadMorePollsTask.Params( + timeline = timeline, + roomId = roomId, + currentTimestampMs = clock.epochMillis(), + loadingPeriodInDays = loadingPeriodInDays, + eventsPageSize = EVENTS_PAGE_SIZE, + ) + loadMorePollsTask.execute(params) + } } override suspend fun getLoadedPollsStatus(): LoadedPollsStatus { @@ -96,10 +101,15 @@ internal class DefaultPollHistoryService @AssistedInject constructor( } override suspend fun syncPolls() { - // TODO unmock - // TODO when sync forward, jump to most recent event Id + paginate forward + jump to oldest eventId after - // TODO avoid possibility to call sync and loadMore at the same time from the service API, how? - delay(1000) + timelineMutex.withLock { + val params = SyncPollsTask.Params( + timeline = timeline, + roomId = roomId, + currentTimestampMs = clock.epochMillis(), + eventsPageSize = EVENTS_PAGE_SIZE, + ) + syncPollsTask.execute(params) + } } override fun getPollEvents(): LiveData> { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/SyncPollsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/SyncPollsTask.kt new file mode 100644 index 0000000000..e968095408 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/SyncPollsTask.kt @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2023 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.poll + +import com.zhuinden.monarchy.Monarchy +import kotlinx.coroutines.delay +import org.matrix.android.sdk.api.session.events.model.isPoll +import org.matrix.android.sdk.api.session.events.model.isPollResponse +import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus +import org.matrix.android.sdk.api.session.room.timeline.Timeline +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import org.matrix.android.sdk.internal.database.model.PollHistoryStatusEntity +import org.matrix.android.sdk.internal.database.query.getOrCreate +import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver +import org.matrix.android.sdk.internal.network.executeRequest +import org.matrix.android.sdk.internal.session.room.RoomAPI +import org.matrix.android.sdk.internal.session.room.event.FilterAndStoreEventsTask +import org.matrix.android.sdk.internal.session.room.poll.PollConstants.MILLISECONDS_PER_DAY +import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection +import org.matrix.android.sdk.internal.session.room.timeline.PaginationResponse +import org.matrix.android.sdk.internal.task.Task +import org.matrix.android.sdk.internal.util.awaitTransaction +import javax.inject.Inject + +internal interface SyncPollsTask : Task { + data class Params( + val timeline: Timeline, + val roomId: String, + val currentTimestampMs: Long, + val eventsPageSize: Int, + ) +} + +internal class DefaultSyncPollsTask @Inject constructor( + @SessionDatabase private val monarchy: Monarchy, +) : SyncPollsTask { + + override suspend fun execute(params: SyncPollsTask.Params) { + val currentPollHistoryStatus = getCurrentPollHistoryStatus(params.roomId) + + params.timeline.restartWithEventId(currentPollHistoryStatus.mostRecentEventIdReached) + + var loadStatus = LoadStatus(shouldLoadMore = true) + while (loadStatus.shouldLoadMore){ + loadStatus = fetchMorePollEventsForward(params) + } + + params.timeline.restartWithEventId(currentPollHistoryStatus.oldestEventIdReached) + } + + private suspend fun getCurrentPollHistoryStatus(roomId: String): PollHistoryStatusEntity { + return monarchy.awaitTransaction { realm -> + PollHistoryStatusEntity + .getOrCreate(realm, roomId) + .copy() + } + } + + private suspend fun fetchMorePollEventsForward(params: SyncPollsTask.Params): LoadStatus { + val events = params.timeline.awaitPaginate( + direction = Timeline.Direction.FORWARDS, + count = params.eventsPageSize, + ) + + val paginationState = params.timeline.getPaginationState(direction = Timeline.Direction.FORWARDS) + + return updatePollHistoryStatus( + roomId = params.roomId, + currentTimestampMs = params.currentTimestampMs, + events = events, + paginationState = paginationState, + ) + } + + private suspend fun updatePollHistoryStatus( + roomId: String, + currentTimestampMs: Long, + events: List, + paginationState: Timeline.PaginationState, + ): LoadStatus { + return monarchy.awaitTransaction { realm -> + val status = PollHistoryStatusEntity.getOrCreate(realm, roomId) + val mostRecentEventIdReached = status.mostRecentEventIdReached + + val mostRecentEvent = events + .maxByOrNull { it.root.originServerTs ?: Long.MIN_VALUE } + ?.root + + if (mostRecentEventIdReached == null) { + // save it for next forward pagination + status.mostRecentEventIdReached = mostRecentEvent?.eventId + } + + val mostRecentTimestamp = mostRecentEvent?.ageLocalTs + + val shouldLoadMore = paginationState.hasMoreToLoad && + (mostRecentTimestamp == null || mostRecentTimestamp < currentTimestampMs) + + LoadStatus(shouldLoadMore = shouldLoadMore) + } + } + + private class LoadStatus( + val shouldLoadMore: Boolean, + ) +} From 63026a3da5a3db8858fc412be5395605c0dfe863 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Mon, 23 Jan 2023 15:07:18 +0100 Subject: [PATCH 038/189] Using copy() on realm object when getting current poll history status --- .../internal/session/room/poll/GetLoadedPollsStatusTask.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/GetLoadedPollsStatusTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/GetLoadedPollsStatusTask.kt index 98b1e5931a..f273d2248a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/GetLoadedPollsStatusTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/GetLoadedPollsStatusTask.kt @@ -38,7 +38,9 @@ internal class DefaultGetLoadedPollsStatusTask @Inject constructor( override suspend fun execute(params: GetLoadedPollsStatusTask.Params): LoadedPollsStatus { return monarchy.awaitTransaction { realm -> - val status = PollHistoryStatusEntity.getOrCreate(realm, params.roomId) + val status = PollHistoryStatusEntity + .getOrCreate(realm, params.roomId) + .copy() LoadedPollsStatus( canLoadMore = status.isEndOfPollsBackward.not(), nbSyncedDays = status.getNbSyncedDays(params.currentTimestampMs), From 2f060952737dddc07dd7ec0b52cd1ac03a99a775 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Mon, 23 Jan 2023 15:16:09 +0100 Subject: [PATCH 039/189] Remove TODO --- .../sdk/internal/session/room/poll/DefaultPollHistoryService.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt index 52ea76b168..3ef0e7918a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt @@ -64,7 +64,6 @@ internal class DefaultPollHistoryService @AssistedInject constructor( get() = LOADING_PERIOD_IN_DAYS private val timeline by lazy { - // TODO check if we need to add a way to avoid using the current filter in rooms val settings = TimelineSettings( initialSize = EVENTS_PAGE_SIZE, buildReadReceipts = false, From 2c2349aa639619d67477b7b649a8b77336d1ae4e Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Mon, 23 Jan 2023 15:35:33 +0100 Subject: [PATCH 040/189] Remove callback when RoomProfileFragment is destroyed --- .../im/vector/app/features/roomprofile/RoomProfileFragment.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt index 51885dbf39..91f57d33e9 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt @@ -207,6 +207,7 @@ class RoomProfileFragment : } override fun onDestroyView() { + roomProfileController.callback = null views.matrixProfileAppBarLayout.removeOnOffsetChangedListener(appBarStateChangeListener) views.matrixProfileRecyclerView.cleanup() appBarStateChangeListener = null From db2e2916a5b671c2f39c579787028ca83d9d4c6a Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Mon, 23 Jan 2023 15:37:18 +0100 Subject: [PATCH 041/189] Remove some TODOs --- .../sdk/internal/session/room/poll/LoadMorePollsTask.kt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/LoadMorePollsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/LoadMorePollsTask.kt index f22447f19c..939769b525 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/LoadMorePollsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/LoadMorePollsTask.kt @@ -50,11 +50,6 @@ internal class DefaultLoadMorePollsTask @Inject constructor( while (shouldFetchMoreEventsBackward(currentPollHistoryStatus)) { currentPollHistoryStatus = fetchMorePollEventsBackward(params) } - // TODO - // check how it behaves when cancelling the process: it should resume where it was stopped - // check the network calls done using Flipper - // check forward of error in case of call api failure - // test on large room return LoadedPollsStatus( canLoadMore = currentPollHistoryStatus.isEndOfPollsBackward.not(), From 470218ca528938fe97ddc11152b2c19cfeadb79e Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Tue, 24 Jan 2023 15:37:53 +0100 Subject: [PATCH 042/189] Updating existing unit tests --- .../room/event/FilterAndStoreEventsTask.kt | 1 + .../polls/list/data/RoomPollRepository.kt | 1 + .../list/domain/DisposePollHistoryUseCase.kt | 1 + .../factory/PollItemViewStateFactoryTest.kt | 266 +++++++++++------- .../polls/RoomPollsViewModelTest.kt | 70 +++-- .../polls/list/data/RoomPollRepositoryTest.kt | 15 +- .../domain/GetLoadedPollsStatusUseCaseTest.kt | 12 +- .../polls/list/domain/GetPollsUseCaseTest.kt | 19 +- .../list/domain/LoadMorePollsUseCaseTest.kt | 1 + .../polls/list/domain/SyncPollsUseCaseTest.kt | 57 +++- 10 files changed, 300 insertions(+), 143 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/event/FilterAndStoreEventsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/event/FilterAndStoreEventsTask.kt index e6e169b9b4..eb7364abd3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/event/FilterAndStoreEventsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/event/FilterAndStoreEventsTask.kt @@ -58,6 +58,7 @@ internal class DefaultFilterAndStoreEventsTask @Inject constructor( private suspend fun addMissingEventsInDB(roomId: String, events: List) { monarchy.awaitTransaction { realm -> + // TODO we should insert TimelineEventEntity as well, how to do that???? val eventIdsToCheck = events.mapNotNull { it.eventId }.filter { it.isNotEmpty() } if (eventIdsToCheck.isNotEmpty()) { val existingIds = EventEntity.where(realm, eventIdsToCheck).findAll().toList().map { it.eventId } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollRepository.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollRepository.kt index d993302fb7..d6dfe12435 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollRepository.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollRepository.kt @@ -25,6 +25,7 @@ class RoomPollRepository @Inject constructor( private val roomPollDataSource: RoomPollDataSource, ) { + // TODO add unit tests fun dispose(roomId: String) { roomPollDataSource.dispose(roomId) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/domain/DisposePollHistoryUseCase.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/domain/DisposePollHistoryUseCase.kt index 21814b5a2b..4ed4e71501 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/domain/DisposePollHistoryUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/domain/DisposePollHistoryUseCase.kt @@ -26,6 +26,7 @@ class DisposePollHistoryUseCase @Inject constructor( private val roomPollRepository: RoomPollRepository, ) { + // TODO add unit tests fun execute(roomId: String) { roomPollRepository.dispose(roomId) } diff --git a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactoryTest.kt b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactoryTest.kt index 8ee55d5b6e..067cb264e1 100644 --- a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactoryTest.kt +++ b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactoryTest.kt @@ -25,6 +25,9 @@ import im.vector.app.features.home.room.detail.timeline.item.ReactionsSummaryDat import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout import im.vector.app.features.poll.PollViewState import im.vector.app.test.fakes.FakeStringProvider +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify import org.amshove.kluent.shouldBeEqualTo import org.junit.Test import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent @@ -56,88 +59,69 @@ private val A_POLL_CONTENT = MessagePollContent( unstablePollCreationInfo = PollCreationInfo( question = PollQuestion( unstableQuestion = "What is your favourite coffee?" + ), kind = PollType.UNDISCLOSED_UNSTABLE, maxSelections = 1, answers = listOf( + PollAnswer( + id = A_POLL_OPTION_IDS[0], unstableAnswer = "Double Espresso" ), - kind = PollType.UNDISCLOSED_UNSTABLE, - maxSelections = 1, - answers = listOf( - PollAnswer( - id = A_POLL_OPTION_IDS[0], - unstableAnswer = "Double Espresso" - ), - PollAnswer( - id = A_POLL_OPTION_IDS[1], - unstableAnswer = "Macchiato" - ), - PollAnswer( - id = A_POLL_OPTION_IDS[2], - unstableAnswer = "Iced Coffee" - ), - ) + PollAnswer( + id = A_POLL_OPTION_IDS[1], unstableAnswer = "Macchiato" + ), + PollAnswer( + id = A_POLL_OPTION_IDS[2], unstableAnswer = "Iced Coffee" + ), + ) ) ) class PollItemViewStateFactoryTest { + private val fakeStringProvider = FakeStringProvider() + private val fakePollOptionViewStateFactory = mockk() + + private val pollItemViewStateFactory = PollItemViewStateFactory( + stringProvider = fakeStringProvider.instance, + pollOptionViewStateFactory = fakePollOptionViewStateFactory, + ) + @Test fun `given a sending poll state then poll is not votable and option states are PollSending`() { - val stringProvider = FakeStringProvider() - val pollItemViewStateFactory = PollItemViewStateFactory(stringProvider.instance) - + // Given val sendingPollInformationData = A_MESSAGE_INFORMATION_DATA.copy(sendState = SendState.SENDING) + val optionViewStates = listOf(PollOptionViewState.PollSending(optionId = "", optionAnswer = "")) + every { fakePollOptionViewStateFactory.createPollSendingOptions(A_POLL_CONTENT.getBestPollCreationInfo()) } returns optionViewStates + + // When val pollViewState = pollItemViewStateFactory.create( pollContent = A_POLL_CONTENT, informationData = sendingPollInformationData, ) + // Then pollViewState shouldBeEqualTo PollViewState( question = A_POLL_CONTENT.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "", - votesStatus = stringProvider.instance.getString(R.string.poll_no_votes_cast), + votesStatus = fakeStringProvider.instance.getString(R.string.poll_no_votes_cast), canVote = false, - optionViewStates = A_POLL_CONTENT.getBestPollCreationInfo()?.answers?.map { answer -> - PollOptionViewState.PollSending( - optionId = answer.id ?: "", - optionAnswer = answer.getBestAnswer() ?: "" - ) - }, + optionViewStates = optionViewStates, ) + verify { fakePollOptionViewStateFactory.createPollSendingOptions(A_POLL_CONTENT.getBestPollCreationInfo()) } } @Test fun `given a sent poll state when poll is closed then poll is not votable and option states are Ended`() { - val stringProvider = FakeStringProvider() - val pollItemViewStateFactory = PollItemViewStateFactory(stringProvider.instance) - + // Given val closedPollSummary = A_POLL_RESPONSE_DATA.copy(isClosed = true) val closedPollInformationData = A_MESSAGE_INFORMATION_DATA.copy(pollResponseAggregatedSummary = closedPollSummary) - - val pollViewState = pollItemViewStateFactory.create( - pollContent = A_POLL_CONTENT, - informationData = closedPollInformationData, + val optionViewStates = listOf( + PollOptionViewState.PollEnded( + optionId = "", optionAnswer = "", voteCount = 0, votePercentage = 0.0, isWinner = false + ) ) - - pollViewState shouldBeEqualTo PollViewState( - question = A_POLL_CONTENT.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "", - votesStatus = stringProvider.instance.getQuantityString(R.plurals.poll_total_vote_count_after_ended, 0, 0), - canVote = false, - optionViewStates = A_POLL_CONTENT.getBestPollCreationInfo()?.answers?.map { answer -> - PollOptionViewState.PollEnded( - optionId = answer.id ?: "", - optionAnswer = answer.getBestAnswer() ?: "", - voteCount = 0, - votePercentage = 0.0, - isWinner = false - ) - }, - ) - } - - @Test - fun `given a sent poll state with some decryption error when poll is closed then warning message is displayed`() { - // Given - val stringProvider = FakeStringProvider() - val pollItemViewStateFactory = PollItemViewStateFactory(stringProvider.instance) - val closedPollSummary = A_POLL_RESPONSE_DATA.copy(isClosed = true, hasEncryptedRelatedEvents = true) - val closedPollInformationData = A_MESSAGE_INFORMATION_DATA.copy(pollResponseAggregatedSummary = closedPollSummary) + every { + fakePollOptionViewStateFactory.createPollEndedOptions( + A_POLL_CONTENT.getBestPollCreationInfo(), + closedPollInformationData.pollResponseAggregatedSummary, + ) + } returns optionViewStates // When val pollViewState = pollItemViewStateFactory.create( @@ -146,42 +130,90 @@ class PollItemViewStateFactoryTest { ) // Then - pollViewState.votesStatus shouldBeEqualTo stringProvider.instance.getString(R.string.unable_to_decrypt_some_events_in_poll) + pollViewState shouldBeEqualTo PollViewState( + question = A_POLL_CONTENT.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "", + votesStatus = fakeStringProvider.instance.getQuantityString(R.plurals.poll_total_vote_count_after_ended, 0, 0), + canVote = false, + optionViewStates = optionViewStates, + ) + verify { + fakePollOptionViewStateFactory.createPollEndedOptions( + A_POLL_CONTENT.getBestPollCreationInfo(), + closedPollInformationData.pollResponseAggregatedSummary, + ) + } + } + + @Test + fun `given a sent poll state with some decryption error when poll is closed then warning message is displayed`() { + // Given + val closedPollSummary = A_POLL_RESPONSE_DATA.copy(isClosed = true, hasEncryptedRelatedEvents = true) + val closedPollInformationData = A_MESSAGE_INFORMATION_DATA.copy(pollResponseAggregatedSummary = closedPollSummary) + val optionViewStates = listOf( + PollOptionViewState.PollEnded( + optionId = "", optionAnswer = "", voteCount = 0, votePercentage = 0.0, isWinner = false + ) + ) + every { + fakePollOptionViewStateFactory.createPollEndedOptions( + A_POLL_CONTENT.getBestPollCreationInfo(), + closedPollInformationData.pollResponseAggregatedSummary, + ) + } returns optionViewStates + + // When + val pollViewState = pollItemViewStateFactory.create( + pollContent = A_POLL_CONTENT, + informationData = closedPollInformationData, + ) + + // Then + pollViewState.votesStatus shouldBeEqualTo fakeStringProvider.instance.getString(R.string.unable_to_decrypt_some_events_in_poll) } @Test fun `given a sent poll when undisclosed poll type is selected then poll is votable and option states are PollUndisclosed`() { - val stringProvider = FakeStringProvider() - val pollItemViewStateFactory = PollItemViewStateFactory(stringProvider.instance) + // Given + val optionViewStates = listOf( + PollOptionViewState.PollUndisclosed( + optionId = "", + optionAnswer = "", + isSelected = false, + ) + ) + every { + fakePollOptionViewStateFactory.createPollUndisclosedOptions( + A_POLL_CONTENT.getBestPollCreationInfo(), + A_MESSAGE_INFORMATION_DATA.pollResponseAggregatedSummary, + ) + } returns optionViewStates + // When val pollViewState = pollItemViewStateFactory.create( pollContent = A_POLL_CONTENT, informationData = A_MESSAGE_INFORMATION_DATA, ) + // Then pollViewState shouldBeEqualTo PollViewState( question = A_POLL_CONTENT.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "", - votesStatus = stringProvider.instance.getString(R.string.poll_undisclosed_not_ended), + votesStatus = fakeStringProvider.instance.getString(R.string.poll_undisclosed_not_ended), canVote = true, - optionViewStates = A_POLL_CONTENT.getBestPollCreationInfo()?.answers?.map { answer -> - PollOptionViewState.PollUndisclosed( - optionId = answer.id ?: "", - optionAnswer = answer.getBestAnswer() ?: "", - isSelected = false - ) - }, + optionViewStates = optionViewStates, ) + verify { + fakePollOptionViewStateFactory.createPollUndisclosedOptions( + A_POLL_CONTENT.getBestPollCreationInfo(), + A_MESSAGE_INFORMATION_DATA.pollResponseAggregatedSummary, + ) + } } @Test fun `given a sent poll when my vote exists then poll is still votable and options states are PollVoted`() { - val stringProvider = FakeStringProvider() - val pollItemViewStateFactory = PollItemViewStateFactory(stringProvider.instance) - + // Given val votedPollData = A_POLL_RESPONSE_DATA.copy( - totalVotes = 1, - myVote = A_POLL_OPTION_IDS[0], - votes = mapOf(A_POLL_OPTION_IDS[0] to PollVoteSummaryData(total = 1, percentage = 1.0)) + totalVotes = 1, myVote = A_POLL_OPTION_IDS[0], votes = mapOf(A_POLL_OPTION_IDS[0] to PollVoteSummaryData(total = 1, percentage = 1.0)) ) val disclosedPollContent = A_POLL_CONTENT.copy( unstablePollCreationInfo = A_POLL_CONTENT.getBestPollCreationInfo()?.copy( @@ -189,33 +221,46 @@ class PollItemViewStateFactoryTest { ), ) val votedInformationData = A_MESSAGE_INFORMATION_DATA.copy(pollResponseAggregatedSummary = votedPollData) + val optionViewStates = listOf( + PollOptionViewState.PollVoted( + optionId = "", + optionAnswer = "", + voteCount = 0, + votePercentage = 0.0, + isSelected = false, + ) + ) + every { + fakePollOptionViewStateFactory.createPollVotedOptions( + disclosedPollContent.getBestPollCreationInfo(), + votedInformationData.pollResponseAggregatedSummary, + ) + } returns optionViewStates + // When val pollViewState = pollItemViewStateFactory.create( pollContent = disclosedPollContent, informationData = votedInformationData, ) + // Then pollViewState shouldBeEqualTo PollViewState( question = A_POLL_CONTENT.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "", - votesStatus = stringProvider.instance.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_voted, 1, 1), + votesStatus = fakeStringProvider.instance.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_voted, 1, 1), canVote = true, - optionViewStates = A_POLL_CONTENT.getBestPollCreationInfo()?.answers?.mapIndexed { index, answer -> - PollOptionViewState.PollVoted( - optionId = answer.id ?: "", - optionAnswer = answer.getBestAnswer() ?: "", - voteCount = if (index == 0) 1 else 0, - votePercentage = if (index == 0) 1.0 else 0.0, - isSelected = index == 0 - ) - }, + optionViewStates = optionViewStates, ) + verify { + fakePollOptionViewStateFactory.createPollVotedOptions( + disclosedPollContent.getBestPollCreationInfo(), + votedInformationData.pollResponseAggregatedSummary, + ) + } } @Test fun `given a sent poll with decryption failure when my vote exists then a warning message is displayed`() { // Given - val stringProvider = FakeStringProvider() - val pollItemViewStateFactory = PollItemViewStateFactory(stringProvider.instance) val votedPollData = A_POLL_RESPONSE_DATA.copy( totalVotes = 1, myVote = A_POLL_OPTION_IDS[0], @@ -228,6 +273,21 @@ class PollItemViewStateFactoryTest { ), ) val votedInformationData = A_MESSAGE_INFORMATION_DATA.copy(pollResponseAggregatedSummary = votedPollData) + val optionViewStates = listOf( + PollOptionViewState.PollVoted( + optionId = "", + optionAnswer = "", + voteCount = 0, + votePercentage = 0.0, + isSelected = false, + ) + ) + every { + fakePollOptionViewStateFactory.createPollVotedOptions( + disclosedPollContent.getBestPollCreationInfo(), + votedInformationData.pollResponseAggregatedSummary, + ) + } returns optionViewStates // When val pollViewState = pollItemViewStateFactory.create( @@ -236,34 +296,46 @@ class PollItemViewStateFactoryTest { ) // Then - pollViewState.votesStatus shouldBeEqualTo stringProvider.instance.getString(R.string.unable_to_decrypt_some_events_in_poll) + pollViewState.votesStatus shouldBeEqualTo fakeStringProvider.instance.getString(R.string.unable_to_decrypt_some_events_in_poll) } @Test fun `given a sent poll when poll type is disclosed then poll is votable and option view states are PollReady`() { - val stringProvider = FakeStringProvider() - val pollItemViewStateFactory = PollItemViewStateFactory(stringProvider.instance) - + // Given val disclosedPollContent = A_POLL_CONTENT.copy( unstablePollCreationInfo = A_POLL_CONTENT.getBestPollCreationInfo()?.copy( kind = PollType.DISCLOSED_UNSTABLE ) ) + val optionViewStates = listOf( + PollOptionViewState.PollReady( + optionId = "", + optionAnswer = "", + ) + ) + every { + fakePollOptionViewStateFactory.createPollReadyOptions( + disclosedPollContent.getBestPollCreationInfo(), + ) + } returns optionViewStates + + // When val pollViewState = pollItemViewStateFactory.create( pollContent = disclosedPollContent, informationData = A_MESSAGE_INFORMATION_DATA, ) + // Then pollViewState shouldBeEqualTo PollViewState( question = A_POLL_CONTENT.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "", - votesStatus = stringProvider.instance.getString(R.string.poll_no_votes_cast), + votesStatus = fakeStringProvider.instance.getString(R.string.poll_no_votes_cast), canVote = true, - optionViewStates = A_POLL_CONTENT.getBestPollCreationInfo()?.answers?.map { answer -> - PollOptionViewState.PollReady( - optionId = answer.id ?: "", - optionAnswer = answer.getBestAnswer() ?: "" - ) - }, + optionViewStates = optionViewStates, ) + verify { + fakePollOptionViewStateFactory.createPollReadyOptions( + disclosedPollContent.getBestPollCreationInfo(), + ) + } } } diff --git a/vector/src/test/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModelTest.kt b/vector/src/test/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModelTest.kt index adbf32006e..084df03e64 100644 --- a/vector/src/test/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModelTest.kt @@ -17,23 +17,26 @@ package im.vector.app.features.roomprofile.polls import com.airbnb.mvrx.test.MavericksTestRule -import im.vector.app.features.roomprofile.polls.list.domain.GetLoadedPollsStatusUseCase +import im.vector.app.features.roomprofile.polls.list.domain.DisposePollHistoryUseCase import im.vector.app.features.roomprofile.polls.list.domain.GetPollsUseCase import im.vector.app.features.roomprofile.polls.list.domain.LoadMorePollsUseCase import im.vector.app.features.roomprofile.polls.list.domain.SyncPollsUseCase import im.vector.app.features.roomprofile.polls.list.ui.PollSummary +import im.vector.app.features.roomprofile.polls.list.ui.PollSummaryMapper import im.vector.app.test.test import im.vector.app.test.testDispatcher import io.mockk.coEvery -import io.mockk.coJustRun import io.mockk.coVerify import io.mockk.every +import io.mockk.justRun import io.mockk.mockk import io.mockk.verify +import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.flowOf import org.junit.Rule import org.junit.Test import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent private const val A_ROOM_ID = "room-id" @@ -42,31 +45,35 @@ class RoomPollsViewModelTest { @get:Rule val mavericksTestRule = MavericksTestRule(testDispatcher = testDispatcher) + private val initialState = RoomPollsViewState(A_ROOM_ID) private val fakeGetPollsUseCase = mockk() - private val fakeGetLoadedPollsStatusUseCase = mockk() private val fakeLoadMorePollsUseCase = mockk() private val fakeSyncPollsUseCase = mockk() - private val initialState = RoomPollsViewState(A_ROOM_ID) + private val fakeDisposePollHistoryUseCase = mockk() + private val fakePollSummaryMapper = mockk() private fun createViewModel(): RoomPollsViewModel { return RoomPollsViewModel( initialState = initialState, getPollsUseCase = fakeGetPollsUseCase, - getLoadedPollsStatusUseCase = fakeGetLoadedPollsStatusUseCase, loadMorePollsUseCase = fakeLoadMorePollsUseCase, syncPollsUseCase = fakeSyncPollsUseCase, + disposePollHistoryUseCase = fakeDisposePollHistoryUseCase, + pollSummaryMapper = fakePollSummaryMapper, ) } @Test fun `given viewModel when created then polls list is observed, sync is launched and viewState is updated`() { // Given - val loadedPollsStatus = givenGetLoadedPollsStatusSuccess() - givenSyncPollsWithSuccess() - val polls = listOf(givenAPollSummary()) + val loadedPollsStatus = givenSyncPollsWithSuccess() + val aPollEvent = givenAPollEvent() + val aPollSummary = givenAPollSummary() + val polls = listOf(aPollEvent) every { fakeGetPollsUseCase.execute(A_ROOM_ID) } returns flowOf(polls) + every { fakePollSummaryMapper.map(aPollEvent) } returns aPollSummary val expectedViewState = initialState.copy( - polls = polls, + polls = listOf(aPollSummary), canLoadMore = loadedPollsStatus.canLoadMore, nbSyncedDays = loadedPollsStatus.nbSyncedDays, ) @@ -81,6 +88,7 @@ class RoomPollsViewModelTest { .finish() verify { fakeGetPollsUseCase.execute(A_ROOM_ID) + fakePollSummaryMapper.map(aPollEvent) } coVerify { fakeSyncPollsUseCase.execute(A_ROOM_ID) } } @@ -88,10 +96,8 @@ class RoomPollsViewModelTest { @Test fun `given viewModel and error during sync process when created then error is raised in view event`() { // Given - givenGetLoadedPollsStatusSuccess() givenSyncPollsWithError(Exception()) - val polls = listOf(givenAPollSummary()) - every { fakeGetPollsUseCase.execute(A_ROOM_ID) } returns flowOf(polls) + every { fakeGetPollsUseCase.execute(A_ROOM_ID) } returns emptyFlow() // When val viewModel = createViewModel() @@ -104,17 +110,30 @@ class RoomPollsViewModelTest { coVerify { fakeSyncPollsUseCase.execute(A_ROOM_ID) } } + @Test + fun `given viewModel when calling onCleared then poll history is disposed`() { + // Given + givenSyncPollsWithSuccess() + every { fakeGetPollsUseCase.execute(A_ROOM_ID) } returns emptyFlow() + justRun { fakeDisposePollHistoryUseCase.execute(A_ROOM_ID) } + val viewModel = createViewModel() + + // When + viewModel.onCleared() + + // Then + verify { fakeDisposePollHistoryUseCase.execute(A_ROOM_ID) } + } + @Test fun `given viewModel when handle load more action then viewState is updated`() { // Given - val loadedPollsStatus = givenGetLoadedPollsStatusSuccess() - givenSyncPollsWithSuccess() - val polls = listOf(givenAPollSummary()) - every { fakeGetPollsUseCase.execute(A_ROOM_ID) } returns flowOf(polls) + val loadedPollsStatus = givenSyncPollsWithSuccess() + every { fakeGetPollsUseCase.execute(A_ROOM_ID) } returns emptyFlow() val newLoadedPollsStatus = givenLoadMoreWithSuccess() val viewModel = createViewModel() val stateAfterInit = initialState.copy( - polls = polls, + polls = emptyList(), canLoadMore = loadedPollsStatus.canLoadMore, nbSyncedDays = loadedPollsStatus.nbSyncedDays, ) @@ -139,8 +158,14 @@ class RoomPollsViewModelTest { return mockk() } - private fun givenSyncPollsWithSuccess() { - coJustRun { fakeSyncPollsUseCase.execute(A_ROOM_ID) } + private fun givenAPollEvent(): TimelineEvent { + return mockk() + } + + private fun givenSyncPollsWithSuccess(): LoadedPollsStatus { + val loadedPollsStatus = givenALoadedPollsStatus() + coEvery { fakeSyncPollsUseCase.execute(A_ROOM_ID) } returns loadedPollsStatus + return loadedPollsStatus } private fun givenSyncPollsWithError(error: Exception) { @@ -153,15 +178,10 @@ class RoomPollsViewModelTest { return loadedPollsStatus } - private fun givenGetLoadedPollsStatusSuccess(): LoadedPollsStatus { - val loadedPollsStatus = givenALoadedPollsStatus() - coEvery { fakeGetLoadedPollsStatusUseCase.execute(A_ROOM_ID) } returns loadedPollsStatus - return loadedPollsStatus - } - private fun givenALoadedPollsStatus(canLoadMore: Boolean = true, nbSyncedDays: Int = 10) = LoadedPollsStatus( canLoadMore = canLoadMore, nbSyncedDays = nbSyncedDays, + hasCompletedASyncBackward = false, ) } diff --git a/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollRepositoryTest.kt b/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollRepositoryTest.kt index 49d9623c04..3883f0dafd 100644 --- a/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollRepositoryTest.kt +++ b/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollRepositoryTest.kt @@ -16,7 +16,7 @@ package im.vector.app.features.roomprofile.polls.list.data -import im.vector.app.features.roomprofile.polls.list.ui.PollSummary +import io.mockk.coEvery import io.mockk.coJustRun import io.mockk.coVerify import io.mockk.every @@ -27,6 +27,8 @@ import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest import org.amshove.kluent.shouldBeEqualTo import org.junit.Test +import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent private const val A_ROOM_ID = "room-id" @@ -41,7 +43,7 @@ class RoomPollRepositoryTest { @Test fun `given data source when getting polls then correct method of data source is called`() = runTest { // Given - val expectedPolls = listOf() + val expectedPolls = listOf() every { fakeRoomPollDataSource.getPolls(A_ROOM_ID) } returns flowOf(expectedPolls) // When @@ -53,20 +55,21 @@ class RoomPollRepositoryTest { } @Test - fun `given data source when getting loaded polls status then correct method of data source is called`() { + fun `given data source when getting loaded polls status then correct method of data source is called`() = runTest { // Given val expectedStatus = LoadedPollsStatus( canLoadMore = true, - nbLoadedDays = 10, + nbSyncedDays = 10, + hasCompletedASyncBackward = false, ) - every { fakeRoomPollDataSource.getLoadedPollsStatus(A_ROOM_ID) } returns expectedStatus + coEvery { fakeRoomPollDataSource.getLoadedPollsStatus(A_ROOM_ID) } returns expectedStatus // When val result = roomPollRepository.getLoadedPollsStatus(A_ROOM_ID) // Then result shouldBeEqualTo expectedStatus - verify { fakeRoomPollDataSource.getLoadedPollsStatus(A_ROOM_ID) } + coVerify { fakeRoomPollDataSource.getLoadedPollsStatus(A_ROOM_ID) } } @Test diff --git a/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/domain/GetLoadedPollsStatusUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/domain/GetLoadedPollsStatusUseCaseTest.kt index 12c23797f0..eeaf7803a6 100644 --- a/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/domain/GetLoadedPollsStatusUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/domain/GetLoadedPollsStatusUseCaseTest.kt @@ -17,9 +17,10 @@ package im.vector.app.features.roomprofile.polls.list.domain import im.vector.app.features.roomprofile.polls.list.data.RoomPollRepository -import io.mockk.every +import io.mockk.coEvery +import io.mockk.coVerify import io.mockk.mockk -import io.mockk.verify +import kotlinx.coroutines.test.runTest import org.amshove.kluent.shouldBeEqualTo import org.junit.Test import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus @@ -33,20 +34,21 @@ class GetLoadedPollsStatusUseCaseTest { ) @Test - fun `given repo when execute then correct method of repo is called`() { + fun `given repo when execute then correct method of repo is called`() = runTest { // Given val aRoomId = "roomId" val expectedStatus = LoadedPollsStatus( canLoadMore = true, nbSyncedDays = 10, + hasCompletedASyncBackward = true, ) - every { fakeRoomPollRepository.getLoadedPollsStatus(aRoomId) } returns expectedStatus + coEvery { fakeRoomPollRepository.getLoadedPollsStatus(aRoomId) } returns expectedStatus // When val status = getLoadedPollsStatusUseCase.execute(aRoomId) // Then status shouldBeEqualTo expectedStatus - verify { fakeRoomPollRepository.getLoadedPollsStatus(aRoomId) } + coVerify { fakeRoomPollRepository.getLoadedPollsStatus(aRoomId) } } } diff --git a/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/domain/GetPollsUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/domain/GetPollsUseCaseTest.kt index e69b9287f8..f29a4844d7 100644 --- a/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/domain/GetPollsUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/domain/GetPollsUseCaseTest.kt @@ -17,8 +17,6 @@ package im.vector.app.features.roomprofile.polls.list.domain import im.vector.app.features.roomprofile.polls.list.data.RoomPollRepository -import im.vector.app.features.roomprofile.polls.list.ui.PollSummary -import im.vector.app.test.fixtures.RoomPollFixture import io.mockk.every import io.mockk.mockk import io.mockk.verify @@ -27,6 +25,7 @@ import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest import org.amshove.kluent.shouldBeEqualTo import org.junit.Test +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent class GetPollsUseCaseTest { private val fakeRoomPollRepository = mockk() @@ -39,16 +38,16 @@ class GetPollsUseCaseTest { fun `given repo when execute then correct method of repo is called and polls are sorted most recent first`() = runTest { // Given val aRoomId = "roomId" - val poll1 = RoomPollFixture.anActivePollSummary(timestamp = 1) - val poll2 = RoomPollFixture.anActivePollSummary(timestamp = 2) - val poll3 = RoomPollFixture.anActivePollSummary(timestamp = 3) - val polls = listOf( + val poll1 = givenTimelineEvent(timestamp = 1) + val poll2 = givenTimelineEvent(timestamp = 2) + val poll3 = givenTimelineEvent(timestamp = 3) + val polls = listOf( poll1, poll2, poll3, ) every { fakeRoomPollRepository.getPolls(aRoomId) } returns flowOf(polls) - val expectedPolls = listOf( + val expectedPolls = listOf( poll3, poll2, poll1, @@ -60,4 +59,10 @@ class GetPollsUseCaseTest { result shouldBeEqualTo expectedPolls verify { fakeRoomPollRepository.getPolls(aRoomId) } } + + private fun givenTimelineEvent(timestamp: Long): TimelineEvent { + return mockk().also { + every { it.root.originServerTs } returns timestamp + } + } } diff --git a/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/domain/LoadMorePollsUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/domain/LoadMorePollsUseCaseTest.kt index 4c769de222..d2f63c2a37 100644 --- a/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/domain/LoadMorePollsUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/domain/LoadMorePollsUseCaseTest.kt @@ -40,6 +40,7 @@ class LoadMorePollsUseCaseTest { val loadedPollsStatus = LoadedPollsStatus( canLoadMore = true, nbSyncedDays = 10, + hasCompletedASyncBackward = true, ) coEvery { fakeRoomPollRepository.loadMorePolls(aRoomId) } returns loadedPollsStatus diff --git a/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/domain/SyncPollsUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/domain/SyncPollsUseCaseTest.kt index 040514e301..e60214dde7 100644 --- a/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/domain/SyncPollsUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/domain/SyncPollsUseCaseTest.kt @@ -17,30 +17,81 @@ package im.vector.app.features.roomprofile.polls.list.domain import im.vector.app.features.roomprofile.polls.list.data.RoomPollRepository +import io.mockk.coEvery import io.mockk.coJustRun import io.mockk.coVerify +import io.mockk.coVerifyOrder import io.mockk.mockk import kotlinx.coroutines.test.runTest +import org.amshove.kluent.shouldBeEqualTo import org.junit.Test +import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus class SyncPollsUseCaseTest { private val fakeRoomPollRepository = mockk() + private val fakeGetLoadedPollsStatusUseCase = mockk() + private val fakeLoadMorePollsUseCase = mockk() private val syncPollsUseCase = SyncPollsUseCase( roomPollRepository = fakeRoomPollRepository, + getLoadedPollsStatusUseCase = fakeGetLoadedPollsStatusUseCase, + loadMorePollsUseCase = fakeLoadMorePollsUseCase, ) @Test - fun `given repo when execute then correct method of repo is called`() = runTest { + fun `given it has completed a sync backward when execute then only sync process is called`() = runTest { // Given val aRoomId = "roomId" + val aLoadedStatus = LoadedPollsStatus( + canLoadMore = true, + nbSyncedDays = 10, + hasCompletedASyncBackward = true, + ) coJustRun { fakeRoomPollRepository.syncPolls(aRoomId) } + coEvery { fakeGetLoadedPollsStatusUseCase.execute(aRoomId) } returns aLoadedStatus // When - syncPollsUseCase.execute(aRoomId) + val result = syncPollsUseCase.execute(aRoomId) // Then - coVerify { fakeRoomPollRepository.syncPolls(aRoomId) } + result shouldBeEqualTo aLoadedStatus + coVerifyOrder { + fakeRoomPollRepository.syncPolls(aRoomId) + fakeGetLoadedPollsStatusUseCase.execute(aRoomId) + } + coVerify(inverse = true) { + fakeLoadMorePollsUseCase.execute(any()) + } + } + + @Test + fun `given it has not completed a sync backward when execute then sync process and load more is called`() = runTest { + // Given + val aRoomId = "roomId" + val aLoadedStatus = LoadedPollsStatus( + canLoadMore = true, + nbSyncedDays = 10, + hasCompletedASyncBackward = false, + ) + val anUpdatedLoadedStatus = LoadedPollsStatus( + canLoadMore = true, + nbSyncedDays = 10, + hasCompletedASyncBackward = true, + ) + coJustRun { fakeRoomPollRepository.syncPolls(aRoomId) } + coEvery { fakeGetLoadedPollsStatusUseCase.execute(aRoomId) } returns aLoadedStatus + coEvery { fakeLoadMorePollsUseCase.execute(aRoomId) } returns anUpdatedLoadedStatus + + // When + val result = syncPollsUseCase.execute(aRoomId) + + // Then + result shouldBeEqualTo anUpdatedLoadedStatus + coVerifyOrder { + fakeRoomPollRepository.syncPolls(aRoomId) + fakeGetLoadedPollsStatusUseCase.execute(aRoomId) + fakeLoadMorePollsUseCase.execute(aRoomId) + } } } From 184a25b811a3939682ef754bd4583858874e5022 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Tue, 24 Jan 2023 15:43:43 +0100 Subject: [PATCH 043/189] Adding unit tests for dispose methods --- .../polls/list/data/RoomPollRepository.kt | 1 - .../list/domain/DisposePollHistoryUseCase.kt | 4 -- .../polls/list/data/RoomPollRepositoryTest.kt | 13 ++++++ .../domain/DisposePollHistoryUseCaseTest.kt | 45 +++++++++++++++++++ 4 files changed, 58 insertions(+), 5 deletions(-) create mode 100644 vector/src/test/java/im/vector/app/features/roomprofile/polls/list/domain/DisposePollHistoryUseCaseTest.kt diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollRepository.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollRepository.kt index d6dfe12435..d993302fb7 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollRepository.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollRepository.kt @@ -25,7 +25,6 @@ class RoomPollRepository @Inject constructor( private val roomPollDataSource: RoomPollDataSource, ) { - // TODO add unit tests fun dispose(roomId: String) { roomPollDataSource.dispose(roomId) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/domain/DisposePollHistoryUseCase.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/domain/DisposePollHistoryUseCase.kt index 4ed4e71501..f1cf031f73 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/domain/DisposePollHistoryUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/domain/DisposePollHistoryUseCase.kt @@ -17,16 +17,12 @@ package im.vector.app.features.roomprofile.polls.list.domain import im.vector.app.features.roomprofile.polls.list.data.RoomPollRepository -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map -import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import javax.inject.Inject class DisposePollHistoryUseCase @Inject constructor( private val roomPollRepository: RoomPollRepository, ) { - // TODO add unit tests fun execute(roomId: String) { roomPollRepository.dispose(roomId) } diff --git a/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollRepositoryTest.kt b/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollRepositoryTest.kt index 3883f0dafd..e57b52a812 100644 --- a/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollRepositoryTest.kt +++ b/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollRepositoryTest.kt @@ -20,6 +20,7 @@ import io.mockk.coEvery import io.mockk.coJustRun import io.mockk.coVerify import io.mockk.every +import io.mockk.justRun import io.mockk.mockk import io.mockk.verify import kotlinx.coroutines.flow.firstOrNull @@ -40,6 +41,18 @@ class RoomPollRepositoryTest { roomPollDataSource = fakeRoomPollDataSource, ) + @Test + fun `given data source when dispose then correct method of data source is called`() { + // Given + justRun { fakeRoomPollDataSource.dispose(A_ROOM_ID) } + + // When + roomPollRepository.dispose(A_ROOM_ID) + + // Then + verify { fakeRoomPollDataSource.dispose(A_ROOM_ID) } + } + @Test fun `given data source when getting polls then correct method of data source is called`() = runTest { // Given diff --git a/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/domain/DisposePollHistoryUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/domain/DisposePollHistoryUseCaseTest.kt new file mode 100644 index 0000000000..0063d9bfd5 --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/domain/DisposePollHistoryUseCaseTest.kt @@ -0,0 +1,45 @@ +/* + * 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.roomprofile.polls.list.domain + +import im.vector.app.features.roomprofile.polls.list.data.RoomPollRepository +import io.mockk.coVerify +import io.mockk.justRun +import io.mockk.mockk +import org.junit.Test + +internal class DisposePollHistoryUseCaseTest { + + private val fakeRoomPollRepository = mockk() + + private val disposePollHistoryUseCase = DisposePollHistoryUseCase( + roomPollRepository = fakeRoomPollRepository, + ) + + @Test + fun `given repo when execute then correct method of repo is called`() { + // Given + val aRoomId = "roomId" + justRun { fakeRoomPollRepository.dispose(aRoomId) } + + // When + disposePollHistoryUseCase.execute(aRoomId) + + // Then + coVerify { fakeRoomPollRepository.dispose(aRoomId) } + } +} From 983649d89a7e2001e6830afc307472735ab636ce Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Tue, 24 Jan 2023 15:49:31 +0100 Subject: [PATCH 044/189] Adding sdk changelog entry --- changelog.d/7864.sdk | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7864.sdk diff --git a/changelog.d/7864.sdk b/changelog.d/7864.sdk new file mode 100644 index 0000000000..b7c6a5b339 --- /dev/null +++ b/changelog.d/7864.sdk @@ -0,0 +1 @@ +[Poll] Adding PollHistoryService From 21cee773e2dc35face9db14408952e241acc404b Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Tue, 24 Jan 2023 16:17:24 +0100 Subject: [PATCH 045/189] Adding unit tests for RoomPollDataSource --- .../polls/list/data/RoomPollDataSource.kt | 1 - .../polls/list/data/RoomPollDataSourceTest.kt | 130 ++++++++++++++++++ .../app/test/fakes/FakePollHistoryService.kt | 75 ++++++++++ .../java/im/vector/app/test/fakes/FakeRoom.kt | 3 + 4 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 vector/src/test/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollDataSourceTest.kt create mode 100644 vector/src/test/java/im/vector/app/test/fakes/FakePollHistoryService.kt diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollDataSource.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollDataSource.kt index 60b53a90b4..3a65297fde 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollDataSource.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollDataSource.kt @@ -25,7 +25,6 @@ import org.matrix.android.sdk.api.session.room.poll.PollHistoryService import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import javax.inject.Inject -// TODO add unit tests class RoomPollDataSource @Inject constructor( private val activeSessionHolder: ActiveSessionHolder, ) { diff --git a/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollDataSourceTest.kt b/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollDataSourceTest.kt new file mode 100644 index 0000000000..11006b10e8 --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollDataSourceTest.kt @@ -0,0 +1,130 @@ +/* + * 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.roomprofile.polls.list.data + +import im.vector.app.test.fakes.FakeActiveSessionHolder +import im.vector.app.test.fakes.FakeFlowLiveDataConversions +import im.vector.app.test.fakes.FakePollHistoryService +import im.vector.app.test.fakes.givenAsFlow +import io.mockk.unmockkAll +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.test.runTest +import org.amshove.kluent.shouldBeEqualTo +import org.junit.Test +import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent + +private const val A_ROOM_ID = "room-id" + +internal class RoomPollDataSourceTest { + + private val fakeActiveSessionHolder = FakeActiveSessionHolder() + + private val roomPollDataSource = RoomPollDataSource( + activeSessionHolder = fakeActiveSessionHolder.instance, + ) + + @Test + fun `given poll history service when dispose then correct method of service is called`() { + // Given + val fakePollHistoryService = givenPollHistoryService() + fakePollHistoryService.givenDispose() + + // When + roomPollDataSource.dispose(A_ROOM_ID) + + // Then + fakePollHistoryService.verifyDispose() + } + + @Test + fun `given poll history service when get polls then correct method of service is called and correct result is returned`() = runTest { + // Given + val fakeFlowLiveDataConversions = FakeFlowLiveDataConversions() + fakeFlowLiveDataConversions.setup() + val fakePollHistoryService = givenPollHistoryService() + val pollEvents = listOf() + fakePollHistoryService + .givenGetPollsReturns(pollEvents) + .givenAsFlow() + + // When + val result = roomPollDataSource.getPolls(A_ROOM_ID).firstOrNull() + + // Then + result shouldBeEqualTo pollEvents + fakePollHistoryService.verifyGetPolls() + unmockkAll() + } + + @Test + fun `given poll history service when get loaded polls then correct method of service is called and correct result is returned`() = runTest { + // Given + val fakePollHistoryService = givenPollHistoryService() + val aLoadedPollsStatus = givenALoadedPollsStatus() + fakePollHistoryService.givenGetLoadedPollsStatusReturns(aLoadedPollsStatus) + + // When + val result = roomPollDataSource.getLoadedPollsStatus(A_ROOM_ID) + + // Then + result shouldBeEqualTo aLoadedPollsStatus + fakePollHistoryService.verifyGetLoadedPollsStatus() + } + + @Test + fun `given poll history service when load more then correct method of service is called and correct result is returned`() = runTest { + // Given + val fakePollHistoryService = givenPollHistoryService() + val aLoadedPollsStatus = givenALoadedPollsStatus() + fakePollHistoryService.givenLoadMoreReturns(aLoadedPollsStatus) + + // When + val result = roomPollDataSource.loadMorePolls(A_ROOM_ID) + + // Then + result shouldBeEqualTo aLoadedPollsStatus + fakePollHistoryService.verifyLoadMore() + } + + @Test + fun `given poll history service when sync polls then correct method of service is called`() = runTest { + // Given + val fakePollHistoryService = givenPollHistoryService() + fakePollHistoryService.givenSyncPollsSuccess() + + // When + roomPollDataSource.syncPolls(A_ROOM_ID) + + // Then + fakePollHistoryService.verifySyncPolls() + } + + private fun givenPollHistoryService(): FakePollHistoryService { + return fakeActiveSessionHolder + .fakeSession + .fakeRoomService + .getRoom(A_ROOM_ID) + .pollHistoryService() + } + + private fun givenALoadedPollsStatus() = LoadedPollsStatus( + canLoadMore = true, + nbSyncedDays = 10, + hasCompletedASyncBackward = true, + ) +} diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakePollHistoryService.kt b/vector/src/test/java/im/vector/app/test/fakes/FakePollHistoryService.kt new file mode 100644 index 0000000000..c934c3acde --- /dev/null +++ b/vector/src/test/java/im/vector/app/test/fakes/FakePollHistoryService.kt @@ -0,0 +1,75 @@ +/* + * 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.test.fakes + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import io.mockk.coEvery +import io.mockk.coJustRun +import io.mockk.coVerify +import io.mockk.every +import io.mockk.justRun +import io.mockk.mockk +import io.mockk.verify +import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus +import org.matrix.android.sdk.api.session.room.poll.PollHistoryService +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent + +class FakePollHistoryService : PollHistoryService by mockk() { + + fun givenDispose() { + justRun { dispose() } + } + + fun verifyDispose() { + verify { dispose() } + } + + fun givenGetPollsReturns(events: List): LiveData> { + return MutableLiveData(events).also { + every { getPollEvents() } returns it + } + } + + fun verifyGetPolls() { + verify { getPollEvents() } + } + + fun givenGetLoadedPollsStatusReturns(status: LoadedPollsStatus) { + coEvery { getLoadedPollsStatus() } returns status + } + + fun verifyGetLoadedPollsStatus() { + coVerify { getLoadedPollsStatus() } + } + + fun givenLoadMoreReturns(status: LoadedPollsStatus) { + coEvery { loadMore() } returns status + } + + fun verifyLoadMore() { + coVerify { loadMore() } + } + + fun givenSyncPollsSuccess() { + coJustRun { syncPolls() } + } + + fun verifySyncPolls() { + coVerify { syncPolls() } + } +} diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeRoom.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeRoom.kt index 7835c314ef..d3703f11c4 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeRoom.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeRoom.kt @@ -25,6 +25,7 @@ class FakeRoom( private val fakeTimelineService: FakeTimelineService = FakeTimelineService(), private val fakeRelationService: FakeRelationService = FakeRelationService(), private val fakeStateService: FakeStateService = FakeStateService(), + private val fakePollHistoryService: FakePollHistoryService = FakePollHistoryService(), ) : Room by mockk() { override fun locationSharingService() = fakeLocationSharingService @@ -36,4 +37,6 @@ class FakeRoom( override fun relationService() = fakeRelationService override fun stateService() = fakeStateService + + override fun pollHistoryService() = fakePollHistoryService } From 326ece4b082216de07f28269e4dbf38821c9285f Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Tue, 24 Jan 2023 17:15:35 +0100 Subject: [PATCH 046/189] Fixing code styling issue --- .../sdk/internal/session/room/poll/SyncPollsTask.kt | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/SyncPollsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/SyncPollsTask.kt index e968095408..4a83f0f870 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/SyncPollsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/SyncPollsTask.kt @@ -17,22 +17,11 @@ package org.matrix.android.sdk.internal.session.room.poll import com.zhuinden.monarchy.Monarchy -import kotlinx.coroutines.delay -import org.matrix.android.sdk.api.session.events.model.isPoll -import org.matrix.android.sdk.api.session.events.model.isPollResponse -import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus import org.matrix.android.sdk.api.session.room.timeline.Timeline import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.internal.database.model.PollHistoryStatusEntity import org.matrix.android.sdk.internal.database.query.getOrCreate import org.matrix.android.sdk.internal.di.SessionDatabase -import org.matrix.android.sdk.internal.network.GlobalErrorReceiver -import org.matrix.android.sdk.internal.network.executeRequest -import org.matrix.android.sdk.internal.session.room.RoomAPI -import org.matrix.android.sdk.internal.session.room.event.FilterAndStoreEventsTask -import org.matrix.android.sdk.internal.session.room.poll.PollConstants.MILLISECONDS_PER_DAY -import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection -import org.matrix.android.sdk.internal.session.room.timeline.PaginationResponse import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.util.awaitTransaction import javax.inject.Inject @@ -56,7 +45,7 @@ internal class DefaultSyncPollsTask @Inject constructor( params.timeline.restartWithEventId(currentPollHistoryStatus.mostRecentEventIdReached) var loadStatus = LoadStatus(shouldLoadMore = true) - while (loadStatus.shouldLoadMore){ + while (loadStatus.shouldLoadMore) { loadStatus = fetchMorePollEventsForward(params) } From cfc67d5b67d10a0409317a8474a5aec2755545fd Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Tue, 24 Jan 2023 17:58:52 +0100 Subject: [PATCH 047/189] Adding unit tests for PollSummaryMapper --- .../room/poll/DefaultPollHistoryService.kt | 1 - .../polls/list/ui/PollSummaryMapper.kt | 1 - .../polls/list/ui/PollSummaryMapperTest.kt | 201 ++++++++++++++++++ 3 files changed, 201 insertions(+), 2 deletions(-) create mode 100644 vector/src/test/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapperTest.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt index 3ef0e7918a..51c8b83191 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt @@ -43,7 +43,6 @@ import timber.log.Timber private const val LOADING_PERIOD_IN_DAYS = 30 private const val EVENTS_PAGE_SIZE = 250 -// TODO add unit tests internal class DefaultPollHistoryService @AssistedInject constructor( @Assisted private val roomId: String, @Assisted private val timelineService: TimelineService, diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapper.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapper.kt index 9d4128e0ac..64c712e61f 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapper.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapper.kt @@ -25,7 +25,6 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import timber.log.Timber import javax.inject.Inject -// TODO add unit tests class PollSummaryMapper @Inject constructor( private val pollResponseDataFactory: PollResponseDataFactory, private val pollOptionViewStateFactory: PollOptionViewStateFactory, diff --git a/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapperTest.kt b/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapperTest.kt new file mode 100644 index 0000000000..b523365970 --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/ui/PollSummaryMapperTest.kt @@ -0,0 +1,201 @@ +/* + * 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.roomprofile.polls.list.ui + +import im.vector.app.core.extensions.getVectorLastMessageContent +import im.vector.app.features.home.room.detail.timeline.factory.PollOptionViewStateFactory +import im.vector.app.features.home.room.detail.timeline.helper.PollResponseDataFactory +import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState +import im.vector.app.features.home.room.detail.timeline.item.PollResponseData +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.unmockkAll +import org.amshove.kluent.shouldBe +import org.amshove.kluent.shouldBeEqualTo +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.matrix.android.sdk.api.session.room.model.message.MessageContent +import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent +import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent +import org.matrix.android.sdk.api.session.room.model.message.PollCreationInfo +import org.matrix.android.sdk.api.session.room.model.message.PollQuestion +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent + +private const val AN_EVENT_ID = "event-id" +private const val AN_EVENT_TIMESTAMP = 123L +private const val A_POLL_TITLE = "poll-title" + +internal class PollSummaryMapperTest { + + private val fakePollResponseDataFactory = mockk() + private val fakePollOptionViewStateFactory = mockk() + + private val pollSummaryMapper = PollSummaryMapper( + pollResponseDataFactory = fakePollResponseDataFactory, + pollOptionViewStateFactory = fakePollOptionViewStateFactory, + ) + + @Before + fun setup() { + mockkStatic("im.vector.app.core.extensions.TimelineEventKt") + } + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun `given a not ended poll event when mapping to model then result is active poll`() { + // Given + val pollStartedEvent = givenAPollTimelineEvent( + eventId = AN_EVENT_ID, + creationTimestamp = AN_EVENT_TIMESTAMP, + pollTitle = A_POLL_TITLE, + isClosed = false, + ) + val expectedResult = PollSummary.ActivePoll( + id = AN_EVENT_ID, + creationTimestamp = AN_EVENT_TIMESTAMP, + title = A_POLL_TITLE, + ) + + // When + val result = pollSummaryMapper.map(pollStartedEvent) + + // Then + result shouldBeEqualTo expectedResult + } + + @Test + fun `given an ended poll event when mapping to model then result is ended poll`() { + // Given + val totalVotes = 10 + val winnerOptions = listOf() + val endedPollEvent = givenAPollTimelineEvent( + eventId = AN_EVENT_ID, + creationTimestamp = AN_EVENT_TIMESTAMP, + pollTitle = A_POLL_TITLE, + isClosed = true, + totalVotes = totalVotes, + winnerOptions = winnerOptions, + ) + val expectedResult = PollSummary.EndedPoll( + id = AN_EVENT_ID, + creationTimestamp = AN_EVENT_TIMESTAMP, + title = A_POLL_TITLE, + totalVotes = totalVotes, + winnerOptions = winnerOptions, + ) + + // When + val result = pollSummaryMapper.map(endedPollEvent) + + // Then + result shouldBeEqualTo expectedResult + } + + @Test + fun `given missing data in event when mapping to model then result is null`() { + // Given + val noIdPollEvent = givenAPollTimelineEvent( + eventId = "", + creationTimestamp = AN_EVENT_TIMESTAMP, + pollTitle = A_POLL_TITLE, + isClosed = false, + ) + val noTimestampPollEvent = givenAPollTimelineEvent( + eventId = AN_EVENT_ID, + creationTimestamp = 0, + pollTitle = A_POLL_TITLE, + isClosed = false, + ) + val notAPollEvent = givenATimelineEvent( + eventId = AN_EVENT_ID, + creationTimestamp = 0, + content = mockk() + ) + + // When + val result1 = pollSummaryMapper.map(noIdPollEvent) + val result2 = pollSummaryMapper.map(noTimestampPollEvent) + val result3 = pollSummaryMapper.map(notAPollEvent) + + // Then + result1 shouldBe null + result2 shouldBe null + result3 shouldBe null + } + + private fun givenATimelineEvent( + eventId: String, + creationTimestamp: Long, + content: MessageContent, + ): TimelineEvent { + val timelineEvent = mockk() + every { timelineEvent.root.eventId } returns eventId + every { timelineEvent.root.originServerTs } returns creationTimestamp + every { timelineEvent.getVectorLastMessageContent() } returns content + return timelineEvent + } + + private fun givenAPollTimelineEvent( + eventId: String, + creationTimestamp: Long, + pollTitle: String, + isClosed: Boolean, + totalVotes: Int = 0, + winnerOptions: List = emptyList(), + ): TimelineEvent { + val pollCreationInfo = givenPollCreationInfo(pollTitle) + val messageContent = givenAMessagePollContent(pollCreationInfo) + val timelineEvent = givenATimelineEvent(eventId, creationTimestamp, messageContent) + val pollResponseData = givenAPollResponseData(isClosed, totalVotes) + every { fakePollResponseDataFactory.create(timelineEvent) } returns pollResponseData + every { + fakePollOptionViewStateFactory.createPollEndedOptions( + pollCreationInfo, + pollResponseData + ) + } returns winnerOptions + + return timelineEvent + } + + private fun givenAMessagePollContent(pollCreationInfo: PollCreationInfo): MessagePollContent { + return MessagePollContent( + unstablePollCreationInfo = pollCreationInfo, + ) + } + + private fun givenPollCreationInfo(pollTitle: String): PollCreationInfo { + return PollCreationInfo( + question = PollQuestion(unstableQuestion = pollTitle), + ) + } + + private fun givenAPollResponseData(isClosed: Boolean, totalVotes: Int): PollResponseData { + return PollResponseData( + myVote = "", + votes = emptyMap(), + isClosed = isClosed, + totalVotes = totalVotes, + ) + } +} From 41bb743cf4c608454dbe5643de7d2716400733a4 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Wed, 25 Jan 2023 10:54:37 +0100 Subject: [PATCH 048/189] Adding unit tests for PollOptionViewStateFactory --- .../factory/PollOptionViewStateFactory.kt | 2 - .../factory/PollItemViewStateFactoryTest.kt | 40 +---- .../factory/PollOptionViewStateFactoryTest.kt | 157 ++++++++++++++++++ .../vector/app/test/fixtures/PollFixture.kt | 67 ++++++++ .../app/test/fixtures/RoomPollFixture.kt | 47 ------ 5 files changed, 228 insertions(+), 85 deletions(-) create mode 100644 vector/src/test/java/im/vector/app/features/home/room/detail/timeline/factory/PollOptionViewStateFactoryTest.kt create mode 100644 vector/src/test/java/im/vector/app/test/fixtures/PollFixture.kt delete mode 100644 vector/src/test/java/im/vector/app/test/fixtures/RoomPollFixture.kt diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollOptionViewStateFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollOptionViewStateFactory.kt index 3000164f74..875675745c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollOptionViewStateFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollOptionViewStateFactory.kt @@ -21,12 +21,10 @@ import im.vector.app.features.home.room.detail.timeline.item.PollResponseData import org.matrix.android.sdk.api.session.room.model.message.PollCreationInfo import javax.inject.Inject -// TODO add unit tests class PollOptionViewStateFactory @Inject constructor() { fun createPollEndedOptions(pollCreationInfo: PollCreationInfo?, pollResponseData: PollResponseData?): List { val winnerVoteCount = pollResponseData?.winnerVoteCount - return pollCreationInfo?.answers?.map { answer -> val voteSummary = pollResponseData?.getVoteSummaryOfAnOption(answer.id ?: "") PollOptionViewState.PollEnded( diff --git a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactoryTest.kt b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactoryTest.kt index 067cb264e1..99c6c69849 100644 --- a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactoryTest.kt +++ b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactoryTest.kt @@ -25,6 +25,10 @@ import im.vector.app.features.home.room.detail.timeline.item.ReactionsSummaryDat import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout import im.vector.app.features.poll.PollViewState import im.vector.app.test.fakes.FakeStringProvider +import im.vector.app.test.fixtures.PollFixture.A_MESSAGE_INFORMATION_DATA +import im.vector.app.test.fixtures.PollFixture.A_POLL_CONTENT +import im.vector.app.test.fixtures.PollFixture.A_POLL_OPTION_IDS +import im.vector.app.test.fixtures.PollFixture.A_POLL_RESPONSE_DATA import io.mockk.every import io.mockk.mockk import io.mockk.verify @@ -37,42 +41,6 @@ import org.matrix.android.sdk.api.session.room.model.message.PollQuestion import org.matrix.android.sdk.api.session.room.model.message.PollType import org.matrix.android.sdk.api.session.room.send.SendState -private val A_MESSAGE_INFORMATION_DATA = MessageInformationData( - eventId = "eventId", - senderId = "senderId", - ageLocalTS = 0, - avatarUrl = "", - sendState = SendState.SENT, - messageLayout = TimelineMessageLayout.Default(showAvatar = true, showDisplayName = true, showTimestamp = true), - reactionsSummary = ReactionsSummaryData(), - sentByMe = true, -) - -private val A_POLL_RESPONSE_DATA = PollResponseData( - myVote = null, - votes = emptyMap(), -) - -private val A_POLL_OPTION_IDS = listOf("5ef5f7b0-c9a1-49cf-a0b3-374729a43e76", "ec1a4db0-46d8-4d7a-9bb6-d80724715938", "3677ca8e-061b-40ab-bffe-b22e4e88fcad") - -private val A_POLL_CONTENT = MessagePollContent( - unstablePollCreationInfo = PollCreationInfo( - question = PollQuestion( - unstableQuestion = "What is your favourite coffee?" - ), kind = PollType.UNDISCLOSED_UNSTABLE, maxSelections = 1, answers = listOf( - PollAnswer( - id = A_POLL_OPTION_IDS[0], unstableAnswer = "Double Espresso" - ), - PollAnswer( - id = A_POLL_OPTION_IDS[1], unstableAnswer = "Macchiato" - ), - PollAnswer( - id = A_POLL_OPTION_IDS[2], unstableAnswer = "Iced Coffee" - ), - ) - ) -) - class PollItemViewStateFactoryTest { private val fakeStringProvider = FakeStringProvider() diff --git a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/factory/PollOptionViewStateFactoryTest.kt b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/factory/PollOptionViewStateFactoryTest.kt new file mode 100644 index 0000000000..285cff7d63 --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/factory/PollOptionViewStateFactoryTest.kt @@ -0,0 +1,157 @@ +/* + * 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.factory + +import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState +import im.vector.app.features.home.room.detail.timeline.item.PollVoteSummaryData +import im.vector.app.test.fixtures.PollFixture.A_POLL_CONTENT +import im.vector.app.test.fixtures.PollFixture.A_POLL_OPTION_IDS +import im.vector.app.test.fixtures.PollFixture.A_POLL_RESPONSE_DATA +import org.amshove.kluent.shouldBeEqualTo +import org.junit.Test +import org.matrix.android.sdk.api.session.room.model.message.PollType + +internal class PollOptionViewStateFactoryTest { + + private val pollOptionViewStateFactory = PollOptionViewStateFactory() + + @Test + fun `given poll data when creating ended poll options then correct options are returned`() { + // Given + val winnerVotesCount = 0 + val pollResponseData = A_POLL_RESPONSE_DATA.copy( + isClosed = true, + winnerVoteCount = winnerVotesCount, + ) + val pollCreationInfo = A_POLL_CONTENT.getBestPollCreationInfo() + val expectedOptions = pollCreationInfo?.answers?.map { answer -> + PollOptionViewState.PollEnded( + optionId = answer.id.orEmpty(), + optionAnswer = answer.getBestAnswer().orEmpty(), + voteCount = 0, + votePercentage = 0.0, + isWinner = false, + ) + } + + // When + val result = pollOptionViewStateFactory.createPollEndedOptions( + pollCreationInfo = pollCreationInfo, + pollResponseData = pollResponseData, + ) + + // Then + result shouldBeEqualTo expectedOptions + } + + @Test + fun `given poll data when creating sending poll options then correct options are returned`() { + // Given + val pollCreationInfo = A_POLL_CONTENT.getBestPollCreationInfo() + val expectedOptions = pollCreationInfo?.answers?.map { answer -> + PollOptionViewState.PollSending( + optionId = answer.id.orEmpty(), + optionAnswer = answer.getBestAnswer().orEmpty(), + ) + } + + // When + val result = pollOptionViewStateFactory.createPollSendingOptions( + pollCreationInfo = pollCreationInfo, + ) + + // Then + result shouldBeEqualTo expectedOptions + } + + @Test + fun `given poll data when creating undisclosed poll options then correct options are returned`() { + // Given + val pollResponseData = A_POLL_RESPONSE_DATA + val pollCreationInfo = A_POLL_CONTENT.getBestPollCreationInfo() + val expectedOptions = pollCreationInfo?.answers?.map { answer -> + PollOptionViewState.PollUndisclosed( + optionId = answer.id.orEmpty(), + optionAnswer = answer.getBestAnswer().orEmpty(), + isSelected = false, + ) + } + + // When + val result = pollOptionViewStateFactory.createPollUndisclosedOptions( + pollCreationInfo = pollCreationInfo, + pollResponseData = pollResponseData, + ) + + // Then + result shouldBeEqualTo expectedOptions + } + + @Test + fun `given poll data when creating voted poll options then correct options are returned`() { + // Given + val pollResponseData = A_POLL_RESPONSE_DATA.copy( + totalVotes = 1, + myVote = A_POLL_OPTION_IDS[0], + votes = mapOf(A_POLL_OPTION_IDS[0] to PollVoteSummaryData(total = 1, percentage = 1.0)), + ) + val disclosedPollContent = A_POLL_CONTENT.copy( + unstablePollCreationInfo = A_POLL_CONTENT.getBestPollCreationInfo()?.copy( + kind = PollType.DISCLOSED_UNSTABLE, + ), + ) + val pollCreationInfo = disclosedPollContent.getBestPollCreationInfo() + val expectedOptions = pollCreationInfo?.answers?.mapIndexed { index, answer -> + PollOptionViewState.PollVoted( + optionId = answer.id.orEmpty(), + optionAnswer = answer.getBestAnswer().orEmpty(), + voteCount = if (index == 0) 1 else 0, + votePercentage = if (index == 0) 1.0 else 0.0, + isSelected = index == 0, + ) + } + + // When + val result = pollOptionViewStateFactory.createPollVotedOptions( + pollCreationInfo = pollCreationInfo, + pollResponseData = pollResponseData, + ) + + // Then + result shouldBeEqualTo expectedOptions + } + + @Test + fun `given poll data when creating ready poll options then correct options are returned`() { + // Given + val pollCreationInfo = A_POLL_CONTENT.getBestPollCreationInfo() + val expectedOptions = pollCreationInfo?.answers?.map { answer -> + PollOptionViewState.PollReady( + optionId = answer.id.orEmpty(), + optionAnswer = answer.getBestAnswer().orEmpty(), + ) + } + + // When + val result = pollOptionViewStateFactory.createPollReadyOptions( + pollCreationInfo = pollCreationInfo, + ) + + // Then + result shouldBeEqualTo expectedOptions + } +} diff --git a/vector/src/test/java/im/vector/app/test/fixtures/PollFixture.kt b/vector/src/test/java/im/vector/app/test/fixtures/PollFixture.kt new file mode 100644 index 0000000000..24e037b299 --- /dev/null +++ b/vector/src/test/java/im/vector/app/test/fixtures/PollFixture.kt @@ -0,0 +1,67 @@ +/* + * 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.test.fixtures + +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.ReactionsSummaryData +import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout +import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent +import org.matrix.android.sdk.api.session.room.model.message.PollAnswer +import org.matrix.android.sdk.api.session.room.model.message.PollCreationInfo +import org.matrix.android.sdk.api.session.room.model.message.PollQuestion +import org.matrix.android.sdk.api.session.room.model.message.PollType +import org.matrix.android.sdk.api.session.room.send.SendState + +object PollFixture { + + val A_MESSAGE_INFORMATION_DATA = MessageInformationData( + eventId = "eventId", + senderId = "senderId", + ageLocalTS = 0, + avatarUrl = "", + sendState = SendState.SENT, + messageLayout = TimelineMessageLayout.Default(showAvatar = true, showDisplayName = true, showTimestamp = true), + reactionsSummary = ReactionsSummaryData(), + sentByMe = true, + ) + + val A_POLL_RESPONSE_DATA = PollResponseData( + myVote = null, + votes = emptyMap(), + ) + + val A_POLL_OPTION_IDS = listOf("5ef5f7b0-c9a1-49cf-a0b3-374729a43e76", "ec1a4db0-46d8-4d7a-9bb6-d80724715938", "3677ca8e-061b-40ab-bffe-b22e4e88fcad") + + val A_POLL_CONTENT = MessagePollContent( + unstablePollCreationInfo = PollCreationInfo( + question = PollQuestion( + unstableQuestion = "What is your favourite coffee?" + ), kind = PollType.UNDISCLOSED_UNSTABLE, maxSelections = 1, answers = listOf( + PollAnswer( + id = A_POLL_OPTION_IDS[0], unstableAnswer = "Double Espresso" + ), + PollAnswer( + id = A_POLL_OPTION_IDS[1], unstableAnswer = "Macchiato" + ), + PollAnswer( + id = A_POLL_OPTION_IDS[2], unstableAnswer = "Iced Coffee" + ), + ) + ) + ) +} diff --git a/vector/src/test/java/im/vector/app/test/fixtures/RoomPollFixture.kt b/vector/src/test/java/im/vector/app/test/fixtures/RoomPollFixture.kt deleted file mode 100644 index 4ccd9fa35a..0000000000 --- a/vector/src/test/java/im/vector/app/test/fixtures/RoomPollFixture.kt +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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.test.fixtures - -import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState -import im.vector.app.features.roomprofile.polls.list.ui.PollSummary - -object RoomPollFixture { - - fun anActivePollSummary( - id: String = "", - timestamp: Long, - title: String = "", - ) = PollSummary.ActivePoll( - id = id, - creationTimestamp = timestamp, - title = title, - ) - - fun anEndedPollSummary( - id: String = "", - timestamp: Long, - title: String = "", - totalVotes: Int, - winnerOptions: List - ) = PollSummary.EndedPoll( - id = id, - creationTimestamp = timestamp, - title = title, - totalVotes = totalVotes, - winnerOptions = winnerOptions, - ) -} From 652a2c2834d02630a65e9d15dcca834339269832 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Wed, 25 Jan 2023 10:58:52 +0100 Subject: [PATCH 049/189] Fix migration of DB after rebase --- .../database/RealmSessionStoreMigration.kt | 4 +- .../database/migration/MigrateSessionTo050.kt | 43 +++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo050.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt index fe55beb997..45bcd792c2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt @@ -66,6 +66,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo046 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo047 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo048 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo049 +import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo050 import org.matrix.android.sdk.internal.util.Normalizer import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration import javax.inject.Inject @@ -74,7 +75,7 @@ internal class RealmSessionStoreMigration @Inject constructor( private val normalizer: Normalizer ) : MatrixRealmMigration( dbName = "Session", - schemaVersion = 49L, + schemaVersion = 50L, ) { /** * Forces all RealmSessionStoreMigration instances to be equal. @@ -133,5 +134,6 @@ internal class RealmSessionStoreMigration @Inject constructor( if (oldVersion < 47) MigrateSessionTo047(realm).perform() if (oldVersion < 48) MigrateSessionTo048(realm).perform() if (oldVersion < 49) MigrateSessionTo049(realm).perform() + if (oldVersion < 50) MigrateSessionTo050(realm).perform() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo050.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo050.kt new file mode 100644 index 0000000000..1d864ae7e9 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo050.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2023 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.database.migration + +import io.realm.DynamicRealm +import org.matrix.android.sdk.internal.database.model.PollHistoryStatusEntityFields +import org.matrix.android.sdk.internal.util.database.RealmMigrator + +/** + * Adding new entity PollHistoryStatusEntity. + */ +internal class MigrateSessionTo050(realm: DynamicRealm) : RealmMigrator(realm, 50) { + + override fun doMigrate(realm: DynamicRealm) { + realm.schema.create("PollHistoryStatusEntity") + .addField(PollHistoryStatusEntityFields.ROOM_ID, String::class.java) + .addPrimaryKey(PollHistoryStatusEntityFields.ROOM_ID) + .setRequired(PollHistoryStatusEntityFields.ROOM_ID, true) + .addField(PollHistoryStatusEntityFields.CURRENT_TIMESTAMP_TARGET_BACKWARD_MS, Long::class.java) + .setNullable(PollHistoryStatusEntityFields.CURRENT_TIMESTAMP_TARGET_BACKWARD_MS, true) + .addField(PollHistoryStatusEntityFields.OLDEST_TIMESTAMP_TARGET_REACHED_MS, Long::class.java) + .setNullable(PollHistoryStatusEntityFields.OLDEST_TIMESTAMP_TARGET_REACHED_MS, true) + .addField(PollHistoryStatusEntityFields.OLDEST_EVENT_ID_REACHED, String::class.java) + .addField(PollHistoryStatusEntityFields.MOST_RECENT_EVENT_ID_REACHED, String::class.java) + .addField(PollHistoryStatusEntityFields.IS_END_OF_POLLS_BACKWARD, Boolean::class.java) + } +} + + From 8a54f7a4edea58ffb0a074f98a0bbf2ad67ecb0c Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Wed, 25 Jan 2023 11:42:27 +0100 Subject: [PATCH 050/189] Revert nullable field in RoomAPI --- .../org/matrix/android/sdk/internal/session/room/RoomAPI.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt index cf57e90c25..aa4bdb1dd4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt @@ -89,7 +89,7 @@ internal interface RoomAPI { @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/messages") suspend fun getRoomMessagesFrom( @Path("roomId") roomId: String, - @Query("from") from: String?, + @Query("from") from: String, @Query("dir") dir: String, @Query("limit") limit: Int?, @Query("filter") filter: String?, From 7118368a15d063c0cb00f43b4837565b087dfef7 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Wed, 25 Jan 2023 11:47:46 +0100 Subject: [PATCH 051/189] Fix copyright in SDK --- .../android/sdk/api/session/room/poll/LoadedPollsStatus.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/poll/LoadedPollsStatus.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/poll/LoadedPollsStatus.kt index efc01e2cdf..003e03178b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/poll/LoadedPollsStatus.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/poll/LoadedPollsStatus.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 New Vector Ltd + * Copyright (c) 2023 The Matrix.org Foundation C.I.C. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From f9591a5fc622ee698997b3a8179cae87bf40a71f Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Wed, 25 Jan 2023 11:53:14 +0100 Subject: [PATCH 052/189] Fix code quality issues --- .../internal/database/migration/MigrateSessionTo050.kt | 2 -- .../sdk/internal/session/room/poll/LoadMorePollsTask.kt | 2 +- .../timeline/factory/PollItemViewStateFactoryTest.kt | 8 -------- 3 files changed, 1 insertion(+), 11 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo050.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo050.kt index 1d864ae7e9..dfbfdc8da7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo050.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo050.kt @@ -39,5 +39,3 @@ internal class MigrateSessionTo050(realm: DynamicRealm) : RealmMigrator(realm, 5 .addField(PollHistoryStatusEntityFields.IS_END_OF_POLLS_BACKWARD, Boolean::class.java) } } - - diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/LoadMorePollsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/LoadMorePollsTask.kt index 939769b525..248d1e5ee3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/LoadMorePollsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/LoadMorePollsTask.kt @@ -132,7 +132,7 @@ internal class DefaultLoadMorePollsTask @Inject constructor( status.oldestTimestampTargetReachedMs = oldestEventTimestamp } - if(oldestEventId != null) { + if (oldestEventId != null) { // save it for next backward pagination status.oldestEventIdReached = oldestEventId } diff --git a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactoryTest.kt b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactoryTest.kt index 99c6c69849..512f7c8a17 100644 --- a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactoryTest.kt +++ b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactoryTest.kt @@ -17,12 +17,8 @@ package im.vector.app.features.home.room.detail.timeline.factory import im.vector.app.R -import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState -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.ReactionsSummaryData -import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout import im.vector.app.features.poll.PollViewState import im.vector.app.test.fakes.FakeStringProvider import im.vector.app.test.fixtures.PollFixture.A_MESSAGE_INFORMATION_DATA @@ -34,10 +30,6 @@ import io.mockk.mockk import io.mockk.verify import org.amshove.kluent.shouldBeEqualTo import org.junit.Test -import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent -import org.matrix.android.sdk.api.session.room.model.message.PollAnswer -import org.matrix.android.sdk.api.session.room.model.message.PollCreationInfo -import org.matrix.android.sdk.api.session.room.model.message.PollQuestion import org.matrix.android.sdk.api.session.room.model.message.PollType import org.matrix.android.sdk.api.session.room.send.SendState From fc26d61305849a33ee8f9a40e758d6edee06b02e Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu, 26 Jan 2023 10:20:51 +0100 Subject: [PATCH 053/189] Removing a debug log --- .../sdk/internal/session/room/poll/DefaultPollHistoryService.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt index 51c8b83191..28a857e6fa 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/DefaultPollHistoryService.kt @@ -38,7 +38,6 @@ import org.matrix.android.sdk.internal.database.model.TimelineEventEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.util.time.Clock -import timber.log.Timber private const val LOADING_PERIOD_IN_DAYS = 30 private const val EVENTS_PAGE_SIZE = 250 @@ -115,7 +114,6 @@ internal class DefaultPollHistoryService @AssistedInject constructor( return Transformations.switchMap(pollHistoryStatusLiveData) { results -> val oldestTimestamp = results.firstOrNull()?.oldestTimestampTargetReachedMs ?: clock.epochMillis() - Timber.d("oldestTimestamp=$oldestTimestamp") getPollStartEventsAfter(oldestTimestamp) } } From c7d3e1926f71c79f127a0fecb0fe39229343bded Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu, 26 Jan 2023 10:33:42 +0100 Subject: [PATCH 054/189] Renaming API field and adding more doc to make things clearer --- .../api/session/room/poll/LoadedPollsStatus.kt | 15 ++++++++++++++- .../api/session/room/poll/PollHistoryService.kt | 3 +++ .../session/room/poll/GetLoadedPollsStatusTask.kt | 2 +- .../session/room/poll/LoadMorePollsTask.kt | 2 +- .../roomprofile/polls/RoomPollsViewModel.kt | 4 ++-- .../roomprofile/polls/RoomPollsViewModelTest.kt | 8 ++++---- .../polls/list/data/RoomPollDataSourceTest.kt | 2 +- .../polls/list/data/RoomPollRepositoryTest.kt | 2 +- .../domain/GetLoadedPollsStatusUseCaseTest.kt | 2 +- .../polls/list/domain/LoadMorePollsUseCaseTest.kt | 2 +- .../polls/list/domain/SyncPollsUseCaseTest.kt | 6 +++--- 11 files changed, 32 insertions(+), 16 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/poll/LoadedPollsStatus.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/poll/LoadedPollsStatus.kt index 003e03178b..02a7667ebf 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/poll/LoadedPollsStatus.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/poll/LoadedPollsStatus.kt @@ -20,7 +20,20 @@ package org.matrix.android.sdk.api.session.room.poll * Represent the status of the loaded polls for a room. */ data class LoadedPollsStatus( + /** + * Indicate whether more polls can be loaded from timeline. + * A false value would mean the start of the timeline has been reached. + */ val canLoadMore: Boolean, - val nbSyncedDays: Int, + + /** + * Number of days of timeline events currently synced (fetched and stored in local). + */ + val daysSynced: Int, + + /** + * Indicate whether a sync of timeline events has been completely done in backward. It would + * mean timeline events have been synced for at least a number of days defined by [PollHistoryService.loadingPeriodInDays]. + */ val hasCompletedASyncBackward: Boolean, ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/poll/PollHistoryService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/poll/PollHistoryService.kt index b62f5a1969..62706af86a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/poll/PollHistoryService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/poll/PollHistoryService.kt @@ -24,6 +24,9 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent */ interface PollHistoryService { + /** + * The number of days covered when requesting to load more polls. + */ val loadingPeriodInDays: Int /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/GetLoadedPollsStatusTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/GetLoadedPollsStatusTask.kt index f273d2248a..5bdb52d04c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/GetLoadedPollsStatusTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/GetLoadedPollsStatusTask.kt @@ -43,7 +43,7 @@ internal class DefaultGetLoadedPollsStatusTask @Inject constructor( .copy() LoadedPollsStatus( canLoadMore = status.isEndOfPollsBackward.not(), - nbSyncedDays = status.getNbSyncedDays(params.currentTimestampMs), + daysSynced = status.getNbSyncedDays(params.currentTimestampMs), hasCompletedASyncBackward = status.hasCompletedASyncBackward, ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/LoadMorePollsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/LoadMorePollsTask.kt index 248d1e5ee3..50dbeb763e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/LoadMorePollsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/LoadMorePollsTask.kt @@ -53,7 +53,7 @@ internal class DefaultLoadMorePollsTask @Inject constructor( return LoadedPollsStatus( canLoadMore = currentPollHistoryStatus.isEndOfPollsBackward.not(), - nbSyncedDays = currentPollHistoryStatus.getNbSyncedDays(params.currentTimestampMs), + daysSynced = currentPollHistoryStatus.getNbSyncedDays(params.currentTimestampMs), hasCompletedASyncBackward = currentPollHistoryStatus.hasCompletedASyncBackward, ) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt index 488660f7d3..2beda47816 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt @@ -68,7 +68,7 @@ class RoomPollsViewModel @AssistedInject constructor( setState { copy( canLoadMore = loadedPollsStatus.canLoadMore, - nbSyncedDays = loadedPollsStatus.nbSyncedDays, + nbSyncedDays = loadedPollsStatus.daysSynced, ) } } @@ -100,7 +100,7 @@ class RoomPollsViewModel @AssistedInject constructor( setState { copy( canLoadMore = status.canLoadMore, - nbSyncedDays = status.nbSyncedDays, + nbSyncedDays = status.daysSynced, ) } } diff --git a/vector/src/test/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModelTest.kt b/vector/src/test/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModelTest.kt index 084df03e64..20471637e6 100644 --- a/vector/src/test/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModelTest.kt @@ -75,7 +75,7 @@ class RoomPollsViewModelTest { val expectedViewState = initialState.copy( polls = listOf(aPollSummary), canLoadMore = loadedPollsStatus.canLoadMore, - nbSyncedDays = loadedPollsStatus.nbSyncedDays, + nbSyncedDays = loadedPollsStatus.daysSynced, ) // When @@ -135,7 +135,7 @@ class RoomPollsViewModelTest { val stateAfterInit = initialState.copy( polls = emptyList(), canLoadMore = loadedPollsStatus.canLoadMore, - nbSyncedDays = loadedPollsStatus.nbSyncedDays, + nbSyncedDays = loadedPollsStatus.daysSynced, ) // When @@ -147,7 +147,7 @@ class RoomPollsViewModelTest { .assertStatesChanges( stateAfterInit, { copy(isLoadingMore = true) }, - { copy(canLoadMore = newLoadedPollsStatus.canLoadMore, nbSyncedDays = newLoadedPollsStatus.nbSyncedDays) }, + { copy(canLoadMore = newLoadedPollsStatus.canLoadMore, nbSyncedDays = newLoadedPollsStatus.daysSynced) }, { copy(isLoadingMore = false) }, ) .finish() @@ -181,7 +181,7 @@ class RoomPollsViewModelTest { private fun givenALoadedPollsStatus(canLoadMore: Boolean = true, nbSyncedDays: Int = 10) = LoadedPollsStatus( canLoadMore = canLoadMore, - nbSyncedDays = nbSyncedDays, + daysSynced = nbSyncedDays, hasCompletedASyncBackward = false, ) } diff --git a/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollDataSourceTest.kt b/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollDataSourceTest.kt index 11006b10e8..89fde7b9df 100644 --- a/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollDataSourceTest.kt +++ b/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollDataSourceTest.kt @@ -124,7 +124,7 @@ internal class RoomPollDataSourceTest { private fun givenALoadedPollsStatus() = LoadedPollsStatus( canLoadMore = true, - nbSyncedDays = 10, + daysSynced = 10, hasCompletedASyncBackward = true, ) } diff --git a/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollRepositoryTest.kt b/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollRepositoryTest.kt index e57b52a812..f27335b844 100644 --- a/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollRepositoryTest.kt +++ b/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/data/RoomPollRepositoryTest.kt @@ -72,7 +72,7 @@ class RoomPollRepositoryTest { // Given val expectedStatus = LoadedPollsStatus( canLoadMore = true, - nbSyncedDays = 10, + daysSynced = 10, hasCompletedASyncBackward = false, ) coEvery { fakeRoomPollDataSource.getLoadedPollsStatus(A_ROOM_ID) } returns expectedStatus diff --git a/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/domain/GetLoadedPollsStatusUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/domain/GetLoadedPollsStatusUseCaseTest.kt index eeaf7803a6..2b3d731b3b 100644 --- a/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/domain/GetLoadedPollsStatusUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/domain/GetLoadedPollsStatusUseCaseTest.kt @@ -39,7 +39,7 @@ class GetLoadedPollsStatusUseCaseTest { val aRoomId = "roomId" val expectedStatus = LoadedPollsStatus( canLoadMore = true, - nbSyncedDays = 10, + daysSynced = 10, hasCompletedASyncBackward = true, ) coEvery { fakeRoomPollRepository.getLoadedPollsStatus(aRoomId) } returns expectedStatus diff --git a/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/domain/LoadMorePollsUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/domain/LoadMorePollsUseCaseTest.kt index d2f63c2a37..c1ae0a3a3f 100644 --- a/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/domain/LoadMorePollsUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/domain/LoadMorePollsUseCaseTest.kt @@ -39,7 +39,7 @@ class LoadMorePollsUseCaseTest { val aRoomId = "roomId" val loadedPollsStatus = LoadedPollsStatus( canLoadMore = true, - nbSyncedDays = 10, + daysSynced = 10, hasCompletedASyncBackward = true, ) coEvery { fakeRoomPollRepository.loadMorePolls(aRoomId) } returns loadedPollsStatus diff --git a/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/domain/SyncPollsUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/domain/SyncPollsUseCaseTest.kt index e60214dde7..9dee8e6170 100644 --- a/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/domain/SyncPollsUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/roomprofile/polls/list/domain/SyncPollsUseCaseTest.kt @@ -45,7 +45,7 @@ class SyncPollsUseCaseTest { val aRoomId = "roomId" val aLoadedStatus = LoadedPollsStatus( canLoadMore = true, - nbSyncedDays = 10, + daysSynced = 10, hasCompletedASyncBackward = true, ) coJustRun { fakeRoomPollRepository.syncPolls(aRoomId) } @@ -71,12 +71,12 @@ class SyncPollsUseCaseTest { val aRoomId = "roomId" val aLoadedStatus = LoadedPollsStatus( canLoadMore = true, - nbSyncedDays = 10, + daysSynced = 10, hasCompletedASyncBackward = false, ) val anUpdatedLoadedStatus = LoadedPollsStatus( canLoadMore = true, - nbSyncedDays = 10, + daysSynced = 10, hasCompletedASyncBackward = true, ) coJustRun { fakeRoomPollRepository.syncPolls(aRoomId) } From 030e37655e82c12a51488395674399ccef1a2af9 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu, 26 Jan 2023 13:50:46 +0100 Subject: [PATCH 055/189] Fixing unit tests in SDK --- .../room/event/FilterAndStoreEventsTask.kt | 1 - .../DefaultFilterAndStoreEventsTaskTest.kt | 133 ++++++++++++++++++ .../DefaultFetchPollResponseEventsTaskTest.kt | 58 +++----- 3 files changed, 150 insertions(+), 42 deletions(-) create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/event/DefaultFilterAndStoreEventsTaskTest.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/event/FilterAndStoreEventsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/event/FilterAndStoreEventsTask.kt index eb7364abd3..e6e169b9b4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/event/FilterAndStoreEventsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/event/FilterAndStoreEventsTask.kt @@ -58,7 +58,6 @@ internal class DefaultFilterAndStoreEventsTask @Inject constructor( private suspend fun addMissingEventsInDB(roomId: String, events: List) { monarchy.awaitTransaction { realm -> - // TODO we should insert TimelineEventEntity as well, how to do that???? val eventIdsToCheck = events.mapNotNull { it.eventId }.filter { it.isNotEmpty() } if (eventIdsToCheck.isNotEmpty()) { val existingIds = EventEntity.where(realm, eventIdsToCheck).findAll().toList().map { it.eventId } diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/event/DefaultFilterAndStoreEventsTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/event/DefaultFilterAndStoreEventsTaskTest.kt new file mode 100644 index 0000000000..1b45430fd1 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/event/DefaultFilterAndStoreEventsTaskTest.kt @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2023 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.event + +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.unmockkAll +import io.mockk.verify +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +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.RelationType +import org.matrix.android.sdk.api.session.events.model.isPollResponse +import org.matrix.android.sdk.api.session.room.send.SendState +import org.matrix.android.sdk.internal.database.mapper.toEntity +import org.matrix.android.sdk.internal.database.model.EventEntity +import org.matrix.android.sdk.internal.database.model.EventEntityFields +import org.matrix.android.sdk.internal.database.model.EventInsertType +import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore +import org.matrix.android.sdk.internal.session.room.relation.RelationsResponse +import org.matrix.android.sdk.internal.session.room.relation.poll.FETCH_RELATED_EVENTS_LIMIT +import org.matrix.android.sdk.internal.session.room.relation.poll.FetchPollResponseEventsTask +import org.matrix.android.sdk.test.fakes.FakeClock +import org.matrix.android.sdk.test.fakes.FakeEventDecryptor +import org.matrix.android.sdk.test.fakes.FakeMonarchy +import org.matrix.android.sdk.test.fakes.givenFindAll +import org.matrix.android.sdk.test.fakes.givenIn + +@OptIn(ExperimentalCoroutinesApi::class) +internal class DefaultFilterAndStoreEventsTaskTest { + + private val fakeMonarchy = FakeMonarchy() + private val fakeClock = FakeClock() + private val fakeEventDecryptor = FakeEventDecryptor() + + private val defaultFilterAndStoreEventsTask = DefaultFilterAndStoreEventsTask( + monarchy = fakeMonarchy.instance, + clock = fakeClock, + eventDecryptor = fakeEventDecryptor.instance, + ) + + @Before + fun setup() { + mockkStatic("org.matrix.android.sdk.api.session.events.model.EventKt") + mockkStatic("org.matrix.android.sdk.internal.database.mapper.EventMapperKt") + mockkStatic("org.matrix.android.sdk.internal.database.query.EventEntityQueriesKt") + } + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun `given a room and list of events when execute then filter in using given predicate and store them in local if needed`() = runTest { + // Given + val aRoomId = "roomId" + val anEventId1 = "eventId1" + val anEventId2 = "eventId2" + val anEventId3 = "eventId3" + val anEventId4 = "eventId4" + val event1 = givenAnEvent(eventId = anEventId1, isEncrypted = true, clearType = EventType.ENCRYPTED) + val event2 = givenAnEvent(eventId = anEventId2, isEncrypted = true, clearType = EventType.MESSAGE) + val event3 = givenAnEvent(eventId = anEventId3, isEncrypted = false, clearType = EventType.MESSAGE) + val event4 = givenAnEvent(eventId = anEventId4, isEncrypted = false, clearType = EventType.MESSAGE) + val events = listOf(event1, event2, event3, event4) + val filterPredicate = { event: Event -> event == event2 } + val params = givenTaskParams(roomId = aRoomId, events = events, predicate = filterPredicate) + fakeEventDecryptor.givenDecryptEventAndSaveResultSuccess(event1) + fakeEventDecryptor.givenDecryptEventAndSaveResultSuccess(event2) + fakeClock.givenEpoch(123) + givenExistingEventEntities(eventIdsToCheck = listOf(anEventId1, anEventId2), existingIds = listOf(anEventId1)) + val eventEntityToSave = EventEntity(eventId = anEventId2) + every { event2.toEntity(any(), any(), any()) } returns eventEntityToSave + every { eventEntityToSave.copyToRealmOrIgnore(any(), any()) } returns eventEntityToSave + + // When + defaultFilterAndStoreEventsTask.execute(params) + + // Then + fakeEventDecryptor.verifyDecryptEventAndSaveResult(event1, timeline = "") + fakeEventDecryptor.verifyDecryptEventAndSaveResult(event2, timeline = "") + // Check we save in DB the event2 which is a non stored poll response + verify { + event2.toEntity(aRoomId, SendState.SYNCED, any()) + eventEntityToSave.copyToRealmOrIgnore(fakeMonarchy.fakeRealm.instance, EventInsertType.PAGINATION) + } + } + + private fun givenTaskParams(roomId: String, events: List, predicate: (Event) -> Boolean) = FilterAndStoreEventsTask.Params( + roomId = roomId, + events = events, + filterPredicate = predicate, + ) + + private fun givenAnEvent( + eventId: String, + isEncrypted: Boolean, + clearType: String, + ): Event { + val event = mockk(relaxed = true) + every { event.eventId } returns eventId + every { event.isEncrypted() } returns isEncrypted + every { event.getClearType() } returns clearType + return event + } + + private fun givenExistingEventEntities(eventIdsToCheck: List, existingIds: List) { + val eventEntities = existingIds.map { EventEntity(eventId = it) } + fakeMonarchy.givenWhere() + .givenIn(EventEntityFields.EVENT_ID, eventIdsToCheck) + .givenFindAll(eventEntities) + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/relation/poll/DefaultFetchPollResponseEventsTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/relation/poll/DefaultFetchPollResponseEventsTaskTest.kt index 8d50bac38f..238a4fa626 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/relation/poll/DefaultFetchPollResponseEventsTaskTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/relation/poll/DefaultFetchPollResponseEventsTaskTest.kt @@ -16,11 +16,12 @@ package org.matrix.android.sdk.internal.session.room.relation.poll +import io.mockk.coJustRun +import io.mockk.coVerify import io.mockk.every import io.mockk.mockk import io.mockk.mockkStatic import io.mockk.unmockkAll -import io.mockk.verify import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.After @@ -29,41 +30,28 @@ import org.junit.Test import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.RelationType import org.matrix.android.sdk.api.session.events.model.isPollResponse -import org.matrix.android.sdk.api.session.room.send.SendState -import org.matrix.android.sdk.internal.database.mapper.toEntity -import org.matrix.android.sdk.internal.database.model.EventEntity -import org.matrix.android.sdk.internal.database.model.EventEntityFields -import org.matrix.android.sdk.internal.database.model.EventInsertType -import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore +import org.matrix.android.sdk.internal.session.room.event.FilterAndStoreEventsTask import org.matrix.android.sdk.internal.session.room.relation.RelationsResponse -import org.matrix.android.sdk.test.fakes.FakeClock -import org.matrix.android.sdk.test.fakes.FakeEventDecryptor import org.matrix.android.sdk.test.fakes.FakeGlobalErrorReceiver -import org.matrix.android.sdk.test.fakes.FakeMonarchy import org.matrix.android.sdk.test.fakes.FakeRoomApi -import org.matrix.android.sdk.test.fakes.givenFindAll -import org.matrix.android.sdk.test.fakes.givenIn @OptIn(ExperimentalCoroutinesApi::class) internal class DefaultFetchPollResponseEventsTaskTest { private val fakeRoomAPI = FakeRoomApi() private val fakeGlobalErrorReceiver = FakeGlobalErrorReceiver() - private val fakeMonarchy = FakeMonarchy() - private val fakeClock = FakeClock() - private val fakeEventDecryptor = FakeEventDecryptor() + private val filterAndStoreEventsTask = mockk() private val defaultFetchPollResponseEventsTask = DefaultFetchPollResponseEventsTask( roomAPI = fakeRoomAPI.instance, globalErrorReceiver = fakeGlobalErrorReceiver, - monarchy = fakeMonarchy.instance, - clock = fakeClock, - eventDecryptor = fakeEventDecryptor.instance, + filterAndStoreEventsTask = filterAndStoreEventsTask, ) @Before fun setup() { mockkStatic("org.matrix.android.sdk.api.session.events.model.EventKt") + mockkStatic("org.matrix.android.sdk.internal.database.mapper.EventMapperKt") mockkStatic("org.matrix.android.sdk.internal.database.query.EventEntityQueriesKt") } @@ -74,7 +62,7 @@ internal class DefaultFetchPollResponseEventsTaskTest { } @Test - fun `given a room and a poll when execute then fetch related events and store them in local if needed`() = runTest { + fun `given a room and a poll when execute then fetch related events and store them in local`() = runTest { // Given val aRoomId = "roomId" val aPollEventId = "eventId" @@ -94,13 +82,7 @@ internal class DefaultFetchPollResponseEventsTaskTest { fakeRoomAPI.givenGetRelationsReturns(from = null, relationsResponse = firstResponse) val secondResponse = givenARelationsResponse(events = secondEvents, nextBatch = null) fakeRoomAPI.givenGetRelationsReturns(from = aNextBatchToken, relationsResponse = secondResponse) - fakeEventDecryptor.givenDecryptEventAndSaveResultSuccess(event1) - fakeEventDecryptor.givenDecryptEventAndSaveResultSuccess(event2) - fakeClock.givenEpoch(123) - givenExistingEventEntities(eventIdsToCheck = listOf(anEventId1, anEventId2), existingIds = listOf(anEventId1)) - val eventEntityToSave = EventEntity(eventId = anEventId2) - every { event2.toEntity(any(), any(), any()) } returns eventEntityToSave - every { eventEntityToSave.copyToRealmOrIgnore(any(), any()) } returns eventEntityToSave + coJustRun { filterAndStoreEventsTask.execute(any()) } // When defaultFetchPollResponseEventsTask.execute(params) @@ -111,21 +93,22 @@ internal class DefaultFetchPollResponseEventsTaskTest { eventId = params.startPollEventId, relationType = RelationType.REFERENCE, from = null, - limit = FETCH_RELATED_EVENTS_LIMIT + limit = FETCH_RELATED_EVENTS_LIMIT, ) fakeRoomAPI.verifyGetRelations( roomId = params.roomId, eventId = params.startPollEventId, relationType = RelationType.REFERENCE, from = aNextBatchToken, - limit = FETCH_RELATED_EVENTS_LIMIT + limit = FETCH_RELATED_EVENTS_LIMIT, ) - fakeEventDecryptor.verifyDecryptEventAndSaveResult(event1, timeline = "") - fakeEventDecryptor.verifyDecryptEventAndSaveResult(event2, timeline = "") - // Check we save in DB the event2 which is a non stored poll response - verify { - event2.toEntity(aRoomId, SendState.SYNCED, any()) - eventEntityToSave.copyToRealmOrIgnore(fakeMonarchy.fakeRealm.instance, EventInsertType.PAGINATION) + coVerify { + filterAndStoreEventsTask.execute(match { + it.roomId == aRoomId && it.events == firstEvents + }) + filterAndStoreEventsTask.execute(match { + it.roomId == aRoomId && it.events == secondEvents + }) } } @@ -153,11 +136,4 @@ internal class DefaultFetchPollResponseEventsTaskTest { every { event.isEncrypted() } returns isEncrypted return event } - - private fun givenExistingEventEntities(eventIdsToCheck: List, existingIds: List) { - val eventEntities = existingIds.map { EventEntity(eventId = it) } - fakeMonarchy.givenWhere() - .givenIn(EventEntityFields.EVENT_ID, eventIdsToCheck) - .givenFindAll(eventEntities) - } } From 3045a8581ad645f62c267a44d8a0be1e79248ff9 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu, 26 Jan 2023 13:58:30 +0100 Subject: [PATCH 056/189] Adding unit tests for DefaultGetLoadedPollsStatusTaskTest --- .../DefaultGetLoadedPollsStatusTaskTest.kt | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/poll/DefaultGetLoadedPollsStatusTaskTest.kt diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/poll/DefaultGetLoadedPollsStatusTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/poll/DefaultGetLoadedPollsStatusTaskTest.kt new file mode 100644 index 0000000000..2f58973eca --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/poll/DefaultGetLoadedPollsStatusTaskTest.kt @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2023 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.poll + +import io.mockk.unmockkAll +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.amshove.kluent.shouldBeEqualTo +import org.junit.After +import org.junit.Test +import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus +import org.matrix.android.sdk.internal.database.model.PollHistoryStatusEntity +import org.matrix.android.sdk.internal.database.model.PollHistoryStatusEntityFields +import org.matrix.android.sdk.test.fakes.FakeMonarchy +import org.matrix.android.sdk.test.fakes.givenEqualTo +import org.matrix.android.sdk.test.fakes.givenFindFirst + +private const val A_ROOM_ID = "room-id" +/** + * 2023/01/26 + */ +private const val A_TIMESTAMP = 1674737619290L + +@OptIn(ExperimentalCoroutinesApi::class) +internal class DefaultGetLoadedPollsStatusTaskTest { + + private val fakeMonarchy = FakeMonarchy() + + private val defaultGetLoadedPollsStatusTask = DefaultGetLoadedPollsStatusTask( + monarchy = fakeMonarchy.instance, + ) + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun `given poll history status exists in db with an oldestTimestamp reached when execute then the computed status is returned`() = runTest { + // Given + val params = givenTaskParams() + // 2023/01/20 + val oldestTimestampReached = 1674169200000 + val pollHistoryStatus = aPollHistoryStatusEntity( + isEndOfPollsBackward = false, + oldestTimestampReached = oldestTimestampReached, + ) + fakeMonarchy.fakeRealm + .givenWhere() + .givenEqualTo(PollHistoryStatusEntityFields.ROOM_ID, A_ROOM_ID) + .givenFindFirst(pollHistoryStatus) + val expectedStatus = LoadedPollsStatus( + canLoadMore = true, + daysSynced = 6, + hasCompletedASyncBackward = true, + ) + + // When + val result = defaultGetLoadedPollsStatusTask.execute(params) + + // Then + result shouldBeEqualTo expectedStatus + } + + @Test + fun `given poll history status exists in db and no oldestTimestamp reached when execute then the computed status is returned`() = runTest { + // Given + val params = givenTaskParams() + val pollHistoryStatus = aPollHistoryStatusEntity( + isEndOfPollsBackward = false, + oldestTimestampReached = null, + ) + fakeMonarchy.fakeRealm + .givenWhere() + .givenEqualTo(PollHistoryStatusEntityFields.ROOM_ID, A_ROOM_ID) + .givenFindFirst(pollHistoryStatus) + val expectedStatus = LoadedPollsStatus( + canLoadMore = true, + daysSynced = 0, + hasCompletedASyncBackward = false, + ) + + // When + val result = defaultGetLoadedPollsStatusTask.execute(params) + + // Then + result shouldBeEqualTo expectedStatus + } + + private fun givenTaskParams(): GetLoadedPollsStatusTask.Params { + return GetLoadedPollsStatusTask.Params( + roomId = A_ROOM_ID, + currentTimestampMs = A_TIMESTAMP, + ) + } + + private fun aPollHistoryStatusEntity( + isEndOfPollsBackward: Boolean, + oldestTimestampReached: Long?, + ): PollHistoryStatusEntity { + return PollHistoryStatusEntity( + roomId = A_ROOM_ID, + isEndOfPollsBackward = isEndOfPollsBackward, + oldestTimestampTargetReachedMs = oldestTimestampReached, + ) + } +} From 41825812352f6cf07acabab983c65205dc095698 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu, 26 Jan 2023 14:51:16 +0100 Subject: [PATCH 057/189] Adding unit tests for SyncPollsTask --- .../session/room/poll/SyncPollsTask.kt | 9 +- .../room/poll/DefaultSyncPollsTaskTest.kt | 129 ++++++++++++++++++ .../android/sdk/test/fakes/FakeTimeline.kt | 40 ++++++ 3 files changed, 173 insertions(+), 5 deletions(-) create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/poll/DefaultSyncPollsTaskTest.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeTimeline.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/SyncPollsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/SyncPollsTask.kt index 4a83f0f870..fff24288b4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/SyncPollsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/SyncPollsTask.kt @@ -84,18 +84,17 @@ internal class DefaultSyncPollsTask @Inject constructor( ): LoadStatus { return monarchy.awaitTransaction { realm -> val status = PollHistoryStatusEntity.getOrCreate(realm, roomId) - val mostRecentEventIdReached = status.mostRecentEventIdReached - val mostRecentEvent = events .maxByOrNull { it.root.originServerTs ?: Long.MIN_VALUE } ?.root + val mostRecentEventIdReached = mostRecentEvent?.eventId - if (mostRecentEventIdReached == null) { + if (mostRecentEventIdReached != null) { // save it for next forward pagination - status.mostRecentEventIdReached = mostRecentEvent?.eventId + status.mostRecentEventIdReached = mostRecentEventIdReached } - val mostRecentTimestamp = mostRecentEvent?.ageLocalTs + val mostRecentTimestamp = mostRecentEvent?.originServerTs val shouldLoadMore = paginationState.hasMoreToLoad && (mostRecentTimestamp == null || mostRecentTimestamp < currentTimestampMs) diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/poll/DefaultSyncPollsTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/poll/DefaultSyncPollsTaskTest.kt new file mode 100644 index 0000000000..8a95a2f131 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/poll/DefaultSyncPollsTaskTest.kt @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2023 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.poll + +import io.mockk.coVerifyOrder +import io.mockk.every +import io.mockk.mockk +import io.mockk.unmockkAll +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.amshove.kluent.shouldBeEqualTo +import org.junit.After +import org.junit.Test +import org.matrix.android.sdk.api.session.room.timeline.Timeline +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import org.matrix.android.sdk.internal.database.model.PollHistoryStatusEntity +import org.matrix.android.sdk.internal.database.model.PollHistoryStatusEntityFields +import org.matrix.android.sdk.test.fakes.FakeMonarchy +import org.matrix.android.sdk.test.fakes.FakeTimeline +import org.matrix.android.sdk.test.fakes.givenEqualTo +import org.matrix.android.sdk.test.fakes.givenFindFirst + +private const val A_ROOM_ID = "room-id" +private const val A_TIMESTAMP = 123L +private const val A_PAGE_SIZE = 200 + +@OptIn(ExperimentalCoroutinesApi::class) +internal class DefaultSyncPollsTaskTest { + + private val fakeMonarchy = FakeMonarchy() + private val fakeTimeline = FakeTimeline() + + private val defaultSyncPollsTask = DefaultSyncPollsTask( + monarchy = fakeMonarchy.instance, + ) + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun `given timeline when execute then more events are fetched in forward direction after the most recent event id reached`() = runTest { + // Given + val params = givenTaskParams() + val mostRecentEventId = "most-recent" + val oldestEventId = "oldest" + val pollHistoryStatus = aPollHistoryStatusEntity( + mostRecentEventIdReached = mostRecentEventId, + oldestEventIdReached = oldestEventId, + ) + fakeMonarchy.fakeRealm + .givenWhere() + .givenEqualTo(PollHistoryStatusEntityFields.ROOM_ID, A_ROOM_ID) + .givenFindFirst(pollHistoryStatus) + fakeTimeline.givenRestartWithEventIdSuccess(mostRecentEventId) + fakeTimeline.givenRestartWithEventIdSuccess(oldestEventId) + val anEventId = "event-id" + val aTimelineEvent = aTimelineEvent(anEventId) + fakeTimeline.givenAwaitPaginateReturns( + events = listOf(aTimelineEvent), + direction = Timeline.Direction.FORWARDS, + count = params.eventsPageSize, + ) + fakeTimeline.givenGetPaginationStateReturns( + paginationState = aPaginationState(), + direction = Timeline.Direction.FORWARDS, + ) + + // When + defaultSyncPollsTask.execute(params) + + // Then + coVerifyOrder { + fakeTimeline.instance.restartWithEventId(mostRecentEventId) + fakeTimeline.instance.awaitPaginate(direction = Timeline.Direction.FORWARDS, count = params.eventsPageSize) + fakeTimeline.instance.getPaginationState(direction = Timeline.Direction.FORWARDS) + fakeTimeline.instance.restartWithEventId(oldestEventId) + } + pollHistoryStatus.mostRecentEventIdReached shouldBeEqualTo anEventId + } + + private fun givenTaskParams(): SyncPollsTask.Params { + return SyncPollsTask.Params( + timeline = fakeTimeline.instance, + roomId = A_ROOM_ID, + currentTimestampMs = A_TIMESTAMP, + eventsPageSize = A_PAGE_SIZE, + ) + } + + private fun aPollHistoryStatusEntity( + mostRecentEventIdReached: String, + oldestEventIdReached: String, + ): PollHistoryStatusEntity { + return PollHistoryStatusEntity( + roomId = A_ROOM_ID, + mostRecentEventIdReached = mostRecentEventIdReached, + oldestEventIdReached = oldestEventIdReached, + ) + } + + private fun aTimelineEvent(eventId: String): TimelineEvent { + val event = mockk() + every { event.root.originServerTs } returns 123L + every { event.root.eventId } returns eventId + return event + } + + private fun aPaginationState(): Timeline.PaginationState { + return Timeline.PaginationState( + hasMoreToLoad = false, + ) + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeTimeline.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeTimeline.kt new file mode 100644 index 0000000000..68b80c7e8f --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeTimeline.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2023 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.test.fakes + +import io.mockk.coEvery +import io.mockk.every +import io.mockk.justRun +import io.mockk.mockk +import org.matrix.android.sdk.api.session.room.timeline.Timeline +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent + +class FakeTimeline { + val instance: Timeline = mockk() + + fun givenRestartWithEventIdSuccess(eventId: String) { + justRun { instance.restartWithEventId(eventId) } + } + + fun givenAwaitPaginateReturns(events: List, direction: Timeline.Direction, count: Int) { + coEvery { instance.awaitPaginate(direction, count) } returns events + } + + fun givenGetPaginationStateReturns(paginationState: Timeline.PaginationState, direction: Timeline.Direction) { + every { instance.getPaginationState(direction) } returns paginationState + } +} From b6f77ac578ef8d2c453f0c33b367d67df1fa0a2c Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu, 26 Jan 2023 15:38:11 +0100 Subject: [PATCH 058/189] Adding unit tests for LoadMorePollsTask --- .../DefaultFilterAndStoreEventsTaskTest.kt | 5 - .../DefaultGetLoadedPollsStatusTaskTest.kt | 16 +- .../room/poll/DefaultLoadMorePollsTaskTest.kt | 192 ++++++++++++++++++ 3 files changed, 202 insertions(+), 11 deletions(-) create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/poll/DefaultLoadMorePollsTaskTest.kt diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/event/DefaultFilterAndStoreEventsTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/event/DefaultFilterAndStoreEventsTaskTest.kt index 1b45430fd1..81e43c7c03 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/event/DefaultFilterAndStoreEventsTaskTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/event/DefaultFilterAndStoreEventsTaskTest.kt @@ -28,17 +28,12 @@ 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.events.model.isPollResponse import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.internal.database.mapper.toEntity import org.matrix.android.sdk.internal.database.model.EventEntity import org.matrix.android.sdk.internal.database.model.EventEntityFields import org.matrix.android.sdk.internal.database.model.EventInsertType import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore -import org.matrix.android.sdk.internal.session.room.relation.RelationsResponse -import org.matrix.android.sdk.internal.session.room.relation.poll.FETCH_RELATED_EVENTS_LIMIT -import org.matrix.android.sdk.internal.session.room.relation.poll.FetchPollResponseEventsTask import org.matrix.android.sdk.test.fakes.FakeClock import org.matrix.android.sdk.test.fakes.FakeEventDecryptor import org.matrix.android.sdk.test.fakes.FakeMonarchy diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/poll/DefaultGetLoadedPollsStatusTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/poll/DefaultGetLoadedPollsStatusTaskTest.kt index 2f58973eca..9c3093897d 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/poll/DefaultGetLoadedPollsStatusTaskTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/poll/DefaultGetLoadedPollsStatusTaskTest.kt @@ -30,10 +30,16 @@ import org.matrix.android.sdk.test.fakes.givenEqualTo import org.matrix.android.sdk.test.fakes.givenFindFirst private const val A_ROOM_ID = "room-id" + /** - * 2023/01/26 + * Timestamp in milliseconds corresponding to 2023/01/26. */ -private const val A_TIMESTAMP = 1674737619290L +private const val A_CURRENT_TIMESTAMP = 1674737619290L + +/** + * Timestamp in milliseconds corresponding to 2023/01/20. + */ +private const val AN_EVENT_TIMESTAMP = 1674169200000L @OptIn(ExperimentalCoroutinesApi::class) internal class DefaultGetLoadedPollsStatusTaskTest { @@ -53,11 +59,9 @@ internal class DefaultGetLoadedPollsStatusTaskTest { fun `given poll history status exists in db with an oldestTimestamp reached when execute then the computed status is returned`() = runTest { // Given val params = givenTaskParams() - // 2023/01/20 - val oldestTimestampReached = 1674169200000 val pollHistoryStatus = aPollHistoryStatusEntity( isEndOfPollsBackward = false, - oldestTimestampReached = oldestTimestampReached, + oldestTimestampReached = AN_EVENT_TIMESTAMP, ) fakeMonarchy.fakeRealm .givenWhere() @@ -104,7 +108,7 @@ internal class DefaultGetLoadedPollsStatusTaskTest { private fun givenTaskParams(): GetLoadedPollsStatusTask.Params { return GetLoadedPollsStatusTask.Params( roomId = A_ROOM_ID, - currentTimestampMs = A_TIMESTAMP, + currentTimestampMs = A_CURRENT_TIMESTAMP, ) } diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/poll/DefaultLoadMorePollsTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/poll/DefaultLoadMorePollsTaskTest.kt new file mode 100644 index 0000000000..489a32b198 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/poll/DefaultLoadMorePollsTaskTest.kt @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2023 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.poll + +import io.mockk.coVerifyOrder +import io.mockk.every +import io.mockk.mockk +import io.mockk.unmockkAll +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.amshove.kluent.shouldBeEqualTo +import org.junit.After +import org.junit.Test +import org.matrix.android.sdk.api.session.room.poll.LoadedPollsStatus +import org.matrix.android.sdk.api.session.room.timeline.Timeline +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import org.matrix.android.sdk.internal.database.model.PollHistoryStatusEntity +import org.matrix.android.sdk.internal.database.model.PollHistoryStatusEntityFields +import org.matrix.android.sdk.test.fakes.FakeMonarchy +import org.matrix.android.sdk.test.fakes.FakeTimeline +import org.matrix.android.sdk.test.fakes.givenEqualTo +import org.matrix.android.sdk.test.fakes.givenFindFirst + +private const val A_ROOM_ID = "room-id" + +/** + * Timestamp in milliseconds corresponding to 2023/01/26. + */ +private const val A_CURRENT_TIMESTAMP = 1674737619290L + +/** + * Timestamp in milliseconds corresponding to 2023/01/20. + */ +private const val AN_EVENT_TIMESTAMP = 1674169200000L +private const val A_PERIOD_IN_DAYS = 3 +private const val A_PAGE_SIZE = 200 + +@OptIn(ExperimentalCoroutinesApi::class) +internal class DefaultLoadMorePollsTaskTest { + + private val fakeMonarchy = FakeMonarchy() + private val fakeTimeline = FakeTimeline() + + private val defaultLoadMorePollsTask = DefaultLoadMorePollsTask( + monarchy = fakeMonarchy.instance, + ) + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun `given timeline when execute then more events are fetched in backward direction until has no more to load`() = runTest { + // Given + val params = givenTaskParams() + val oldestEventId = "oldest" + val pollHistoryStatus = aPollHistoryStatusEntity( + oldestEventIdReached = oldestEventId, + ) + fakeMonarchy.fakeRealm + .givenWhere() + .givenEqualTo(PollHistoryStatusEntityFields.ROOM_ID, A_ROOM_ID) + .givenFindFirst(pollHistoryStatus) + fakeTimeline.givenRestartWithEventIdSuccess(oldestEventId) + val anEventId = "event-id" + val aTimelineEvent = aTimelineEvent(anEventId, AN_EVENT_TIMESTAMP) + fakeTimeline.givenAwaitPaginateReturns( + events = listOf(aTimelineEvent), + direction = Timeline.Direction.BACKWARDS, + count = params.eventsPageSize, + ) + val aPaginationState = aPaginationState(hasMoreToLoad = false) + fakeTimeline.givenGetPaginationStateReturns( + paginationState = aPaginationState, + direction = Timeline.Direction.BACKWARDS, + ) + val expectedLoadStatus = LoadedPollsStatus( + canLoadMore = false, + daysSynced = 6, + hasCompletedASyncBackward = true, + ) + + // When + val result = defaultLoadMorePollsTask.execute(params) + + // Then + coVerifyOrder { + fakeTimeline.instance.restartWithEventId(oldestEventId) + fakeTimeline.instance.awaitPaginate(direction = Timeline.Direction.BACKWARDS, count = params.eventsPageSize) + fakeTimeline.instance.getPaginationState(direction = Timeline.Direction.BACKWARDS) + } + pollHistoryStatus.mostRecentEventIdReached shouldBeEqualTo anEventId + pollHistoryStatus.oldestEventIdReached shouldBeEqualTo anEventId + pollHistoryStatus.isEndOfPollsBackward shouldBeEqualTo true + pollHistoryStatus.oldestTimestampTargetReachedMs shouldBeEqualTo AN_EVENT_TIMESTAMP + result shouldBeEqualTo expectedLoadStatus + } + + @Test + fun `given timeline when execute then more events are fetched in backward direction until current target is reached`() = runTest { + // Given + val params = givenTaskParams() + val oldestEventId = "oldest" + val pollHistoryStatus = aPollHistoryStatusEntity( + oldestEventIdReached = oldestEventId, + ) + fakeMonarchy.fakeRealm + .givenWhere() + .givenEqualTo(PollHistoryStatusEntityFields.ROOM_ID, A_ROOM_ID) + .givenFindFirst(pollHistoryStatus) + fakeTimeline.givenRestartWithEventIdSuccess(oldestEventId) + val anEventId = "event-id" + val aTimelineEvent = aTimelineEvent(anEventId, AN_EVENT_TIMESTAMP) + fakeTimeline.givenAwaitPaginateReturns( + events = listOf(aTimelineEvent), + direction = Timeline.Direction.BACKWARDS, + count = params.eventsPageSize, + ) + val aPaginationState = aPaginationState(hasMoreToLoad = true) + fakeTimeline.givenGetPaginationStateReturns( + paginationState = aPaginationState, + direction = Timeline.Direction.BACKWARDS, + ) + val expectedLoadStatus = LoadedPollsStatus( + canLoadMore = true, + daysSynced = 6, + hasCompletedASyncBackward = true, + ) + + // When + val result = defaultLoadMorePollsTask.execute(params) + + // Then + coVerifyOrder { + fakeTimeline.instance.restartWithEventId(oldestEventId) + fakeTimeline.instance.awaitPaginate(direction = Timeline.Direction.BACKWARDS, count = params.eventsPageSize) + fakeTimeline.instance.getPaginationState(direction = Timeline.Direction.BACKWARDS) + } + pollHistoryStatus.mostRecentEventIdReached shouldBeEqualTo anEventId + pollHistoryStatus.oldestEventIdReached shouldBeEqualTo anEventId + pollHistoryStatus.isEndOfPollsBackward shouldBeEqualTo false + pollHistoryStatus.oldestTimestampTargetReachedMs shouldBeEqualTo AN_EVENT_TIMESTAMP + result shouldBeEqualTo expectedLoadStatus + } + + private fun givenTaskParams(): LoadMorePollsTask.Params { + return LoadMorePollsTask.Params( + timeline = fakeTimeline.instance, + roomId = A_ROOM_ID, + currentTimestampMs = A_CURRENT_TIMESTAMP, + loadingPeriodInDays = A_PERIOD_IN_DAYS, + eventsPageSize = A_PAGE_SIZE, + ) + } + + private fun aPollHistoryStatusEntity( + oldestEventIdReached: String, + ): PollHistoryStatusEntity { + return PollHistoryStatusEntity( + roomId = A_ROOM_ID, + oldestEventIdReached = oldestEventIdReached, + ) + } + + private fun aTimelineEvent(eventId: String, timestamp: Long): TimelineEvent { + val event = mockk() + every { event.root.originServerTs } returns timestamp + every { event.root.eventId } returns eventId + return event + } + + private fun aPaginationState(hasMoreToLoad: Boolean): Timeline.PaginationState { + return Timeline.PaginationState( + hasMoreToLoad = hasMoreToLoad, + ) + } +} From 25a09bc44612e582fd7fe37ca36b4a8df655ade6 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 27 Jan 2023 13:29:00 +0100 Subject: [PATCH 059/189] Add a debug slash command to crash the application from the timeline screen. --- library/ui-strings/src/main/res/values/donottranslate.xml | 2 ++ .../main/java/im/vector/app/features/command/Command.kt | 1 + .../java/im/vector/app/features/command/CommandParser.kt | 8 +++++++- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values/donottranslate.xml b/library/ui-strings/src/main/res/values/donottranslate.xml index bfe751ef5a..910ce31c41 100755 --- a/library/ui-strings/src/main/res/values/donottranslate.xml +++ b/library/ui-strings/src/main/res/values/donottranslate.xml @@ -10,6 +10,8 @@ Cut the slack from teams. + Crash the application. + © MapTiler © OpenStreetMap contributors diff --git a/vector/src/main/java/im/vector/app/features/command/Command.kt b/vector/src/main/java/im/vector/app/features/command/Command.kt index 324029c45b..b112751f68 100644 --- a/vector/src/main/java/im/vector/app/features/command/Command.kt +++ b/vector/src/main/java/im/vector/app/features/command/Command.kt @@ -32,6 +32,7 @@ enum class Command( val isDevCommand: Boolean, val isThreadCommand: Boolean ) { + CRASH_APP("/crash", null, "", R.string.command_description_crash_application, true, true), EMOTE("/me", null, "", R.string.command_description_emote, false, true), BAN_USER("/ban", null, " [reason]", R.string.command_description_ban_user, false, false), UNBAN_USER("/unban", null, " [reason]", R.string.command_description_unban_user, false, false), diff --git a/vector/src/main/java/im/vector/app/features/command/CommandParser.kt b/vector/src/main/java/im/vector/app/features/command/CommandParser.kt index e08bc9fb64..298387c324 100644 --- a/vector/src/main/java/im/vector/app/features/command/CommandParser.kt +++ b/vector/src/main/java/im/vector/app/features/command/CommandParser.kt @@ -20,13 +20,16 @@ import im.vector.app.core.extensions.isEmail import im.vector.app.core.extensions.isMsisdn import im.vector.app.core.extensions.orEmpty import im.vector.app.features.home.room.detail.ChatEffect +import im.vector.app.features.settings.VectorPreferences import org.matrix.android.sdk.api.MatrixPatterns import org.matrix.android.sdk.api.MatrixUrls.isMxcUrl import org.matrix.android.sdk.api.session.identity.ThreePid import timber.log.Timber import javax.inject.Inject -class CommandParser @Inject constructor() { +class CommandParser @Inject constructor( + private val vectorPreferences: VectorPreferences +) { /** * Convert the text message into a Slash command. @@ -404,6 +407,9 @@ class CommandParser @Inject constructor() { ParsedCommand.ErrorSyntax(Command.UPGRADE_ROOM) } } + Command.CRASH_APP.matches(slashCommand) && vectorPreferences.developerMode() -> { + throw RuntimeException("Application crashed from user demand") + } else -> { // Unknown command ParsedCommand.ErrorUnknownSlashCommand(slashCommand) From 8f927a46ca14aaf317fc60d508c1710051659723 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 27 Jan 2023 15:41:23 +0100 Subject: [PATCH 060/189] Fix issue of `Idle` displayed after pausing and resuming the app. --- .../src/main/java/org/matrix/android/sdk/flow/FlowSession.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowSession.kt b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowSession.kt index 80ed311901..64cb0acb2d 100644 --- a/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowSession.kt +++ b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowSession.kt @@ -80,6 +80,9 @@ class FlowSession(private val session: Session) { fun liveSyncState(): Flow { return session.syncService().getSyncStateLive().asFlow() + .startWith(session.coroutineDispatchers.io) { + session.syncService().getSyncState() + } } fun livePushers(): Flow> { From fae17840055a5b57bee0ad926788c25bacd7a591 Mon Sep 17 00:00:00 2001 From: waclaw66 Date: Thu, 26 Jan 2023 19:17:02 +0000 Subject: [PATCH 061/189] Translated using Weblate (Czech) Currently translated at 100.0% (2597 of 2597 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/cs/ --- library/ui-strings/src/main/res/values-cs/strings.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values-cs/strings.xml b/library/ui-strings/src/main/res/values-cs/strings.xml index c9d697f560..c122de7798 100644 --- a/library/ui-strings/src/main/res/values-cs/strings.xml +++ b/library/ui-strings/src/main/res/values-cs/strings.xml @@ -2978,4 +2978,5 @@ Hlasovou zprávu nelze spustit, protoÅŸe právě nahráváte ÅŸivé vysílání. Ukončete prosím ÅŸivé vysílání, abyste mohli začít nahrávat hlasovou zprávu Nelze spustit hlasovou zprávu - + Chyba připojení - nahrávání pozastaveno + \ No newline at end of file From 5f33474ff5c6fac5ece986dd8d3416bc2d7b8d23 Mon Sep 17 00:00:00 2001 From: Vri Date: Wed, 25 Jan 2023 12:21:26 +0000 Subject: [PATCH 062/189] Translated using Weblate (German) Currently translated at 100.0% (2597 of 2597 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/de/ --- library/ui-strings/src/main/res/values-de/strings.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values-de/strings.xml b/library/ui-strings/src/main/res/values-de/strings.xml index f0e5a7bb8d..17fa9b6e44 100644 --- a/library/ui-strings/src/main/res/values-de/strings.xml +++ b/library/ui-strings/src/main/res/values-de/strings.xml @@ -2917,4 +2917,5 @@ Du kannst keine Sprachnachricht beginnen, da du im Moment eine EchtzeitÃŒbertragung aufzeichnest. Bitte beende deine SprachÃŒbertragung, um ein GesprÀch zu beginnen Kann Sprachnachricht nicht beginnen - + Verbindungsfehler − Aufnahme pausiert + \ No newline at end of file From e971e09e2e48be86d9b185a3c8b2f30891da2c7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Wed, 25 Jan 2023 19:01:18 +0000 Subject: [PATCH 063/189] Translated using Weblate (Estonian) Currently translated at 99.6% (2589 of 2597 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/et/ --- library/ui-strings/src/main/res/values-et/strings.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values-et/strings.xml b/library/ui-strings/src/main/res/values-et/strings.xml index 1d7b96d2f9..f33ade2a7e 100644 --- a/library/ui-strings/src/main/res/values-et/strings.xml +++ b/library/ui-strings/src/main/res/values-et/strings.xml @@ -2909,4 +2909,5 @@ Viga kÃŒsitluste laadimisel. HÀÀlsõnumi esitamine ei õnnestu Kuna sa hetkel salvestad ringhÀÀlingukõnet, siis hÀÀlsõnumi salvestamine või esitamine ei õnnestu. Selleks palun lõpeta ringhÀÀlingukõne - + Viga võrguÃŒhenduses - salvestamine on peatatud + \ No newline at end of file From c868452194663f097a04af0a1c165e2f7ac2102d Mon Sep 17 00:00:00 2001 From: Danial Behzadi Date: Wed, 25 Jan 2023 16:03:23 +0000 Subject: [PATCH 064/189] Translated using Weblate (Persian) Currently translated at 100.0% (2597 of 2597 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fa/ --- library/ui-strings/src/main/res/values-fa/strings.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values-fa/strings.xml b/library/ui-strings/src/main/res/values-fa/strings.xml index 1b726a2428..d498f4a51b 100644 --- a/library/ui-strings/src/main/res/values-fa/strings.xml +++ b/library/ui-strings/src/main/res/values-fa/strings.xml @@ -2918,4 +2918,5 @@ از آن‌جا که در حال ضؚط ٟخ؎ی زنده‌اید، نمی‌توانید ٟیامی صوتی را آغاز کنید. لطفاً ؚرای آغاز ضؚط یک ٟیام صوتی، ٟخ؎ زنده‌تان را ٟایان دهید نمی‌توان ٟخ؎ صوتی را آغاز کرد - + خطای اتّصال - ضؚط مکث ؎د + \ No newline at end of file From 5c4ab205f7bc4ae4c663de15c1175d4dc001d97c Mon Sep 17 00:00:00 2001 From: Glandos Date: Thu, 26 Jan 2023 13:04:46 +0000 Subject: [PATCH 065/189] Translated using Weblate (French) Currently translated at 100.0% (2597 of 2597 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fr/ --- library/ui-strings/src/main/res/values-fr/strings.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values-fr/strings.xml b/library/ui-strings/src/main/res/values-fr/strings.xml index e45211b61a..d62d208e43 100644 --- a/library/ui-strings/src/main/res/values-fr/strings.xml +++ b/library/ui-strings/src/main/res/values-fr/strings.xml @@ -2918,4 +2918,5 @@ Vous ne pouvez pas commencer un message vocal car vous êtes en train d’enregistrer une diffusion en direct. Veuillez terminer cette diffusion pour commencer un message vocal Impossible de démarrer un message vocal - + Erreur de connexion – Enregistrement en pause + \ No newline at end of file From 43dcc405d27876f1e99b1b60cb7f3db1ecee1022 Mon Sep 17 00:00:00 2001 From: Szimszon Date: Thu, 26 Jan 2023 10:17:59 +0000 Subject: [PATCH 066/189] Translated using Weblate (Hungarian) Currently translated at 100.0% (2597 of 2597 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/hu/ --- library/ui-strings/src/main/res/values-hu/strings.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values-hu/strings.xml b/library/ui-strings/src/main/res/values-hu/strings.xml index 0aa70cea55..c265b79969 100644 --- a/library/ui-strings/src/main/res/values-hu/strings.xml +++ b/library/ui-strings/src/main/res/values-hu/strings.xml @@ -2918,4 +2918,5 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze Nem lehet hang ÃŒzenetet indítani élő közvetítés felvétele közben. Az élő közvetítés bejezése szÃŒkséges a hang ÃŒzenet indításához Hang ÃŒzenetet nem lehet elindítani - + Kapcsolódási hiba – Felvétel szÃŒneteltetve + \ No newline at end of file From 882357f6a4dbeecccb44a9e4bde4010836a22107 Mon Sep 17 00:00:00 2001 From: Linerly Date: Wed, 25 Jan 2023 13:00:36 +0000 Subject: [PATCH 067/189] Translated using Weblate (Indonesian) Currently translated at 100.0% (2597 of 2597 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/id/ --- library/ui-strings/src/main/res/values-in/strings.xml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values-in/strings.xml b/library/ui-strings/src/main/res/values-in/strings.xml index 4c524df727..8a05481fd5 100644 --- a/library/ui-strings/src/main/res/values-in/strings.xml +++ b/library/ui-strings/src/main/res/values-in/strings.xml @@ -2858,4 +2858,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan. Tidak ada pemungutan suara aktif %1$d hari terakhir. \nMuat lebih banyak pemungutan suara untuk melihat pemungutan suara untuk hari sebelumnya. - + Kesalahan koneksi - Perekaman dijeda + Anda tidak dapat memulai sebuah pesan suara karena Anda saat ini merekam sebuah siaran langsung. Silakan mengakhiri siaran langsung Anda untuk memulai merekam sebuah pesan suara + Tidak dapat memulai pesan suara + \ No newline at end of file From 356f221caad85cba18dcb29d5bdc0363dfe52ad8 Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Wed, 25 Jan 2023 19:23:07 +0000 Subject: [PATCH 068/189] Translated using Weblate (Japanese) Currently translated at 87.4% (2270 of 2597 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/ --- .../src/main/res/values-ja/strings.xml | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/library/ui-strings/src/main/res/values-ja/strings.xml b/library/ui-strings/src/main/res/values-ja/strings.xml index d893156f6e..0b987ce683 100644 --- a/library/ui-strings/src/main/res/values-ja/strings.xml +++ b/library/ui-strings/src/main/res/values-ja/strings.xml @@ -198,7 +198,7 @@ メむンアドレスずしお蚭定 メむンアドレスずしおの蚭定を解陀 セッションID - 文字の倧きさ + フォントの倧きさ ずおも小さい 小さい 暙準 @@ -2391,7 +2391,7 @@ 招埅 プッシュ通知 セッション名 - セッションを改名 + セッション名を倉曎 IPアドレス オペレヌティングシステム 圢匏 @@ -2487,4 +2487,17 @@ %1$dを遞択したした - + 有効にするず、このアプリケヌションを䜿甚しおいる際にも、他のナヌザヌにオフラむンずしお衚瀺されたす。 + 最近のチャットをシステムの共有メニュヌに衚瀺 + システムの既定倀を䜿甚 + 手動で蚭定 + 自動的に蚭定 + フォントの倧きさを遞択 + ⚠ 未認蚌の端末がこのルヌムにありたす。あなたが送信するメッセヌゞを埩号化するこずはできたせん。 + このルヌムの未認蚌のセッションに暗号化されたメッセヌゞを送信しない。 + あなたのホヌムサヌバヌはスレッドの䞀芧衚瀺をただサポヌトしおいたせん。 + ここに新しいリク゚ストず招埅が衚瀺されたす。 + リッチテキスト゚ディタヌを詊しおみるプレヌンテキストモヌドは近日公開 + タブを䜿甚しおElementの衚瀺をシンプルにする + セッションの詳现 + \ No newline at end of file From 684408d6d29864e9dfab8503f74f1ec30eaf1daf Mon Sep 17 00:00:00 2001 From: Didek Date: Fri, 27 Jan 2023 03:06:44 +0000 Subject: [PATCH 069/189] Translated using Weblate (Polish) Currently translated at 93.1% (2418 of 2597 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/pl/ --- .../src/main/res/values-pl/strings.xml | 42 ++++++++++++++++--- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/library/ui-strings/src/main/res/values-pl/strings.xml b/library/ui-strings/src/main/res/values-pl/strings.xml index 0aad400340..4419187ba5 100644 --- a/library/ui-strings/src/main/res/values-pl/strings.xml +++ b/library/ui-strings/src/main/res/values-pl/strings.xml @@ -345,7 +345,7 @@ Importuj klucze z lokalnego pliku Importuj Szyfruj wiadomości tylko do zaufanych sesji - Nigdy nie wysyłaj szyfrowanych wiadomości do sesji (np urządzeń innych uÅŒytkowników) które nie zostały zweryfikowane. + Nigdy nie wysyłaj szyfrowanych wiadomości do niezweryfikowanych sesji (bez zielonej tarczy) z tego urządzenia. Aby sprawdzić czy ta sesja jest zaufana, skontaktuj się z jej właścicielem uÅŒywając innych form (np. osobiście lub telefonicznie) i zapytaj czy klucz, który widzą w ustawieniach uÅŒytkownika dla tego urządzenia pasuje do klucza poniÅŒej: Jeśli klucz pasuje, potwierdź to przyciskiem poniÅŒej. Jeśli nie, to ktoś inny najprawdopodobniej przejmuje lub podszywa się pod tą sesję i powinieneś dodać tę sesję do czarnej listy. W przyszłości proces weryfikacji będzie bardziej skomplikowany. Wyślij naklejkę @@ -1115,7 +1115,7 @@ \nKlucze nie są zaufane Podpis krzyÅŒowy nie jest aktywowany Aktywne Sesje - PokaÅŒ wszystkie Sesje + PokaÅŒ wszystkie sesje Zarządzaj Sesjami Wyloguj z tej sesji Brak dostępnej informacji o kryptografii @@ -1242,7 +1242,7 @@ Zapisz Klucz Bezpieczeństwa UÅŒyj Frazy Bezpieczeństwa UÅŒyj klucza bezpieczeństwa - Zabezpiecza przeciwko utracie dostępu do zaszyfrowanych wiadomości oraz danych poprzez zapisanie zaszyfrowanych kluczy na Twoim serwerze. + Zabezpiecza przed utratą dostępu do zaszyfrowanych wiadomości poprzez zapisanie kluczy szyfrujących na twoim serwerze. Włącz aparat Wyłącz aparat Wyłącz wyciszenie mikrofonu @@ -1493,7 +1493,7 @@ Integracje są zablokowane To zastąpi obecny Klucz bądź Hasło. Wygeneruj nowy klucz bezpieczeństwa albo hasło dla istniejącej kopii zapasowej. - Zabezpiecza przeciwko utracie dostępu do zaszyfrowanych wiadomości oraz danych poprzez zapisanie zaszyfrowanych kluczy na Twoim serwerze. + Zabezpiecza przed utratą dostępu do zaszyfrowanych wiadomości poprzez zapisanie kluczy szyfrujących na twoim serwerze. Powiadomienie zostało kliknięte! Proszę kliknąć na powiadomieniu, JeÅŒeli nie widzisz powiadomienia, sprawdź ustawienia systemowe. Widzisz powiadomienia! Kliknij na mnie! @@ -2795,4 +2795,36 @@ Rozumiem Zwiń %s pokojów Rozwiń %s pokojów - + Nieaktywne sesje + Ta sesja jest gotowa do bezpiecznego przesyłania wiadomości. + Twoja bieŌąca sesja jest gotowa do bezpiecznego przesyłania wiadomości. + Kontakt + Lokalizacja + Aparat + Transmisja głosowa + Rozpocznij transmisję głosową + Ostatnie ankiety + W tym pokoju nie ma aktywnych ankiet + Aktywne ankiety + Niektóre głosy mogą nie zostać policzone z powodu błędów w odszyfrowaniu + Zakończono ankietę. + Błąd połączenia - Nagrywanie wstrzymane + Nie moÅŒna odtworzyć tej transmisji głosowej. + Jesteś juÅŒ w trakcie nagrywania transmisji głosowej. Proszę zakończyć bieŌącą transmisję, aby rozpocząć nową. + Ktoś inny nagrywa juÅŒ transmisję głosową. Aby rozpocząć nową transmisję, naleÅŒy poczekać na jej zakończenie. + Nie masz wymaganych uprawnień do rozpoczęcia transmisji głosowej w tym pokoju. Skontaktuj się z administratorem pokoju, aby przyznał ci uprawnienia. + Nie moÅŒna rozpocząć nowej transmisji głosowej + Buforowanie
 + Nie moÅŒna rozpocząć wiadomości głosowej + Masz niezweryfikowane sesje + Autentyczność tej zaszyfrowanej wiadomości nie moÅŒe być zagwarantowana na tym urządzeniu. + Historia ankiet + Dodaje (╯°□°╯ ┻━┻ do wiadomości tekstowej + Skanuj kod QR + %1$s zakończył(a) transmisję głosową. + Zarządaj od systemu Android aby klawiatura nie zapisywała ÅŒadnych danych takich jak historia pisania lub słownik. Pamiętaj, nie niektóre klawiatury mogą nie zastosować się do tego ustawienia. + Klawiatura incognito + Witaj w ${app_name}, +\n%s. + Wszechstronna, bezpieczna aplikacja do czatowania dla zespołów, przyjaciół i organizacji. Utwórz czat lub dołącz do istniejącego pokoju, aby rozpocząć. + \ No newline at end of file From b2eb65cd0be9890da2719d5ed898efdce1884b75 Mon Sep 17 00:00:00 2001 From: DarkCoder15 Date: Thu, 26 Jan 2023 15:24:02 +0000 Subject: [PATCH 070/189] Translated using Weblate (Russian) Currently translated at 100.0% (2597 of 2597 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ru/ --- .../src/main/res/values-ru/strings.xml | 72 ++++++++++++++++--- 1 file changed, 63 insertions(+), 9 deletions(-) diff --git a/library/ui-strings/src/main/res/values-ru/strings.xml b/library/ui-strings/src/main/res/values-ru/strings.xml index 1255776c1f..5f2d383460 100644 --- a/library/ui-strings/src/main/res/values-ru/strings.xml +++ b/library/ui-strings/src/main/res/values-ru/strings.xml @@ -65,7 +65,7 @@ %1$s ПтклПМОл прОглашеМОе. ПрОчОМа: %2$s %1$s выгМалО %2$s. ПрОчОМа: %3$s %1$s разблПкОрПваМП %2$s. ПрОчОМа: %3$s - %1$s забаМеМ %2$s. ПрОчОМа: %3$s + %1$s забаМОл %2$s. ПрОчОМа: %3$s %1$s прОМял прОглашеМОе Ўля %2$s. ПрОчОМа: %3$s %1$s ПтПзвал прОглашеМОе %2$s. ПрОчОМа: %3$s %1$s сПзЎал(а) кПЌМату @@ -1420,7 +1420,7 @@ ППсылает сППбщеМОе, ПкрашеММПе в цвет раЎугО ППсылает ЎаММую эЌПцОю, ПкрашеММую в цвет раЎугО РеЎактПр сППбщеМОй - ВключаеЌ сквПзМПе шОфрПваМОе  + ВключОть сквПзМПе шОфрПваМОе  ВключОть шОфрПваМОе\? ППсле включеМОя шОфрПваМОе Ўля кПЌМаты Мельзя ПтключОть. СППбщеМОя ПтправлеММые в зашОфрПваММПй кПЌМате Ме буЎут вОЎМы серверу, тПлькП участМОкаЌ кПЌМаты. ВключеМОе шОфрПваМОя ЌПжет пПЌешать правОльМПй рабПте ЌМПгОх бПтПв О ЌПстПв. ВключОть шОфрПваМОе @@ -2433,7 +2433,7 @@ Не уЎалПсь загрузОть карту Карта ПрОЌечаМОе: прОлПжеМОе буЎет перезапущеМП - ОбсужЎеМОя сППбщеМОй + ВключОть ПбсужЎеМОя сППбщеМОй ППЎключОться к серверу ХПтОте прОсПеЎОМОться к существующеЌу серверу\? ПрПпустОть вПпрПс @@ -2540,7 +2540,7 @@ ДПЌашМОй сервер Ме прОМОЌает ОЌя пПльзПвателя, сПстПящее тПлькП Оз цОфр. ПрПпустОть этПт шаг СПхраМОть О прПЎПлжОть - ВашО преЎпПчтеМОя былО сПхраМеМы. + ЗайЎОте в МастрПйкО чтПбы ОзЌеМОть Ваш прПфОль ВыгляЎОт хПрПшП! ${app_name} также ПтлОчМП пПЎхПЎОт Ўля рабПты. ЕЌу ЎПверяют саЌые МаЎёжМые ПргаМОзацОО в ЌОре. РезервМая кПпОя ОЌеет ЎействОтельМую пПЎпОсь Ўля ЎаММПгП пПльзПвателя. @@ -2791,7 +2791,7 @@ РассЌПтрОте вПзЌПжМПсть выхПЎа Оз старых сеаМсПв (%1$d ЎМей ОлО ЎПльше), кПтПрые вы бПлее Ме ОспПльзуете. ГПлПсПвая траМсляцОя - ГПлПсПвые траМсляцОО + ВключОть гПлПсПвые траМсляцОО ЗапОсывает МазваМОе клОеМта, версОю О URL-аЎрес Ўля бПлее лёгкПгП распПзМаваМОя сеаМсПв в ЌеМеЎжере сеаМсПв. ЗапОсывать ОМфПрЌацОю П клОеМте Галерея @@ -2824,9 +2824,9 @@ РазверМуть ЎПчерМОе элеЌеМты %s ВыбраМП %1$d - ВыбраМП %1$d - ВыбраМП %1$d - ВыбраМП %1$d + ВыбраМы %1$d + ВыбраМы %1$d + ВыбраМы %1$d ВПйтО в пПлМПэкраММый режОЌ ПрОЌеМОть фПрЌатОрПваМОе пПЎчёркОваМОеЌ @@ -2970,4 +2970,58 @@ ЭтПт сеаМс Ме пПЎЎержОвает шОфрПваМОе О пПэтПЌу Ме ЌПжет быть завереМ. %1$s завершОл(а) гПлПсПвую траМсляцОю. Вы завершОлО гПлПсПвую траМсляцОю. - + + Нет актОвМых ПпрПсПв за %1$d ЎеМь. +\nЗагрузОте бПльше чтПбы пПказать ПпрПсы за прПшеЎшОе ЎМО. + Нет актОвМых ПпрПсПв за %1$d ЎМей. +\nЗагрузОте бПльше чтПбы пПказать ПпрПсы за прПшеЎшОе ЎМО. + Нет актОвМых ПпрПсПв за %1$d ЎМей. +\nЗагрузОте бПльше чтПбы пПказать ПпрПсы за прПшеЎшОе ЎМО. + Нет актОвМых ПпрПсПв за %1$d ЎМей. +\nЗагрузОте бПльше чтПбы пПказать ПпрПсы за прПшеЎшОе ЎМО. + + + Нет завершёММых ПпрПсПв за ЎеМь %1$d. +\nЗагрузОте бПльше чтПбы пПказать ПпрПсы за преЎыЎущОе ЎМО. + Нет завершёММых ПпрПсПв за %1$d ЎМей +\nЗагрузОте бПльше чтПбы пПказать ПпрПсы за преЎыЎущОе ЎМО. + Нет завершёММых ПпрПсПв за %1$d ЎМей +\nЗагрузОте бПльше чтПбы пПказать ПпрПсы за преЎыЎущОе ЎМО. + Нет завершёММых ПпрПсПв за %1$d ЎМей +\nЗагрузОте бПльше чтПбы пПказать ПпрПсы за преЎыЎущОе ЎМО. + + ТПкеМ ЎПступа Ўаёт пПлМый ЎПступ к аккауМту. Не ЎелОтесь ОЌ МО с кеЌ. + ТПкеМ ЎПступа + ЗавершёММый ПпрПс + ОпрПс + завершёММый ПпрПс. + ИзЌеМОть ссылку + СПзЎать ссылку + Ссылка + Текст + СпОсПк + ПрПМуЌерПваММый спОсПк + Ссылка + ОшОбка счОтываМОя ПпрПсПв. + ЗагрузОть бПльше ПпрПсПв + ППказываеЌ ПпрПсы + Нет завершёММых ПпрПсПв + ЗавершёММые ПпрПсы + Нет актОвМых ПпрПсПв + АктОвМые ПпрПсы + Из-за ПшОбПк расшОфрПвкО, МекПтПрые гПлПса ЌПгут быть Ме засчОтаМы + ОпрПс завершёМ. + Вы увереМы чтП хПтОте завершОть гПлПсПвую траМсляцОю\? ЭтП завершОт траМсляцОю О пПлМая запОсь буЎет ЎПступМа в чате. + ЗавершОть гПлПсПвую траМсляцОю\? + ОшОбка пПЎключеМОя - ЗапОсь прОПстаМПвлеМа + НевПзЌПжМП прПслушать гПлПсПвую траМсляцОю. + ГПлПсПвая траМсляцОя + Вы Ме ЌПжете запОсать гПлПсПвПе сППбщеМОе, пПтПЌу-чтП Вы запОсываете гПлПсПвую траМсляцОю. ЗавершОте гПлПсПвую траМсляцОю, чтПбы запОсать гПлПсПвПе сППбщеМОе + Не уЎалПсь запОсать гПлПсПвПе сППбщеМОе + УбеЎОться чтП Ваш аккауМт в безПпасМПстО + ППлучОть пПслеЎМюю сбПрку (у вас ЌПгут быть прПблеЌы сП вхПЎПЌ) + ИстПрОя ПпрПса + ГПлПсПвая траМсляцОя Мачата + Ваш ЎПЌашМОй сервер Ме пПЎЎержОвает спОсПк ПбсужЎеМОй. + ОстаМПвОть + \ No newline at end of file From 2b5fb3bfbfde21f5fba11fd600d0547eadbd386c Mon Sep 17 00:00:00 2001 From: Jozef Gaal Date: Wed, 25 Jan 2023 19:01:47 +0000 Subject: [PATCH 071/189] Translated using Weblate (Slovak) Currently translated at 100.0% (2597 of 2597 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/sk/ --- library/ui-strings/src/main/res/values-sk/strings.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values-sk/strings.xml b/library/ui-strings/src/main/res/values-sk/strings.xml index ed3f47f9d3..c9e92d323b 100644 --- a/library/ui-strings/src/main/res/values-sk/strings.xml +++ b/library/ui-strings/src/main/res/values-sk/strings.xml @@ -2978,4 +2978,5 @@ NemÃŽÅŸete spustiÅ¥ hlasovú správu, pretoÅŸe práve nahrávate ÅŸivé vysielanie. Ukončite prosím ÅŸivé vysielanie, aby ste mohli začaÅ¥ nahrávaÅ¥ hlasovú správu NemoÅŸno spustiÅ¥ hlasovú správu - + Chyba pripojenia - nahrávanie pozastavené + \ No newline at end of file From b01fd17413cf1bec58acb887c9918136478aeb30 Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Wed, 25 Jan 2023 17:38:17 +0000 Subject: [PATCH 072/189] Translated using Weblate (Ukrainian) Currently translated at 100.0% (2597 of 2597 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/uk/ --- library/ui-strings/src/main/res/values-uk/strings.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values-uk/strings.xml b/library/ui-strings/src/main/res/values-uk/strings.xml index 6294526be2..0f6027903f 100644 --- a/library/ui-strings/src/main/res/values-uk/strings.xml +++ b/library/ui-strings/src/main/res/values-uk/strings.xml @@ -3038,4 +3038,5 @@ ППказ ПпОтуваМь ВО Ме ЌПжете рПзпПчатО запОс гПлПсПвПгП пПвіЎПЌлеММя, ПскількО вО запОсуєте траМсляцію МажОвП. БуЎь ласка, заверште її, щПб рПзпПчатО запОс гПлПсПвПгП пПвіЎПЌлеММя Не вЎалПся рПзпПчатО запОс гПлПсПвПгП пПвіЎПЌлеММя - + ППЌОлка з\'єЎМаММя - ЗапОс прОзупОМеМП + \ No newline at end of file From d83efde9f03abfe7cefc51ff4c6f0c402046a8b3 Mon Sep 17 00:00:00 2001 From: DarkCoder15 Date: Thu, 26 Jan 2023 15:33:23 +0000 Subject: [PATCH 073/189] Translated using Weblate (Russian) Currently translated at 100.0% (90 of 90 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/ru/ --- fastlane/metadata/android/ru-RU/changelogs/40105110.txt | 2 ++ fastlane/metadata/android/ru-RU/changelogs/40105140.txt | 2 ++ fastlane/metadata/android/ru-RU/changelogs/40105160.txt | 2 ++ fastlane/metadata/android/ru-RU/changelogs/40105180.txt | 2 ++ fastlane/metadata/android/ru-RU/changelogs/40105200.txt | 2 ++ 5 files changed, 10 insertions(+) create mode 100644 fastlane/metadata/android/ru-RU/changelogs/40105110.txt create mode 100644 fastlane/metadata/android/ru-RU/changelogs/40105140.txt create mode 100644 fastlane/metadata/android/ru-RU/changelogs/40105160.txt create mode 100644 fastlane/metadata/android/ru-RU/changelogs/40105180.txt create mode 100644 fastlane/metadata/android/ru-RU/changelogs/40105200.txt diff --git a/fastlane/metadata/android/ru-RU/changelogs/40105110.txt b/fastlane/metadata/android/ru-RU/changelogs/40105110.txt new file mode 100644 index 0000000000..3de0ce886e --- /dev/null +++ b/fastlane/metadata/android/ru-RU/changelogs/40105110.txt @@ -0,0 +1,2 @@ +ГлавМые ОзЌеМеМОя в этПй версОО: НПвый пПлМПэкраММый режОЌ в улучшеММПЌ реЎактПре текста О ОсправлеМОя багПв. +ППлМый спОсПк: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ru-RU/changelogs/40105140.txt b/fastlane/metadata/android/ru-RU/changelogs/40105140.txt new file mode 100644 index 0000000000..9700aef76f --- /dev/null +++ b/fastlane/metadata/android/ru-RU/changelogs/40105140.txt @@ -0,0 +1,2 @@ +ГлавМые ОзЌеМеМОя в этПй версОО: ОбсужЎеМОя включеМы пП уЌПлчаМОю. +ППлМый спОсПк: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ru-RU/changelogs/40105160.txt b/fastlane/metadata/android/ru-RU/changelogs/40105160.txt new file mode 100644 index 0000000000..9700aef76f --- /dev/null +++ b/fastlane/metadata/android/ru-RU/changelogs/40105160.txt @@ -0,0 +1,2 @@ +ГлавМые ОзЌеМеМОя в этПй версОО: ОбсужЎеМОя включеМы пП уЌПлчаМОю. +ППлМый спОсПк: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ru-RU/changelogs/40105180.txt b/fastlane/metadata/android/ru-RU/changelogs/40105180.txt new file mode 100644 index 0000000000..9700aef76f --- /dev/null +++ b/fastlane/metadata/android/ru-RU/changelogs/40105180.txt @@ -0,0 +1,2 @@ +ГлавМые ОзЌеМеМОя в этПй версОО: ОбсужЎеМОя включеМы пП уЌПлчаМОю. +ППлМый спОсПк: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ru-RU/changelogs/40105200.txt b/fastlane/metadata/android/ru-RU/changelogs/40105200.txt new file mode 100644 index 0000000000..9ee0bb9656 --- /dev/null +++ b/fastlane/metadata/android/ru-RU/changelogs/40105200.txt @@ -0,0 +1,2 @@ +ГлавМые ОзЌеМеМОя в этПй версОО: УстраМеМОя багПв! +ППлМый спОсПк: https://github.com/vector-im/element-android/releases From 366ce8665d43ab264896aab8abe60af54162c355 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 27 Jan 2023 16:40:20 +0100 Subject: [PATCH 074/189] Do not show unknown data. --- .../im/vector/app/features/home/HomeDetailViewState.kt | 4 ++-- .../features/home/room/detail/RoomDetailViewState.kt | 4 ++-- .../vector/app/features/sync/widget/SyncStateView.kt | 10 ++++++---- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewState.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewState.kt index 7fbd5b2bf6..dcf4d87894 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewState.kt @@ -38,8 +38,8 @@ data class HomeDetailViewState( val notificationCountRooms: Int = 0, val notificationHighlightRooms: Boolean = false, val hasUnreadMessages: Boolean = false, - val syncState: SyncState = SyncState.Idle, - val incrementalSyncRequestState: SyncRequestState.IncrementalSyncRequestState = SyncRequestState.IncrementalSyncIdle, + val syncState: SyncState? = null, + val incrementalSyncRequestState: SyncRequestState.IncrementalSyncRequestState? = null, val pushCounter: Int = 0, val pstnSupportFlag: Boolean = false, val forceDialPadTab: Boolean = false diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt index 897594ffad..f4919a5906 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt @@ -60,8 +60,8 @@ data class RoomDetailViewState( val formattedTypingUsers: String? = null, val tombstoneEvent: Event? = null, val joinUpgradedRoomAsync: Async = Uninitialized, - val syncState: SyncState = SyncState.Idle, - val incrementalSyncRequestState: SyncRequestState.IncrementalSyncRequestState = SyncRequestState.IncrementalSyncIdle, + val syncState: SyncState? = null, + val incrementalSyncRequestState: SyncRequestState.IncrementalSyncRequestState? = null, val pushCounter: Int = 0, val highlightedEventId: String? = null, val unreadState: UnreadState = UnreadState.Unknown, diff --git a/vector/src/main/java/im/vector/app/features/sync/widget/SyncStateView.kt b/vector/src/main/java/im/vector/app/features/sync/widget/SyncStateView.kt index a71f445859..a40b70b8a6 100755 --- a/vector/src/main/java/im/vector/app/features/sync/widget/SyncStateView.kt +++ b/vector/src/main/java/im/vector/app/features/sync/widget/SyncStateView.kt @@ -40,8 +40,8 @@ class SyncStateView @JvmOverloads constructor(context: Context, attrs: Attribute @SuppressLint("SetTextI18n") fun render( - newState: SyncState, - incrementalSyncRequestState: SyncRequestState.IncrementalSyncRequestState, + newState: SyncState?, + incrementalSyncRequestState: SyncRequestState.IncrementalSyncRequestState?, pushCounter: Int, showDebugInfo: Boolean ) { @@ -64,8 +64,9 @@ class SyncStateView @JvmOverloads constructor(context: Context, attrs: Attribute } } - private fun SyncState.toHumanReadable(): String { + private fun SyncState?.toHumanReadable(): String { return when (this) { + null -> "Unknown" SyncState.Idle -> "Idle" SyncState.InvalidToken -> "InvalidToken" SyncState.Killed -> "Killed" @@ -76,8 +77,9 @@ class SyncStateView @JvmOverloads constructor(context: Context, attrs: Attribute } } - private fun SyncRequestState.IncrementalSyncRequestState.toHumanReadable(): String { + private fun SyncRequestState.IncrementalSyncRequestState?.toHumanReadable(): String { return when (this) { + null -> "Unknown" SyncRequestState.IncrementalSyncIdle -> "Idle" is SyncRequestState.IncrementalSyncParsing -> "Parsing ${this.rooms} room(s) ${this.toDevice} toDevice(s)" SyncRequestState.IncrementalSyncError -> "Error" From 5a62e31c86ac55256d7915606b11d25cf830e12f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 27 Jan 2023 16:44:35 +0100 Subject: [PATCH 075/189] Ensure sync thread is started when recovering from crash. --- .../session/EnsureSessionSyncingUseCase.kt | 38 +++++++++++++++++++ .../features/home/HomeActivityViewModel.kt | 4 ++ 2 files changed, 42 insertions(+) create mode 100644 vector/src/main/java/im/vector/app/core/session/EnsureSessionSyncingUseCase.kt diff --git a/vector/src/main/java/im/vector/app/core/session/EnsureSessionSyncingUseCase.kt b/vector/src/main/java/im/vector/app/core/session/EnsureSessionSyncingUseCase.kt new file mode 100644 index 0000000000..c53795d18d --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/session/EnsureSessionSyncingUseCase.kt @@ -0,0 +1,38 @@ +/* + * 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.core.session + +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext +import im.vector.app.core.di.ActiveSessionHolder +import im.vector.app.core.extensions.startSyncing +import org.matrix.android.sdk.api.session.sync.SyncState +import timber.log.Timber +import javax.inject.Inject + +class EnsureSessionSyncingUseCase @Inject constructor( + @ApplicationContext private val context: Context, + private val activeSessionHolder: ActiveSessionHolder, +) { + fun execute() { + val session = activeSessionHolder.getSafeActiveSession() ?: return + if (session.syncService().getSyncState() == SyncState.Idle) { + Timber.w("EnsureSessionSyncingUseCase: start syncing") + session.startSyncing(context) + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt index 8f16121a30..f961cc5da6 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt @@ -30,6 +30,7 @@ import im.vector.app.core.pushers.EnsureFcmTokenIsRetrievedUseCase import im.vector.app.core.pushers.PushersManager import im.vector.app.core.pushers.RegisterUnifiedPushUseCase import im.vector.app.core.pushers.UnregisterUnifiedPushUseCase +import im.vector.app.core.session.EnsureSessionSyncingUseCase import im.vector.app.features.analytics.AnalyticsConfig import im.vector.app.features.analytics.AnalyticsTracker import im.vector.app.features.analytics.extensions.toAnalyticsType @@ -95,6 +96,7 @@ class HomeActivityViewModel @AssistedInject constructor( private val registerUnifiedPushUseCase: RegisterUnifiedPushUseCase, private val unregisterUnifiedPushUseCase: UnregisterUnifiedPushUseCase, private val ensureFcmTokenIsRetrievedUseCase: EnsureFcmTokenIsRetrievedUseCase, + private val ensureSessionSyncingUseCase: EnsureSessionSyncingUseCase, ) : VectorViewModel(initialState) { @AssistedFactory @@ -118,6 +120,8 @@ class HomeActivityViewModel @AssistedInject constructor( private fun initialize() { if (isInitialized) return isInitialized = true + // Ensure Session is syncing + ensureSessionSyncingUseCase.execute() registerUnifiedPushIfNeeded() cleanupFiles() observeInitialSync() From d6712b7c93d11f50f001c663ab017706b0afec8c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 27 Jan 2023 17:01:52 +0100 Subject: [PATCH 076/189] Fix layout issue with `Messages failed to send` banner. --- .../src/main/res/layout/fragment_timeline.xml | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/vector/src/main/res/layout/fragment_timeline.xml b/vector/src/main/res/layout/fragment_timeline.xml index 6e83dbe8fd..a022ad2744 100644 --- a/vector/src/main/res/layout/fragment_timeline.xml +++ b/vector/src/main/res/layout/fragment_timeline.xml @@ -17,7 +17,8 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="48dp" - android:visibility="gone" /> + android:visibility="gone" + tools:visibility="visible" /> + android:layout_height="match_parent"> + app:layout_constraintTop_toBottomOf="@id/removeJitsiWidgetView" + tools:visibility="visible" /> + + - - + android:translationZ="10dp" + android:visibility="visible" /> From 0c89245392673ddcf3cf36dd32735798061a2410 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 27 Jan 2023 18:13:27 +0100 Subject: [PATCH 077/189] Fix test compilation --- .../java/im/vector/app/features/command/CommandParserTest.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/vector/src/test/java/im/vector/app/features/command/CommandParserTest.kt b/vector/src/test/java/im/vector/app/features/command/CommandParserTest.kt index f502db85ca..ef6a99aa49 100644 --- a/vector/src/test/java/im/vector/app/features/command/CommandParserTest.kt +++ b/vector/src/test/java/im/vector/app/features/command/CommandParserTest.kt @@ -16,12 +16,15 @@ package im.vector.app.features.command +import im.vector.app.test.fakes.FakeVectorPreferences import org.amshove.kluent.shouldBeEqualTo import org.junit.Test private const val A_SPACE_ID = "!my-space-id" class CommandParserTest { + private val fakeVectorPreferences = FakeVectorPreferences() + @Test fun parseSlashCommandEmpty() { test("/", ParsedCommand.ErrorEmptySlashCommand) @@ -70,7 +73,7 @@ class CommandParserTest { } private fun test(message: String, expectedResult: ParsedCommand) { - val commandParser = CommandParser() + val commandParser = CommandParser(fakeVectorPreferences.instance) val result = commandParser.parseSlashCommand(message, null, false) result shouldBeEqualTo expectedResult } From a7238bdb3b308563b5344be752230fa73eaca447 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Jan 2023 23:03:22 +0000 Subject: [PATCH 078/189] Bump org.jlleitschuh.gradle.ktlint from 11.0.0 to 11.1.0 Bumps org.jlleitschuh.gradle.ktlint from 11.0.0 to 11.1.0. --- updated-dependencies: - dependency-name: org.jlleitschuh.gradle.ktlint dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 3e8233fa4d..84334d679b 100644 --- a/build.gradle +++ b/build.gradle @@ -41,7 +41,7 @@ buildscript { plugins { // ktlint Plugin - id "org.jlleitschuh.gradle.ktlint" version "11.0.0" + id "org.jlleitschuh.gradle.ktlint" version "11.1.0" // Detekt id "io.gitlab.arturbosch.detekt" version "1.22.0" // Ksp From 3104f62988d0224254e4386b8f87c6ed2a431784 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Jan 2023 23:03:50 +0000 Subject: [PATCH 079/189] Bump sentry-android from 6.12.1 to 6.13.0 Bumps [sentry-android](https://github.com/getsentry/sentry-java) from 6.12.1 to 6.13.0. - [Release notes](https://github.com/getsentry/sentry-java/releases) - [Changelog](https://github.com/getsentry/sentry-java/blob/main/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-java/compare/6.12.1...6.13.0) --- updated-dependencies: - dependency-name: io.sentry:sentry-android dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index 5d7286ab1a..bab9229b3b 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -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.12.1" +def sentry = "6.13.0" // Use 1.6.0 alpha to fix issue with test def fragment = "1.6.0-alpha04" // Testing From 934e9178b4b34cce44ec4aa966165d1e6f72651e Mon Sep 17 00:00:00 2001 From: DarkCoder15 Date: Sat, 28 Jan 2023 09:09:26 +0000 Subject: [PATCH 080/189] Translated using Weblate (Russian) Currently translated at 100.0% (2597 of 2597 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ru/ --- library/ui-strings/src/main/res/values-ru/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values-ru/strings.xml b/library/ui-strings/src/main/res/values-ru/strings.xml index 5f2d383460..5938200c1e 100644 --- a/library/ui-strings/src/main/res/values-ru/strings.xml +++ b/library/ui-strings/src/main/res/values-ru/strings.xml @@ -6,7 +6,7 @@ %1$s вПшёл(шла) в кПЌМату %1$s пПкОМул(а) кПЌМату %1$s ПтклПМОл(а) прОглашеМОе - %1$s выгМаМ %2$s + %1$s выгМал %2$s %1$s разблПкОрПвал(а) %2$s %1$s заблПкОрПвал(а) %2$s %1$s ПтПзвал(а) прОглашеМОе %2$s From 5de386c3c9a9ddb0249a3cad51590aa4a53f5457 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 30 Jan 2023 10:49:36 +0100 Subject: [PATCH 081/189] Ensure we never call `posthog.identify` if user did not consent, because it sends request `/decide/?v=2` to the analytic server. --- .../analytics/impl/DefaultVectorAnalytics.kt | 35 ++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/analytics/impl/DefaultVectorAnalytics.kt b/vector/src/main/java/im/vector/app/features/analytics/impl/DefaultVectorAnalytics.kt index ca7608166c..20712c81b6 100644 --- a/vector/src/main/java/im/vector/app/features/analytics/impl/DefaultVectorAnalytics.kt +++ b/vector/src/main/java/im/vector/app/features/analytics/impl/DefaultVectorAnalytics.kt @@ -31,6 +31,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import org.matrix.android.sdk.api.extensions.orFalse import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton @@ -60,6 +61,9 @@ class DefaultVectorAnalytics @Inject constructor( private var userConsent: Boolean? = null private var analyticsId: String? = null + // Cache for the properties to send + private var pendingUserProperties: UserProperties? = null + override fun init() { observeUserConsent() observeAnalyticsId() @@ -112,6 +116,7 @@ class DefaultVectorAnalytics @Inject constructor( private suspend fun identifyPostHog() { val id = analyticsId ?: return + if (!userConsent.orFalse()) return if (id.isEmpty()) { Timber.tag(analyticsTag.value).d("reset") posthog?.reset() @@ -126,7 +131,7 @@ class DefaultVectorAnalytics @Inject constructor( .onEach { consent -> Timber.tag(analyticsTag.value).d("User consent updated to $consent") userConsent = consent - optOutPostHog() + initOrStopPostHog() initOrStopSentry() } .launchIn(globalScope) @@ -141,8 +146,20 @@ class DefaultVectorAnalytics @Inject constructor( } } - private fun optOutPostHog() { - userConsent?.let { posthog?.optOut(!it) } + private suspend fun initOrStopPostHog() { + userConsent?.let { _userConsent -> + when (_userConsent) { + true -> { + posthog?.optOut(false) + identifyPostHog() + pendingUserProperties?.let { doUpdateUserProperties(it) } + pendingUserProperties = null + } + false -> { + posthog?.optOut(true) + } + } + } } override fun capture(event: VectorAnalyticsEvent) { @@ -160,7 +177,17 @@ class DefaultVectorAnalytics @Inject constructor( } override fun updateUserProperties(userProperties: UserProperties) { - posthog?.identify(REUSE_EXISTING_ID, userProperties.getProperties()?.toPostHogUserProperties(), IGNORED_OPTIONS) + if (userConsent == true) { + doUpdateUserProperties(userProperties) + } else { + pendingUserProperties = userProperties + } + } + + private fun doUpdateUserProperties(userProperties: UserProperties) { + posthog + ?.takeIf { userConsent == true } + ?.identify(REUSE_EXISTING_ID, userProperties.getProperties()?.toPostHogUserProperties(), IGNORED_OPTIONS) } private fun Map?.toPostHogProperties(): Properties? { From af67705778bff9a769a85b12018f9d8cdd341e8d Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 26 Jan 2023 18:26:34 +0100 Subject: [PATCH 082/189] Add optional initial time parameter in CountUpTimer --- .../lib/attachmentviewer/VideoViewHolder.kt | 2 +- .../lib/core/utils/timer/CountUpTimer.kt | 18 ++++++++++-------- .../app/features/call/webrtc/WebRtcCall.kt | 2 +- .../room/detail/composer/AudioMessageHelper.kt | 2 +- .../location/live/map/LiveLocationUserItem.kt | 2 +- .../listening/VoiceBroadcastPlayerImpl.kt | 5 ++++- 6 files changed, 18 insertions(+), 13 deletions(-) diff --git a/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/VideoViewHolder.kt b/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/VideoViewHolder.kt index 07c7b4588f..30da59750e 100644 --- a/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/VideoViewHolder.kt +++ b/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/VideoViewHolder.kt @@ -102,7 +102,7 @@ class VideoViewHolder constructor(itemView: View) : views.videoView.setOnPreparedListener { stopTimer() - countUpTimer = CountUpTimer(100).also { + countUpTimer = CountUpTimer(intervalInMs = 100).also { it.tickListener = CountUpTimer.TickListener { val duration = views.videoView.duration val progress = views.videoView.currentPosition diff --git a/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/CountUpTimer.kt b/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/CountUpTimer.kt index a4fd8bb4e1..297e33d9cc 100644 --- a/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/CountUpTimer.kt +++ b/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/CountUpTimer.kt @@ -28,10 +28,10 @@ import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicLong @OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class) -class CountUpTimer(private val intervalInMs: Long = 1_000) { +class CountUpTimer(private val initialTime: Long = 0L, private val intervalInMs: Long = 1_000) { private val coroutineScope = CoroutineScope(Dispatchers.Main) - private val elapsedTime: AtomicLong = AtomicLong() + private val elapsedTime: AtomicLong = AtomicLong(initialTime) private val resumed: AtomicBoolean = AtomicBoolean(false) init { @@ -39,13 +39,13 @@ class CountUpTimer(private val intervalInMs: Long = 1_000) { } private fun startCounter() { - tickerFlow(coroutineScope, intervalInMs / 10) + val internalDelay = if (intervalInMs > 100) intervalInMs / 10 else intervalInMs + tickerFlow(coroutineScope, internalDelay) .filter { resumed.get() } - .map { elapsedTime.addAndGet(intervalInMs / 10) } - .filter { it % intervalInMs == 0L } - .onEach { - tickListener?.onTick(it) - }.launchIn(coroutineScope) + .map { elapsedTime.addAndGet(internalDelay) } + .filter { (it - initialTime) % intervalInMs == 0L } + .onEach { tickListener?.onTick(it) } + .launchIn(coroutineScope) } var tickListener: TickListener? = null @@ -55,6 +55,7 @@ class CountUpTimer(private val intervalInMs: Long = 1_000) { } fun pause() { + tickListener?.onTick(elapsedTime()) resumed.set(false) } @@ -63,6 +64,7 @@ class CountUpTimer(private val intervalInMs: Long = 1_000) { } fun stop() { + tickListener?.onTick(elapsedTime()) coroutineScope.cancel() } diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt index 0bf70690ba..9c65d94a94 100644 --- a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt +++ b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt @@ -166,7 +166,7 @@ class WebRtcCall( private var videoSender: RtpSender? = null private var screenSender: RtpSender? = null - private val timer = CountUpTimer(1000L).apply { + private val timer = CountUpTimer(intervalInMs = 1000L).apply { tickListener = CountUpTimer.TickListener { milliseconds -> val formattedDuration = formatDuration(Duration.ofMillis(milliseconds)) listeners.forEach { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt index 900de041d0..1929abcc4f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt @@ -198,7 +198,7 @@ class AudioMessageHelper @Inject constructor( private fun startRecordingAmplitudes() { amplitudeTicker?.stop() - amplitudeTicker = CountUpTimer(50).apply { + amplitudeTicker = CountUpTimer(intervalInMs = 50).apply { tickListener = CountUpTimer.TickListener { onAmplitudeTick() } resume() } diff --git a/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationUserItem.kt b/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationUserItem.kt index c108e83e76..724b2c9b6f 100644 --- a/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationUserItem.kt +++ b/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationUserItem.kt @@ -105,7 +105,7 @@ abstract class LiveLocationUserItem : VectorEpoxyModel(R.id.itemUserAvatarImageView) val itemUserDisplayNameTextView by bind(R.id.itemUserDisplayNameTextView) val itemRemainingTimeTextView by bind(R.id.itemRemainingTimeTextView) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt index 2559f1a7d6..c356ed5500 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt @@ -488,7 +488,10 @@ class VoiceBroadcastPlayerImpl @Inject constructor( fun startPlaybackTicker(id: String) { playbackTicker?.stop() - playbackTicker = CountUpTimer(50L).apply { + playbackTicker = CountUpTimer( + initialTime = playbackTracker.getPlaybackTime(id)?.toLong() ?: 0L, + intervalInMs = 50L + ).apply { tickListener = CountUpTimer.TickListener { onPlaybackTick(id) } resume() } From a06104534b763b8f94c2255ab0e4c46f717d80c9 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 25 Jan 2023 18:07:16 +0100 Subject: [PATCH 083/189] Voice Broadcast - use internal playback timer instead of relying on the media player position --- .../listening/VoiceBroadcastPlayerImpl.kt | 61 ++++++++----------- 1 file changed, 25 insertions(+), 36 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt index c356ed5500..bcc8d0605e 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt @@ -206,7 +206,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( } } State.Buffering -> { - val savedPosition = currentVoiceBroadcast?.voiceBroadcastId?.let { playbackTracker.getPlaybackTime(it) } + val savedPosition = currentVoiceBroadcast?.let { playbackTracker.getPlaybackTime(it.voiceBroadcastId) } when { // resume playback from the next sequence item playlist.currentSequence != null -> playlist.getNextItem()?.let { startPlayback(it.startTime) } @@ -226,18 +226,21 @@ class VoiceBroadcastPlayerImpl @Inject constructor( private fun startPlayback(position: Int) { stopPlayer() - val playlistItem = playlist.findByPosition(position) - val content = playlistItem?.audioEvent?.content ?: run { Timber.w("## Voice Broadcast | No content to play at position $position"); return } + val playlistItem = playlist.findByPosition(position) ?: run { Timber.w("## Voice Broadcast | No content to play at position $position"); return } val sequence = playlistItem.sequence ?: run { Timber.w("## Voice Broadcast | Playlist item has no sequence"); return } val sequencePosition = position - playlistItem.startTime + prepareCurrentPlayerJob = sessionScope.launch { try { - val mp = prepareMediaPlayer(content) + val mp = prepareMediaPlayer(playlistItem.audioEvent.content) playlist.currentSequence = sequence - 1 // will be incremented in onNextMediaPlayerStarted + mp.start() if (sequencePosition > 0) { mp.seekTo(sequencePosition) } + + currentVoiceBroadcast?.let { playbackTicker.startPlaybackTicker(it.voiceBroadcastId) } onNextMediaPlayerStarted(mp) } catch (failure: VoiceBroadcastFailure.ListeningError) { playingState = State.Error(failure) @@ -259,7 +262,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( playingState = State.Playing currentMediaPlayer?.start() } else { - val savedPosition = currentVoiceBroadcast?.voiceBroadcastId?.let { playbackTracker.getPlaybackTime(it) } ?: 0 + val savedPosition = currentVoiceBroadcast?.let { playbackTracker.getPlaybackTime(it.voiceBroadcastId) } ?: 0 startPlayback(savedPosition) } } @@ -270,6 +273,8 @@ class VoiceBroadcastPlayerImpl @Inject constructor( playbackTracker.updatePausedAtPlaybackTime(voiceBroadcast.voiceBroadcastId, positionMillis, positionMillis.toFloat() / duration) } playingState == State.Playing || playingState == State.Buffering -> { + stopPlayer() + playbackTracker.updatePlayingAtPlaybackTime(voiceBroadcast.voiceBroadcastId, positionMillis, positionMillis.toFloat() / duration) startPlayback(positionMillis) } playingState == State.Idle || playingState == State.Paused -> { @@ -355,6 +360,8 @@ class VoiceBroadcastPlayerImpl @Inject constructor( private fun stopPlayer() { tryOrNull { currentMediaPlayer?.stop() } + playbackTicker.stopPlaybackTicker() + currentMediaPlayer?.release() currentMediaPlayer = null @@ -376,7 +383,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( State.Paused, State.Buffering, is State.Error, - State.Idle -> playbackTicker.stopPlaybackTicker(voiceBroadcastId) + State.Idle -> playbackTicker.stopPlaybackTicker() } // Notify playback tracker about error @@ -416,22 +423,6 @@ class VoiceBroadcastPlayerImpl @Inject constructor( prepareNextMediaPlayer() } - private fun getCurrentPlaybackPosition(): Int? { - val voiceBroadcastId = currentVoiceBroadcast?.voiceBroadcastId ?: return null - val computedPosition = tryOrNull { currentMediaPlayer?.currentPosition }?.let { playlist.currentItem?.startTime?.plus(it) } - val savedPosition = playbackTracker.getPlaybackTime(voiceBroadcastId) - return computedPosition ?: savedPosition - } - - private fun getCurrentPlaybackPercentage(): Float? { - val playlistPosition = playlist.currentItem?.startTime - val computedPosition = tryOrNull { currentMediaPlayer?.currentPosition }?.let { playlistPosition?.plus(it) } ?: playlistPosition - val duration = playlist.duration - val computedPercentage = if (computedPosition != null && duration > 0) computedPosition.toFloat() / duration else null - val savedPercentage = currentVoiceBroadcast?.voiceBroadcastId?.let { playbackTracker.getPercentage(it) } - return computedPercentage ?: savedPercentage - } - private inner class MediaPlayerListener : MediaPlayer.OnInfoListener, MediaPlayer.OnCompletionListener, @@ -492,39 +483,37 @@ class VoiceBroadcastPlayerImpl @Inject constructor( initialTime = playbackTracker.getPlaybackTime(id)?.toLong() ?: 0L, intervalInMs = 50L ).apply { - tickListener = CountUpTimer.TickListener { onPlaybackTick(id) } + tickListener = CountUpTimer.TickListener { onPlaybackTick(id, it.toInt()) } resume() } - onPlaybackTick(id) } - fun stopPlaybackTicker(id: String) { + fun stopPlaybackTicker() { playbackTicker?.stop() + playbackTicker?.tickListener = null playbackTicker = null - onPlaybackTick(id) } - private fun onPlaybackTick(id: String) { - val playbackTime = getCurrentPlaybackPosition() - val percentage = getCurrentPlaybackPercentage() + private fun onPlaybackTick(id: String, position: Int) { + val percentage = tryOrNull { position.toFloat() / playlist.duration } when (playingState) { State.Playing -> { - if (playbackTime != null && percentage != null) { - playbackTracker.updatePlayingAtPlaybackTime(id, playbackTime, percentage) + if (percentage != null) { + playbackTracker.updatePlayingAtPlaybackTime(id, position, percentage) } } State.Paused, State.Buffering -> { - if (playbackTime != null && percentage != null) { - playbackTracker.updatePausedAtPlaybackTime(id, playbackTime, percentage) + if (percentage != null) { + playbackTracker.updatePausedAtPlaybackTime(id, position, percentage) } } State.Idle -> { - // restart the playback time if player completed with less than 250 ms remaining time - if (playbackTime == null || percentage == null || (playlist.duration - playbackTime) < 250) { + // restart the playback time if player completed with less than 1s remaining time + if (percentage == null || (playlist.duration - position) < 1000) { playbackTracker.stopPlayback(id) } else { - playbackTracker.updatePausedAtPlaybackTime(id, playbackTime, percentage) + playbackTracker.updatePausedAtPlaybackTime(id, position, percentage) } } is State.Error -> Unit From bdfebac76d4dbd14048a9cb7435d6c57cdac8804 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 27 Jan 2023 17:49:55 +0100 Subject: [PATCH 084/189] CountUpTimer - compute elapsed time using real time --- .../im/vector/lib/core/utils/timer/Clock.kt | 34 +++++++++++++++++++ .../lib/core/utils/timer/CountUpTimer.kt | 23 ++++++++----- 2 files changed, 49 insertions(+), 8 deletions(-) create mode 100644 library/core-utils/src/main/java/im/vector/lib/core/utils/timer/Clock.kt diff --git a/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/Clock.kt b/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/Clock.kt new file mode 100644 index 0000000000..47e2c6532a --- /dev/null +++ b/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/Clock.kt @@ -0,0 +1,34 @@ +/* + * 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.lib.core.utils.timer + +interface Clock { + fun epochMillis(): Long +} + +class DefaultClock : Clock { + + /** + * Provides a UTC epoch in milliseconds + * + * This value is not guaranteed to be correct with reality + * as a User can override the system time and date to any values. + */ + override fun epochMillis(): Long { + return System.currentTimeMillis() + } +} diff --git a/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/CountUpTimer.kt b/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/CountUpTimer.kt index 297e33d9cc..debb1d0f4b 100644 --- a/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/CountUpTimer.kt +++ b/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/CountUpTimer.kt @@ -28,22 +28,23 @@ import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicLong @OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class) -class CountUpTimer(private val initialTime: Long = 0L, private val intervalInMs: Long = 1_000) { +class CountUpTimer(initialTime: Long = 0L, private val intervalInMs: Long = 1_000) { private val coroutineScope = CoroutineScope(Dispatchers.Main) - private val elapsedTime: AtomicLong = AtomicLong(initialTime) private val resumed: AtomicBoolean = AtomicBoolean(false) + private val clock: Clock = DefaultClock() + private var lastTime: AtomicLong = AtomicLong() + private val elapsedTime: AtomicLong = AtomicLong(initialTime) + init { startCounter() } private fun startCounter() { - val internalDelay = if (intervalInMs > 100) intervalInMs / 10 else intervalInMs - tickerFlow(coroutineScope, internalDelay) + tickerFlow(coroutineScope, intervalInMs) .filter { resumed.get() } - .map { elapsedTime.addAndGet(internalDelay) } - .filter { (it - initialTime) % intervalInMs == 0L } + .map { addAndGetElapsedTime() } .onEach { tickListener?.onTick(it) } .launchIn(coroutineScope) } @@ -55,19 +56,25 @@ class CountUpTimer(private val initialTime: Long = 0L, private val intervalInMs: } fun pause() { - tickListener?.onTick(elapsedTime()) + tickListener?.onTick(addAndGetElapsedTime()) resumed.set(false) } fun resume() { + lastTime.set(clock.epochMillis()) resumed.set(true) } fun stop() { - tickListener?.onTick(elapsedTime()) + tickListener?.onTick(addAndGetElapsedTime()) coroutineScope.cancel() } + private fun addAndGetElapsedTime(): Long { + val now = clock.epochMillis() + return elapsedTime.addAndGet(now - lastTime.getAndSet(now)) + } + fun interface TickListener { fun onTick(milliseconds: Long) } From 05ffadb0ef1a353ed4eb993931e9a9742d71fc26 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 27 Jan 2023 17:54:34 +0100 Subject: [PATCH 085/189] Fix playback position on live broadcast --- .../listening/VoiceBroadcastPlayerImpl.kt | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt index bcc8d0605e..6efa41822e 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt @@ -225,11 +225,21 @@ class VoiceBroadcastPlayerImpl @Inject constructor( private fun startPlayback(position: Int) { stopPlayer() + playingState = State.Buffering - val playlistItem = playlist.findByPosition(position) ?: run { Timber.w("## Voice Broadcast | No content to play at position $position"); return } - val sequence = playlistItem.sequence ?: run { Timber.w("## Voice Broadcast | Playlist item has no sequence"); return } + val playlistItem = playlist.findByPosition(position) ?: run { + Timber.w("## Voice Broadcast | No content to play at position $position"); stop(); return + } + val sequence = playlistItem.sequence ?: run { + Timber.w("## Voice Broadcast | Playlist item has no sequence"); stop(); return + } val sequencePosition = position - playlistItem.startTime + currentVoiceBroadcast?.let { + val percentage = tryOrNull { position.toFloat() / playlist.duration } ?: 0f + playbackTracker.updatePausedAtPlaybackTime(it.voiceBroadcastId, position, percentage) + } + prepareCurrentPlayerJob = sessionScope.launch { try { val mp = prepareMediaPlayer(playlistItem.audioEvent.content) @@ -240,7 +250,6 @@ class VoiceBroadcastPlayerImpl @Inject constructor( mp.seekTo(sequencePosition) } - currentVoiceBroadcast?.let { playbackTicker.startPlaybackTicker(it.voiceBroadcastId) } onNextMediaPlayerStarted(mp) } catch (failure: VoiceBroadcastFailure.ListeningError) { playingState = State.Error(failure) From 003c2cd3dad97342ed8ebf6b95e2ec10c54a9fa8 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 27 Jan 2023 17:56:42 +0100 Subject: [PATCH 086/189] changelog --- changelog.d/8012.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/8012.bugfix diff --git a/changelog.d/8012.bugfix b/changelog.d/8012.bugfix new file mode 100644 index 0000000000..bd2ee3dd08 --- /dev/null +++ b/changelog.d/8012.bugfix @@ -0,0 +1 @@ +[Voice Broadcast] Use internal playback timer to compute the current playback position From 3d87b796b46441e1c08fd8bd772565ecce587715 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 27 Jan 2023 18:01:22 +0100 Subject: [PATCH 087/189] Fix lint --- tools/check/forbidden_strings_in_code.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/check/forbidden_strings_in_code.txt b/tools/check/forbidden_strings_in_code.txt index b4d7ebae1f..d12ace911b 100755 --- a/tools/check/forbidden_strings_in_code.txt +++ b/tools/check/forbidden_strings_in_code.txt @@ -176,7 +176,7 @@ PreferenceManager\.getDefaultSharedPreferences==2 R\.string\.template_ ### Use the Clock interface, or use `measureTimeMillis` -System\.currentTimeMillis\(\)===2 +System\.currentTimeMillis\(\)===3 ### Remove extra space between the name and the description \* @\w+ \w+ + From ff07cad64524d0f816f7ee04d319839831ae6253 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 30 Jan 2023 10:28:48 +0100 Subject: [PATCH 088/189] Remove useless log --- .../voicebroadcast/model/MessageVoiceBroadcastInfoContent.kt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/model/MessageVoiceBroadcastInfoContent.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/model/MessageVoiceBroadcastInfoContent.kt index d882d4049e..333e211772 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/model/MessageVoiceBroadcastInfoContent.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/model/MessageVoiceBroadcastInfoContent.kt @@ -23,7 +23,6 @@ import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageType.MSGTYPE_VOICE_BROADCAST_INFO import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent -import timber.log.Timber /** * Content of the state event of type [VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO]. @@ -50,8 +49,4 @@ data class MessageVoiceBroadcastInfoContent( val voiceBroadcastState: VoiceBroadcastState? = VoiceBroadcastState.values() .find { it.value == voiceBroadcastStateStr } - ?: run { - Timber.w("Invalid value for state: `$voiceBroadcastStateStr`") - null - } } From 44266bcb67afe562846429425e4a7fedeb8eaf7d Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 30 Jan 2023 11:17:06 +0100 Subject: [PATCH 089/189] Increase touch area of the seek bar --- ...item_timeline_event_voice_broadcast_listening_stub.xml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml b/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml index de9b92884c..037e9ee67a 100644 --- a/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_voice_broadcast_listening_stub.xml @@ -147,9 +147,11 @@ android:id="@+id/seekBar" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginTop="24dp" + android:layout_marginTop="20dp" android:paddingStart="0dp" + android:paddingTop="4dp" android:paddingEnd="0dp" + android:paddingBottom="4dp" android:progressDrawable="@drawable/bg_seek_bar" android:thumbOffset="3dp" android:thumbTint="?vctr_content_secondary" @@ -164,7 +166,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="4dp" - android:layout_marginTop="-3dp" + android:layout_marginTop="-7dp" android:textColor="?vctr_content_tertiary" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/seekBar" @@ -176,7 +178,7 @@ style="@style/Widget.Vector.TextView.Caption" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginTop="-3dp" + android:layout_marginTop="-7dp" android:layout_marginEnd="4dp" android:textColor="?vctr_content_tertiary" app:layout_constraintEnd_toEndOf="parent" From 15c610f9ad2861de8f8e1bf259c725fe54510423 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 30 Jan 2023 12:11:32 +0100 Subject: [PATCH 090/189] remove useless code --- .../voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt index 6efa41822e..2be4072bad 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt @@ -282,8 +282,6 @@ class VoiceBroadcastPlayerImpl @Inject constructor( playbackTracker.updatePausedAtPlaybackTime(voiceBroadcast.voiceBroadcastId, positionMillis, positionMillis.toFloat() / duration) } playingState == State.Playing || playingState == State.Buffering -> { - stopPlayer() - playbackTracker.updatePlayingAtPlaybackTime(voiceBroadcast.voiceBroadcastId, positionMillis, positionMillis.toFloat() / duration) startPlayback(positionMillis) } playingState == State.Idle || playingState == State.Paused -> { From d5bd05d0bbdc907bab0005060fb44e49c04414b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Mon, 30 Jan 2023 12:10:28 +0100 Subject: [PATCH 091/189] Fix: cannot select text in plain text mode in Rich Text Editor --- changelog.d/7801.bugfix | 1 + library/ui-styles/src/main/res/values/styles_edit_text.xml | 1 - .../home/room/detail/composer/RichTextComposerLayout.kt | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 changelog.d/7801.bugfix diff --git a/changelog.d/7801.bugfix b/changelog.d/7801.bugfix new file mode 100644 index 0000000000..4ab8d7feda --- /dev/null +++ b/changelog.d/7801.bugfix @@ -0,0 +1 @@ +Cannot select text properly in plain text mode when using Rich Text Editor. 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 6b282a7674..abb180ad87 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 @@ -15,7 +15,6 @@ @android:color/transparent textCapSentences|textMultiLine 10 - 40dp 10dp 10dp 12dp diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/RichTextComposerLayout.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/RichTextComposerLayout.kt index 1bb82b41fe..b60b1ca843 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/RichTextComposerLayout.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/RichTextComposerLayout.kt @@ -291,7 +291,7 @@ internal class RichTextComposerLayout @JvmOverloads constructor( private fun updateEditTextVisibility() { views.richTextComposerEditText.isVisible = isTextFormattingEnabled - views.richTextMenu.isVisible = isTextFormattingEnabled + views.richTextMenuScrollView.isVisible = isTextFormattingEnabled views.plainTextComposerEditText.isVisible = !isTextFormattingEnabled // The layouts for formatted text mode and plain text mode are different, so we need to update the constraints From 7bef90109d994ebf723cd531f98bca36029fa17e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 30 Jan 2023 12:22:05 +0100 Subject: [PATCH 092/189] Changelog --- changelog.d/8031.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/8031.bugfix diff --git a/changelog.d/8031.bugfix b/changelog.d/8031.bugfix new file mode 100644 index 0000000000..0e7ff28509 --- /dev/null +++ b/changelog.d/8031.bugfix @@ -0,0 +1 @@ +Do not send any request to Posthog if no consent is provided. From c8277e2d4359db9498a53d8a84cdd192900318ca Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 30 Jan 2023 12:22:31 +0100 Subject: [PATCH 093/189] Posthog: flush queue before optin out. --- .../app/features/analytics/impl/DefaultVectorAnalytics.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/analytics/impl/DefaultVectorAnalytics.kt b/vector/src/main/java/im/vector/app/features/analytics/impl/DefaultVectorAnalytics.kt index 20712c81b6..917c3468c6 100644 --- a/vector/src/main/java/im/vector/app/features/analytics/impl/DefaultVectorAnalytics.kt +++ b/vector/src/main/java/im/vector/app/features/analytics/impl/DefaultVectorAnalytics.kt @@ -156,6 +156,8 @@ class DefaultVectorAnalytics @Inject constructor( pendingUserProperties = null } false -> { + // When opting out, ensure that the queue is flushed first, or it will be flushed later (after user has revoked consent) + posthog?.flush() posthog?.optOut(true) } } From 7bb7a627c45d4aaaed3c523d77923a48a3b0efe9 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 30 Jan 2023 14:12:09 +0100 Subject: [PATCH 094/189] Change variable to immutable --- .../main/java/im/vector/lib/core/utils/timer/CountUpTimer.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/CountUpTimer.kt b/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/CountUpTimer.kt index debb1d0f4b..0be20675af 100644 --- a/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/CountUpTimer.kt +++ b/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/CountUpTimer.kt @@ -34,7 +34,7 @@ class CountUpTimer(initialTime: Long = 0L, private val intervalInMs: Long = 1_00 private val resumed: AtomicBoolean = AtomicBoolean(false) private val clock: Clock = DefaultClock() - private var lastTime: AtomicLong = AtomicLong() + private val lastTime: AtomicLong = AtomicLong() private val elapsedTime: AtomicLong = AtomicLong(initialTime) init { From 307ac4060e86d801a59fefca224d00806a8b0fee Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 30 Jan 2023 14:17:16 +0100 Subject: [PATCH 095/189] Posthog: fix test. User consent must be provided to touch Posthog API. --- .../app/features/analytics/impl/DefaultVectorAnalyticsTest.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vector/src/test/java/im/vector/app/features/analytics/impl/DefaultVectorAnalyticsTest.kt b/vector/src/test/java/im/vector/app/features/analytics/impl/DefaultVectorAnalyticsTest.kt index 3fd0528a19..084ee2410e 100644 --- a/vector/src/test/java/im/vector/app/features/analytics/impl/DefaultVectorAnalyticsTest.kt +++ b/vector/src/test/java/im/vector/app/features/analytics/impl/DefaultVectorAnalyticsTest.kt @@ -97,6 +97,7 @@ class DefaultVectorAnalyticsTest { @Test fun `given lateinit user properties when valid analytics id updates then identify with lateinit properties`() = runTest { fakeLateInitUserPropertiesFactory.givenCreatesProperties(A_LATE_INIT_USER_PROPERTIES) + fakeAnalyticsStore.givenUserContent(true) fakeAnalyticsStore.givenAnalyticsId(AN_ANALYTICS_ID) @@ -106,6 +107,7 @@ class DefaultVectorAnalyticsTest { @Test fun `when signing out then resets posthog and closes Sentry`() = runTest { fakeAnalyticsStore.allowSettingAnalyticsIdToCallBackingFlow() + fakeAnalyticsStore.givenUserContent(true) defaultVectorAnalytics.onSignOut() From ecc0cb5f0bb7ac32124be744b7cbd9d0a550d1cb Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 30 Jan 2023 14:25:02 +0100 Subject: [PATCH 096/189] CountUpTimer.elapsedTime now returns updated value --- .../lib/core/utils/timer/CountUpTimer.kt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/CountUpTimer.kt b/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/CountUpTimer.kt index 0be20675af..c96b51a85d 100644 --- a/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/CountUpTimer.kt +++ b/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/CountUpTimer.kt @@ -44,7 +44,7 @@ class CountUpTimer(initialTime: Long = 0L, private val intervalInMs: Long = 1_00 private fun startCounter() { tickerFlow(coroutineScope, intervalInMs) .filter { resumed.get() } - .map { addAndGetElapsedTime() } + .map { elapsedTime() } .onEach { tickListener?.onTick(it) } .launchIn(coroutineScope) } @@ -52,11 +52,16 @@ class CountUpTimer(initialTime: Long = 0L, private val intervalInMs: Long = 1_00 var tickListener: TickListener? = null fun elapsedTime(): Long { - return elapsedTime.get() + return if (resumed.get()) { + val now = clock.epochMillis() + elapsedTime.addAndGet(now - lastTime.getAndSet(now)) + } else { + elapsedTime.get() + } } fun pause() { - tickListener?.onTick(addAndGetElapsedTime()) + tickListener?.onTick(elapsedTime()) resumed.set(false) } @@ -66,15 +71,10 @@ class CountUpTimer(initialTime: Long = 0L, private val intervalInMs: Long = 1_00 } fun stop() { - tickListener?.onTick(addAndGetElapsedTime()) + tickListener?.onTick(elapsedTime()) coroutineScope.cancel() } - private fun addAndGetElapsedTime(): Long { - val now = clock.epochMillis() - return elapsedTime.addAndGet(now - lastTime.getAndSet(now)) - } - fun interface TickListener { fun onTick(milliseconds: Long) } From 3ce757ee4bd52b6ab945c1bc532a34a67cca7b63 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 30 Jan 2023 14:26:34 +0100 Subject: [PATCH 097/189] Remove useless code since CountUpTimer notify on pause and stop --- .../recording/VoiceBroadcastRecorderQ.kt | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/recording/VoiceBroadcastRecorderQ.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/recording/VoiceBroadcastRecorderQ.kt index 7ca6ab3c9c..5af3b2f05b 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/recording/VoiceBroadcastRecorderQ.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/recording/VoiceBroadcastRecorderQ.kt @@ -248,30 +248,20 @@ class VoiceBroadcastRecorderQ( recordingTicker = CountUpTimer().apply { tickListener = CountUpTimer.TickListener { onTick(elapsedTime()) } resume() - onTick(elapsedTime()) } } fun pause() { - recordingTicker?.apply { - pause() - onTick(elapsedTime()) - } + recordingTicker?.pause() } fun resume() { - recordingTicker?.apply { - resume() - onTick(elapsedTime()) - } + recordingTicker?.resume() } fun stop() { - recordingTicker?.apply { - stop() - onTick(elapsedTime()) - recordingTicker = null - } + recordingTicker?.stop() + recordingTicker = null } private fun onTick(elapsedTimeMillis: Long) { From a0ab6de85aff6ba88360935b1bcf2c02d85c9ee8 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 30 Jan 2023 15:21:47 +0100 Subject: [PATCH 098/189] Do not enter in error state in case of cancellation exception --- .../voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt index 2be4072bad..61e4bf640c 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt @@ -252,7 +252,9 @@ class VoiceBroadcastPlayerImpl @Inject constructor( onNextMediaPlayerStarted(mp) } catch (failure: VoiceBroadcastFailure.ListeningError) { - playingState = State.Error(failure) + if (failure.cause !is CancellationException) { + playingState = State.Error(failure) + } } } } @@ -313,7 +315,7 @@ class VoiceBroadcastPlayerImpl @Inject constructor( } catch (failure: VoiceBroadcastFailure.ListeningError) { // Do not change the playingState if the current player is still valid, // the error will be thrown again when switching to the next player - if (playingState == State.Buffering || tryOrNull { currentMediaPlayer?.isPlaying } != true) { + if (failure.cause !is CancellationException && (playingState == State.Buffering || tryOrNull { currentMediaPlayer?.isPlaying } != true)) { playingState = State.Error(failure) } } From 66e8b5bf7a99dc59cffaec34ecb8fd6439c0114c Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 30 Jan 2023 17:04:27 +0100 Subject: [PATCH 099/189] Remove useless Flow observation --- .../usecase/GetLiveVoiceBroadcastChunksUseCase.kt | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/usecase/GetLiveVoiceBroadcastChunksUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/usecase/GetLiveVoiceBroadcastChunksUseCase.kt index b2aebd9932..6f7444849a 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/usecase/GetLiveVoiceBroadcastChunksUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/usecase/GetLiveVoiceBroadcastChunksUseCase.kt @@ -24,17 +24,15 @@ import im.vector.app.features.voicebroadcast.model.VoiceBroadcastEvent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import im.vector.app.features.voicebroadcast.sequence -import im.vector.app.features.voicebroadcast.usecase.GetVoiceBroadcastStateEventLiveUseCase +import im.vector.app.features.voicebroadcast.usecase.GetVoiceBroadcastStateEventUseCase import im.vector.app.features.voicebroadcast.voiceBroadcastId import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.emptyFlow -import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.runningReduce -import kotlinx.coroutines.runBlocking import org.matrix.android.sdk.api.session.events.model.RelationType import org.matrix.android.sdk.api.session.room.model.message.MessageAudioEvent import org.matrix.android.sdk.api.session.room.model.message.asMessageAudioEvent @@ -48,7 +46,7 @@ import javax.inject.Inject */ class GetLiveVoiceBroadcastChunksUseCase @Inject constructor( private val activeSessionHolder: ActiveSessionHolder, - private val getVoiceBroadcastEventUseCase: GetVoiceBroadcastStateEventLiveUseCase, + private val getVoiceBroadcastEventUseCase: GetVoiceBroadcastStateEventUseCase, ) { fun execute(voiceBroadcast: VoiceBroadcast): Flow> { @@ -60,7 +58,7 @@ class GetLiveVoiceBroadcastChunksUseCase @Inject constructor( val existingChunks = room.timelineService().getTimelineEventsRelatedTo(RelationType.REFERENCE, voiceBroadcast.voiceBroadcastId) .mapNotNull { timelineEvent -> timelineEvent.root.asMessageAudioEvent().takeIf { it.isVoiceBroadcast() } } - val voiceBroadcastEvent = runBlocking { getVoiceBroadcastEventUseCase.execute(voiceBroadcast).firstOrNull()?.getOrNull() } + val voiceBroadcastEvent = getVoiceBroadcastEventUseCase.execute(voiceBroadcast) val voiceBroadcastState = voiceBroadcastEvent?.content?.voiceBroadcastState return if (voiceBroadcastState == null || voiceBroadcastState == VoiceBroadcastState.STOPPED) { From 00f9c362da7bcf5be9c24e63b31718a58ee49dce Mon Sep 17 00:00:00 2001 From: jonnyandrew Date: Mon, 30 Jan 2023 17:35:29 +0000 Subject: [PATCH 100/189] [Rich text editor] Add inline code to rich text editor (#8011) Also: - Fixes https://github.com/vector-im/element-android/issues/7975 - See https://github.com/noties/Markwon/issues/423 --- changelog.d/7975.bugfix | 1 + changelog.d/8011.feature | 1 + .../src/main/res/values/strings.xml | 1 + .../java/im/vector/app/core/utils/TestSpan.kt | 33 +++- .../features/html/EventHtmlRendererTest.kt | 26 ++- .../detail/composer/RichTextComposerLayout.kt | 3 + .../timeline/factory/MessageItemFactory.kt | 9 +- .../detail/timeline/item/MessageTextItem.kt | 34 ++-- .../app/features/html/EventHtmlRenderer.kt | 184 ++++++++++++------ .../app/features/html/HtmlCodeHandlers.kt | 72 +++++-- .../app/features/html/HtmlRootTagPlugin.kt | 33 ++++ .../res/drawable/ic_composer_inline_code.xml | 15 ++ ...timeline_event_text_message_plain_stub.xml | 11 ++ ..._timeline_event_text_message_rich_stub.xml | 11 ++ .../item_timeline_event_text_message_stub.xml | 15 +- 15 files changed, 339 insertions(+), 110 deletions(-) create mode 100644 changelog.d/7975.bugfix create mode 100644 changelog.d/8011.feature create mode 100644 vector/src/main/java/im/vector/app/features/html/HtmlRootTagPlugin.kt create mode 100644 vector/src/main/res/drawable/ic_composer_inline_code.xml create mode 100644 vector/src/main/res/layout/item_timeline_event_text_message_plain_stub.xml create mode 100644 vector/src/main/res/layout/item_timeline_event_text_message_rich_stub.xml diff --git a/changelog.d/7975.bugfix b/changelog.d/7975.bugfix new file mode 100644 index 0000000000..b34c784b27 --- /dev/null +++ b/changelog.d/7975.bugfix @@ -0,0 +1 @@ +Fix extra new lines added to inline code diff --git a/changelog.d/8011.feature b/changelog.d/8011.feature new file mode 100644 index 0000000000..700a528fc1 --- /dev/null +++ b/changelog.d/8011.feature @@ -0,0 +1 @@ +[Rich text editor] Add inline code to rich text editor \ No newline at end of file diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 46c175437a..e690f06bbb 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3502,6 +3502,7 @@ Set link Toggle numbered list Toggle bullet list + Apply inline code format Toggle full screen mode Text diff --git a/vector/src/androidTest/java/im/vector/app/core/utils/TestSpan.kt b/vector/src/androidTest/java/im/vector/app/core/utils/TestSpan.kt index e31dc6942c..1d0d6548e1 100644 --- a/vector/src/androidTest/java/im/vector/app/core/utils/TestSpan.kt +++ b/vector/src/androidTest/java/im/vector/app/core/utils/TestSpan.kt @@ -19,9 +19,10 @@ package im.vector.app.core.utils import android.graphics.Canvas import android.graphics.Paint import android.text.Layout -import android.text.Spannable +import android.text.Spanned import androidx.core.text.getSpans import im.vector.app.features.html.HtmlCodeSpan +import io.element.android.wysiwyg.spans.InlineCodeSpan import io.mockk.justRun import io.mockk.mockk import io.mockk.slot @@ -31,9 +32,9 @@ import io.noties.markwon.core.spans.OrderedListItemSpan import io.noties.markwon.core.spans.StrongEmphasisSpan import me.gujun.android.span.style.CustomTypefaceSpan -fun Spannable.toTestSpan(): String { +fun Spanned.toTestSpan(): String { var output = toString() - readSpansWithContent().forEach { + readSpansWithContent().reversed().forEach { val tags = it.span.readTags() val remappedContent = it.span.remapContent(source = this, originalContent = it.content) output = output.replace(it.content, "${tags.open}$remappedContent${tags.close}") @@ -41,7 +42,7 @@ fun Spannable.toTestSpan(): String { return output } -private fun Spannable.readSpansWithContent() = getSpans().map { span -> +private fun Spanned.readSpansWithContent() = getSpans().map { span -> val start = getSpanStart(span) val end = getSpanEnd(span) SpanWithContent( @@ -51,12 +52,24 @@ private fun Spannable.readSpansWithContent() = getSpans().map { span -> }.reversed() private fun Any.readTags(): SpanTags { - return when (this::class) { - OrderedListItemSpan::class -> SpanTags("[list item]", "[/list item]") - HtmlCodeSpan::class -> SpanTags("[code]", "[/code]") - StrongEmphasisSpan::class -> SpanTags("[bold]", "[/bold]") - EmphasisSpan::class, CustomTypefaceSpan::class -> SpanTags("[italic]", "[/italic]") - else -> throw IllegalArgumentException("Unknown ${this::class}") + val tagName = when (this::class) { + OrderedListItemSpan::class -> "list item" + HtmlCodeSpan::class -> + if ((this as HtmlCodeSpan).isBlock) "code block" else "inline code" + StrongEmphasisSpan::class -> "bold" + EmphasisSpan::class, CustomTypefaceSpan::class -> "italic" + InlineCodeSpan::class -> "inline code" + else -> if (this::class.qualifiedName!!.startsWith("android.widget")) { + null + } else { + throw IllegalArgumentException("Unknown ${this::class}") + } + } + + return if (tagName == null) { + SpanTags("", "") + } else { + SpanTags("[$tagName]", "[/$tagName]") } } diff --git a/vector/src/androidTest/java/im/vector/app/features/html/EventHtmlRendererTest.kt b/vector/src/androidTest/java/im/vector/app/features/html/EventHtmlRendererTest.kt index a2e489dd70..7f3293e7d1 100644 --- a/vector/src/androidTest/java/im/vector/app/features/html/EventHtmlRendererTest.kt +++ b/vector/src/androidTest/java/im/vector/app/features/html/EventHtmlRendererTest.kt @@ -16,7 +16,8 @@ package im.vector.app.features.html -import androidx.core.text.toSpannable +import android.widget.TextView +import androidx.core.text.toSpanned import androidx.test.platform.app.InstrumentationRegistry import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.resources.ColorProvider @@ -36,16 +37,19 @@ class EventHtmlRendererTest { private val context = InstrumentationRegistry.getInstrumentation().targetContext private val fakeVectorPreferences = mockk().also { every { it.latexMathsIsEnabled() } returns false + every { it.isRichTextEditorEnabled() } returns false } private val fakeSessionHolder = mockk() private val renderer = EventHtmlRenderer( - MatrixHtmlPluginConfigure(ColorProvider(context), context.resources), + MatrixHtmlPluginConfigure(ColorProvider(context), context.resources, fakeVectorPreferences), context, fakeVectorPreferences, fakeSessionHolder, ) + private val textView: TextView = TextView(context) + @Test fun takesInitialListPositionIntoAccount() { val result = """
  1. first entry
""".renderAsTestSpan() @@ -57,7 +61,7 @@ class EventHtmlRendererTest { fun doesNotProcessMarkdownWithinCodeBlocks() { val result = """__italic__ **bold**""".renderAsTestSpan() - result shouldBeEqualTo "[code]__italic__ **bold**[/code]" + result shouldBeEqualTo "[inline code]__italic__ **bold**[/inline code]" } @Test @@ -71,7 +75,15 @@ class EventHtmlRendererTest { fun processesHtmlWithinCodeBlocks() { val result = """italic bold""".renderAsTestSpan() - result shouldBeEqualTo "[code][italic]italic[/italic] [bold]bold[/bold][/code]" + result shouldBeEqualTo "[inline code][italic]italic[/italic] [bold]bold[/bold][/inline code]" + } + + @Test + fun processesHtmlWithinCodeBlocks_givenRichTextEditorEnabled() { + every { fakeVectorPreferences.isRichTextEditorEnabled() } returns true + val result = """italic bold""".renderAsTestSpan() + + result shouldBeEqualTo "[inline code][italic]italic[/italic] [bold]bold[/bold][/inline code]" } @Test @@ -81,5 +93,9 @@ class EventHtmlRendererTest { result shouldBeEqualTo """& < > ' """" } - private fun String.renderAsTestSpan() = renderer.render(this).toSpannable().toTestSpan() + private fun String.renderAsTestSpan(): String { + textView.text = renderer.render(this).toSpanned() + renderer.plugins.forEach { markwonPlugin -> markwonPlugin.afterSetText(textView) } + return textView.text.toSpanned().toTestSpan() + } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/RichTextComposerLayout.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/RichTextComposerLayout.kt index 1bb82b41fe..2c0d77045e 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/RichTextComposerLayout.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/RichTextComposerLayout.kt @@ -246,6 +246,9 @@ internal class RichTextComposerLayout @JvmOverloads constructor( addRichTextMenuItem(R.drawable.ic_composer_numbered_list, R.string.rich_text_editor_numbered_list, ComposerAction.ORDERED_LIST) { views.richTextComposerEditText.toggleList(ordered = true) } + addRichTextMenuItem(R.drawable.ic_composer_inline_code, R.string.rich_text_editor_inline_code, ComposerAction.INLINE_CODE) { + views.richTextComposerEditText.toggleInlineFormat(InlineFormat.InlineCode) + } } fun setLink(link: String?) = 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 219ccbe11c..9cb1608415 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 @@ -160,6 +160,9 @@ class MessageItemFactory @Inject constructor( textRendererFactory.create(roomId) } + private val useRichTextEditorStyle: Boolean get() = + vectorPreferences.isRichTextEditorEnabled() + fun create(params: TimelineItemFactoryParams): VectorEpoxyModel<*>? { val event = params.event val highlight = params.isHighlighted @@ -480,6 +483,7 @@ class MessageItemFactory @Inject constructor( highlight, callback, attributes, + useRichTextEditorStyle = vectorPreferences.isRichTextEditorEnabled(), ) } @@ -586,7 +590,7 @@ class MessageItemFactory @Inject constructor( val replyToContent = messageContent.relatesTo?.inReplyTo buildFormattedTextItem(matrixFormattedBody, informationData, highlight, callback, attributes, replyToContent) } else { - buildMessageTextItem(messageContent.body, false, informationData, highlight, callback, attributes) + buildMessageTextItem(messageContent.body, false, informationData, highlight, callback, attributes, useRichTextEditorStyle) } } @@ -610,6 +614,7 @@ class MessageItemFactory @Inject constructor( highlight, callback, attributes, + useRichTextEditorStyle, ) } @@ -620,6 +625,7 @@ class MessageItemFactory @Inject constructor( highlight: Boolean, callback: TimelineEventController.Callback?, attributes: AbsMessageItem.Attributes, + useRichTextEditorStyle: Boolean, ): MessageTextItem? { val renderedBody = textRenderer.render(body) val bindingOptions = spanUtils.getBindingOptions(renderedBody) @@ -640,6 +646,7 @@ class MessageItemFactory @Inject constructor( .previewUrlRetriever(callback?.getPreviewUrlRetriever()) .imageContentRenderer(imageContentRenderer) .previewUrlCallback(callback) + .useRichTextEditorStyle(useRichTextEditorStyle) .leftGuideline(avatarSizeProvider.leftGuideline) .attributes(attributes) .highlighted(highlight) 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 072c3dcd27..a9cd25ae19 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 @@ -18,6 +18,7 @@ package im.vector.app.features.home.room.detail.timeline.item import android.text.Spanned import android.text.method.MovementMethod +import android.view.ViewStub import androidx.appcompat.widget.AppCompatTextView import androidx.core.text.PrecomputedTextCompat import androidx.core.view.isVisible @@ -67,6 +68,9 @@ abstract class MessageTextItem : AbsMessageItem() { @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var markwonPlugins: (List)? = null + @EpoxyAttribute + var useRichTextEditorStyle: Boolean = false + private val previewUrlViewUpdater = PreviewUrlViewUpdater() override fun bind(holder: Holder) { @@ -82,27 +86,28 @@ abstract class MessageTextItem : AbsMessageItem() { holder.previewUrlView.delegate = previewUrlCallback holder.previewUrlView.renderMessageLayout(attributes.informationData.messageLayout) + val messageView: AppCompatTextView = if (useRichTextEditorStyle) holder.richMessageView else holder.plainMessageView if (useBigFont) { - holder.messageView.textSize = 44F + messageView.textSize = 44F } else { - holder.messageView.textSize = 15.5F + messageView.textSize = 15.5F } if (searchForPills) { message?.charSequence?.findPillsAndProcess(coroutineScope) { // mmm.. not sure this is so safe in regards to cell reuse - it.bind(holder.messageView) + it.bind(messageView) } } message?.charSequence.let { charSequence -> - markwonPlugins?.forEach { plugin -> plugin.beforeSetText(holder.messageView, charSequence as Spanned) } + markwonPlugins?.forEach { plugin -> plugin.beforeSetText(messageView, charSequence as Spanned) } } super.bind(holder) - holder.messageView.movementMethod = movementMethod - renderSendState(holder.messageView, holder.messageView) - holder.messageView.onClick(attributes.itemClickListener) - holder.messageView.onLongClickIgnoringLinks(attributes.itemLongClickListener) - holder.messageView.setTextWithEmojiSupport(message?.charSequence, bindingOptions) - markwonPlugins?.forEach { plugin -> plugin.afterSetText(holder.messageView) } + messageView.movementMethod = movementMethod + renderSendState(messageView, messageView) + messageView.onClick(attributes.itemClickListener) + messageView.onLongClickIgnoringLinks(attributes.itemLongClickListener) + messageView.setTextWithEmojiSupport(message?.charSequence, bindingOptions) + markwonPlugins?.forEach { plugin -> plugin.afterSetText(messageView) } } private fun AppCompatTextView.setTextWithEmojiSupport(message: CharSequence?, bindingOptions: BindingOptions?) { @@ -125,8 +130,15 @@ abstract class MessageTextItem : AbsMessageItem() { override fun getViewStubId() = STUB_ID class Holder : AbsMessageItem.Holder(STUB_ID) { - val messageView by bind(R.id.messageTextView) val previewUrlView by bind(R.id.messageUrlPreview) + private val richMessageStub by bind(R.id.richMessageTextViewStub) + private val plainMessageStub by bind(R.id.plainMessageTextViewStub) + val richMessageView: AppCompatTextView by lazy { + richMessageStub.inflate().findViewById(R.id.messageTextView) + } + val plainMessageView: AppCompatTextView by lazy { + plainMessageStub.inflate().findViewById(R.id.messageTextView) + } } inner class PreviewUrlViewUpdater : PreviewUrlRetriever.PreviewUrlRetrieverListener { diff --git a/vector/src/main/java/im/vector/app/features/html/EventHtmlRenderer.kt b/vector/src/main/java/im/vector/app/features/html/EventHtmlRenderer.kt index 21fcbffb03..bc9ba0b85a 100644 --- a/vector/src/main/java/im/vector/app/features/html/EventHtmlRenderer.kt +++ b/vector/src/main/java/im/vector/app/features/html/EventHtmlRenderer.kt @@ -30,6 +30,8 @@ import android.content.res.Resources import android.graphics.Typeface import android.graphics.drawable.Drawable import android.text.Spannable +import android.text.SpannableStringBuilder +import android.widget.TextView import androidx.core.text.toSpannable import com.bumptech.glide.Glide import com.bumptech.glide.RequestBuilder @@ -38,6 +40,7 @@ import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.resources.ColorProvider import im.vector.app.core.utils.DimensionConverter import im.vector.app.features.settings.VectorPreferences +import io.element.android.wysiwyg.spans.InlineCodeSpan import io.noties.markwon.AbstractMarkwonPlugin import io.noties.markwon.Markwon import io.noties.markwon.MarkwonPlugin @@ -64,8 +67,8 @@ import javax.inject.Singleton @Singleton class EventHtmlRenderer @Inject constructor( htmlConfigure: MatrixHtmlPluginConfigure, - context: Context, - vectorPreferences: VectorPreferences, + private val context: Context, + private val vectorPreferences: VectorPreferences, private val activeSessionHolder: ActiveSessionHolder ) { @@ -73,73 +76,121 @@ class EventHtmlRenderer @Inject constructor( fun afterRender(renderedText: Spannable) } - private val builder = Markwon.builder(context) - .usePlugin(HtmlPlugin.create(htmlConfigure)) - .usePlugin(GlideImagesPlugin.create(object : GlideImagesPlugin.GlideStore { - override fun load(drawable: AsyncDrawable): RequestBuilder { - val url = drawable.destination - if (url.isMxcUrl()) { - val contentUrlResolver = activeSessionHolder.getActiveSession().contentUrlResolver() - val imageUrl = contentUrlResolver.resolveFullSize(url) - // Override size to avoid crashes for huge pictures - return Glide.with(context).load(imageUrl).override(500) - } - // We don't want to support other url schemes here, so just return a request for null - return Glide.with(context).load(null as String?) - } + private val glidePlugin = GlideImagesPlugin.create(object : GlideImagesPlugin.GlideStore { + override fun load(drawable: AsyncDrawable): RequestBuilder { + val url = drawable.destination + if (url.isMxcUrl()) { + val contentUrlResolver = activeSessionHolder.getActiveSession().contentUrlResolver() + val imageUrl = contentUrlResolver.resolveFullSize(url) + // Override size to avoid crashes for huge pictures + return Glide.with(context).load(imageUrl).override(500) + } + // We don't want to support other url schemes here, so just return a request for null + return Glide.with(context).load(null as String?) + } - override fun cancel(target: Target<*>) { - Glide.with(context).clear(target) - } - })) + override fun cancel(target: Target<*>) { + Glide.with(context).clear(target) + } + }) - private val markwon = if (vectorPreferences.latexMathsIsEnabled()) { - // If latex maths is enabled in app preferences, refomat it so Markwon recognises it - // It needs to be in this specific format: https://noties.io/Markwon/docs/v4/ext-latex - builder - .usePlugin(object : AbstractMarkwonPlugin() { - override fun processMarkdown(markdown: String): String { - return markdown - .replace(Regex(""".*?""")) { matchResult -> - "$$" + matchResult.groupValues[1] + "$$" - } - .replace(Regex(""".*?""")) { matchResult -> - "\n$$\n" + matchResult.groupValues[1] + "\n$$\n" - } - } - }) - .usePlugin(JLatexMathPlugin.create(44F) { builder -> - builder.inlinesEnabled(true) - builder.theme().inlinePadding(JLatexMathTheme.Padding.symmetric(24, 8)) - }) - } else { - builder - } - .usePlugin( - MarkwonInlineParserPlugin.create( - /* Configuring the Markwon inline formatting processor. - * Default settings are all Markdown features. Turn those off, only using the - * inline HTML processor and HTML entities processor. - */ - MarkwonInlineParser.factoryBuilderNoDefaults() - .addInlineProcessor(HtmlInlineProcessor()) // use inline HTML processor - .addInlineProcessor(EntityInlineProcessor()) // use HTML entities processor - ) - ) - .usePlugin(object : AbstractMarkwonPlugin() { - override fun configureSpansFactory(builder: MarkwonSpansFactory.Builder) { - builder.setFactory( - Emphasis::class.java - ) { _, _ -> CustomTypefaceSpan(Typeface.create(Typeface.DEFAULT, Typeface.ITALIC)) } + private val latexPlugins = listOf( + object : AbstractMarkwonPlugin() { + override fun processMarkdown(markdown: String): String { + return markdown + .replace(Regex(""".*?""")) { matchResult -> + "$$" + matchResult.groupValues[1] + "$$" + } + .replace(Regex(""".*?""")) { matchResult -> + "\n$$\n" + matchResult.groupValues[1] + "\n$$\n" + } } + }, + JLatexMathPlugin.create(44F) { builder -> + builder.inlinesEnabled(true) + builder.theme().inlinePadding(JLatexMathTheme.Padding.symmetric(24, 8)) + } + ) - override fun configureParser(builder: Parser.Builder) { - /* Configuring the Markwon block formatting processor. - * Default settings are all Markdown blocks. Turn those off. + private val markwonInlineParserPlugin = + MarkwonInlineParserPlugin.create( + /* Configuring the Markwon inline formatting processor. + * Default settings are all Markdown features. Turn those off, only using the + * inline HTML processor and HTML entities processor. */ - builder.enabledBlockTypes(kotlin.collections.emptySet()) + MarkwonInlineParser.factoryBuilderNoDefaults() + .addInlineProcessor(HtmlInlineProcessor()) // use inline HTML processor + .addInlineProcessor(EntityInlineProcessor()) // use HTML entities processor + ) + + private val italicPlugin = object : AbstractMarkwonPlugin() { + override fun configureSpansFactory(builder: MarkwonSpansFactory.Builder) { + builder.setFactory( + Emphasis::class.java + ) { _, _ -> CustomTypefaceSpan(Typeface.create(Typeface.DEFAULT, Typeface.ITALIC)) } + } + + override fun configureParser(builder: Parser.Builder) { + /* Configuring the Markwon block formatting processor. + * Default settings are all Markdown blocks. Turn those off. + */ + builder.enabledBlockTypes(emptySet()) + } + } + + private val cleanUpIntermediateCodePlugin = object : AbstractMarkwonPlugin() { + override fun afterSetText(textView: TextView) { + super.afterSetText(textView) + + // Remove any intermediate spans + val text = textView.text.toSpannable() + text.getSpans(0, text.length, IntermediateCodeSpan::class.java) + .forEach { span -> + text.removeSpan(span) + } + } + } + + /** + * Workaround for https://github.com/noties/Markwon/issues/423 + */ + private val removeLeadingNewlineForInlineCode = object : AbstractMarkwonPlugin() { + override fun afterSetText(textView: TextView) { + super.afterSetText(textView) + + val text = SpannableStringBuilder(textView.text.toSpannable()) + val inlineCodeSpans = text.getSpans(0, textView.length(), InlineCodeSpan::class.java).toList() + val legacyInlineCodeSpans = text.getSpans(0, textView.length(), HtmlCodeSpan::class.java).filter { !it.isBlock } + val spans = inlineCodeSpans + legacyInlineCodeSpans + + if (spans.isEmpty()) return + + spans.forEach { span -> + val start = text.getSpanStart(span) + if (text[start] == '\n') { + text.replace(start, start + 1, "") } - }) + } + + textView.text = text + } + } + + private val markwon = Markwon.builder(context) + .usePlugin(HtmlRootTagPlugin()) + .usePlugin(HtmlPlugin.create(htmlConfigure)) + .usePlugin(removeLeadingNewlineForInlineCode) + .usePlugin(glidePlugin) + .apply { + if (vectorPreferences.latexMathsIsEnabled()) { + // If latex maths is enabled in app preferences, refomat it so Markwon recognises it + // It needs to be in this specific format: https://noties.io/Markwon/docs/v4/ext-latex + latexPlugins.forEach(::usePlugin) + } + } + .usePlugin(markwonInlineParserPlugin) + .usePlugin(italicPlugin) + .usePlugin(cleanUpIntermediateCodePlugin) .textSetter(PrecomputedFutureTextSetterCompat.create()) .build() @@ -185,7 +236,11 @@ class EventHtmlRenderer @Inject constructor( } } -class MatrixHtmlPluginConfigure @Inject constructor(private val colorProvider: ColorProvider, private val resources: Resources) : HtmlPlugin.HtmlConfigure { +class MatrixHtmlPluginConfigure @Inject constructor( + private val colorProvider: ColorProvider, + private val resources: Resources, + private val vectorPreferences: VectorPreferences, +) : HtmlPlugin.HtmlConfigure { override fun configureHtml(plugin: HtmlPlugin) { plugin @@ -193,6 +248,7 @@ class MatrixHtmlPluginConfigure @Inject constructor(private val colorProvider: C .addHandler(FontTagHandler()) .addHandler(ParagraphHandler(DimensionConverter(resources))) .addHandler(MxReplyTagHandler()) + .addHandler(CodePostProcessorTagHandler(vectorPreferences)) .addHandler(CodePreTagHandler()) .addHandler(CodeTagHandler()) .addHandler(SpanHandler(colorProvider)) diff --git a/vector/src/main/java/im/vector/app/features/html/HtmlCodeHandlers.kt b/vector/src/main/java/im/vector/app/features/html/HtmlCodeHandlers.kt index 1010625370..295b74c7a9 100644 --- a/vector/src/main/java/im/vector/app/features/html/HtmlCodeHandlers.kt +++ b/vector/src/main/java/im/vector/app/features/html/HtmlCodeHandlers.kt @@ -16,20 +16,29 @@ package im.vector.app.features.html +import im.vector.app.features.settings.VectorPreferences +import io.element.android.wysiwyg.spans.InlineCodeSpan import io.noties.markwon.MarkwonVisitor import io.noties.markwon.SpannableBuilder +import io.noties.markwon.core.MarkwonTheme import io.noties.markwon.html.HtmlTag import io.noties.markwon.html.MarkwonHtmlRenderer import io.noties.markwon.html.TagHandler -class CodeTagHandler : TagHandler() { +/** + * Span to be added to any found during initial pass. + * The actual code spans can then be added on a second pass using this + * span as a reference. + */ +internal class IntermediateCodeSpan( + var isBlock: Boolean +) + +internal class CodeTagHandler : TagHandler() { override fun handle(visitor: MarkwonVisitor, renderer: MarkwonHtmlRenderer, tag: HtmlTag) { SpannableBuilder.setSpans( - visitor.builder(), - HtmlCodeSpan(visitor.configuration().theme(), false), - tag.start(), - tag.end() + visitor.builder(), IntermediateCodeSpan(isBlock = false), tag.start(), tag.end() ) } @@ -42,15 +51,13 @@ class CodeTagHandler : TagHandler() { * Pre tag are already handled by HtmlPlugin to keep the formatting. * We are only using it to check for
*
tags. */ -class CodePreTagHandler : TagHandler() { +internal class CodePreTagHandler : TagHandler() { override fun handle(visitor: MarkwonVisitor, renderer: MarkwonHtmlRenderer, tag: HtmlTag) { - val htmlCodeSpan = visitor.builder() - .getSpans(tag.start(), tag.end()) - .firstOrNull { - it.what is HtmlCodeSpan - } - if (htmlCodeSpan != null) { - (htmlCodeSpan.what as HtmlCodeSpan).isBlock = true + val codeSpan = visitor.builder().getSpans(tag.start(), tag.end()).firstOrNull { + it.what is IntermediateCodeSpan + } + if (codeSpan != null) { + (codeSpan.what as IntermediateCodeSpan).isBlock = true } } @@ -58,3 +65,42 @@ class CodePreTagHandler : TagHandler() { return listOf("pre") } } + +internal class CodePostProcessorTagHandler( + private val vectorPreferences: VectorPreferences, +) : TagHandler() { + + override fun supportedTags() = listOf(HtmlRootTagPlugin.ROOT_TAG_NAME) + + override fun handle(visitor: MarkwonVisitor, renderer: MarkwonHtmlRenderer, tag: HtmlTag) { + if (tag.attributes()[HtmlRootTagPlugin.ROOT_ATTRIBUTE] == null) { + return + } + + if (tag.isBlock) { + visitChildren(visitor, renderer, tag.asBlock) + } + + // Replace any intermediate code spans with the real formatting spans + visitor.builder() + .getSpans(tag.start(), tag.end()) + .filter { + it.what is IntermediateCodeSpan + }.forEach { code -> + val intermediateCodeSpan = code.what as IntermediateCodeSpan + val theme = visitor.configuration().theme() + val span = intermediateCodeSpan.toFinalCodeSpan(theme) + SpannableBuilder.setSpans( + visitor.builder(), span, code.start, code.end + ) + } + } + + private fun IntermediateCodeSpan.toFinalCodeSpan( + markwonTheme: MarkwonTheme + ): Any = if (vectorPreferences.isRichTextEditorEnabled() && !isBlock) { + InlineCodeSpan() + } else { + HtmlCodeSpan(markwonTheme, isBlock) + } +} diff --git a/vector/src/main/java/im/vector/app/features/html/HtmlRootTagPlugin.kt b/vector/src/main/java/im/vector/app/features/html/HtmlRootTagPlugin.kt new file mode 100644 index 0000000000..59f2cda00b --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/html/HtmlRootTagPlugin.kt @@ -0,0 +1,33 @@ +/* + * 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.html + +import io.noties.markwon.AbstractMarkwonPlugin + +/** + * A root node enables post-processing of optionally nested tags. + * See: [im.vector.app.features.html.CodePostProcessorTagHandler] + */ +internal class HtmlRootTagPlugin : AbstractMarkwonPlugin() { + companion object { + const val ROOT_ATTRIBUTE = "data-root" + const val ROOT_TAG_NAME = "div" + } + override fun processMarkdown(html: String): String { + return "<$ROOT_TAG_NAME $ROOT_ATTRIBUTE>$html" + } +} diff --git a/vector/src/main/res/drawable/ic_composer_inline_code.xml b/vector/src/main/res/drawable/ic_composer_inline_code.xml new file mode 100644 index 0000000000..1743b757af --- /dev/null +++ b/vector/src/main/res/drawable/ic_composer_inline_code.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/vector/src/main/res/layout/item_timeline_event_text_message_plain_stub.xml b/vector/src/main/res/layout/item_timeline_event_text_message_plain_stub.xml new file mode 100644 index 0000000000..1d94632686 --- /dev/null +++ b/vector/src/main/res/layout/item_timeline_event_text_message_plain_stub.xml @@ -0,0 +1,11 @@ + + + diff --git a/vector/src/main/res/layout/item_timeline_event_text_message_rich_stub.xml b/vector/src/main/res/layout/item_timeline_event_text_message_rich_stub.xml new file mode 100644 index 0000000000..bedff8bd4a --- /dev/null +++ b/vector/src/main/res/layout/item_timeline_event_text_message_rich_stub.xml @@ -0,0 +1,11 @@ + + + diff --git a/vector/src/main/res/layout/item_timeline_event_text_message_stub.xml b/vector/src/main/res/layout/item_timeline_event_text_message_stub.xml index 5c5280ad4e..32785a41af 100644 --- a/vector/src/main/res/layout/item_timeline_event_text_message_stub.xml +++ b/vector/src/main/res/layout/item_timeline_event_text_message_stub.xml @@ -7,14 +7,17 @@ android:orientation="vertical" tools:viewBindingIgnore="true"> - + android:layout="@layout/item_timeline_event_text_message_plain_stub" /> + + Date: Mon, 30 Jan 2023 23:00:57 +0000 Subject: [PATCH 101/189] Bump danger/danger-js from 11.2.2 to 11.2.3 Bumps [danger/danger-js](https://github.com/danger/danger-js) from 11.2.2 to 11.2.3. - [Release notes](https://github.com/danger/danger-js/releases) - [Changelog](https://github.com/danger/danger-js/blob/main/CHANGELOG.md) - [Commits](https://github.com/danger/danger-js/compare/11.2.2...11.2.3) --- updated-dependencies: - dependency-name: danger/danger-js dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/danger.yml | 2 +- .github/workflows/quality.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/danger.yml b/.github/workflows/danger.yml index 91352bb27b..a88a5faa9d 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.2 + uses: danger/danger-js@11.2.3 with: args: "--dangerfile ./tools/danger/dangerfile.js" env: diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index e8c56ba67f..b8800ea65b 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.2 + uses: danger/danger-js@11.2.3 with: args: "--dangerfile ./tools/danger/dangerfile-lint.js" env: From 8dae126d4cf095e4f60fc635b45a9a452146b714 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Jan 2023 23:12:40 +0000 Subject: [PATCH 102/189] Bump org.owasp:dependency-check-gradle from 8.0.1 to 8.0.2 Bumps org.owasp:dependency-check-gradle from 8.0.1 to 8.0.2. --- updated-dependencies: - dependency-name: org.owasp:dependency-check-gradle dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 3e8233fa4d..23c403da31 100644 --- a/build.gradle +++ b/build.gradle @@ -29,7 +29,7 @@ buildscript { classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.5.0.2730' 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:8.0.1' + classpath 'org.owasp:dependency-check-gradle:8.0.2' classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.7.20" classpath "org.jetbrains.kotlinx:kotlinx-knit:0.4.0" classpath 'com.jakewharton:butterknife-gradle-plugin:10.2.3' From af26b10ee22a6569f17d33e3154119b20e858803 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Jan 2023 23:13:55 +0000 Subject: [PATCH 103/189] Bump com.posthog.android:posthog from 2.0.0 to 2.0.1 Bumps [com.posthog.android:posthog](https://github.com/PostHog/posthog-android) from 2.0.0 to 2.0.1. - [Release notes](https://github.com/PostHog/posthog-android/releases) - [Changelog](https://github.com/PostHog/posthog-android/blob/master/CHANGELOG.md) - [Commits](https://github.com/PostHog/posthog-android/compare/2.0.0...2.0.1) --- updated-dependencies: - dependency-name: com.posthog.android:posthog dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- vector/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/build.gradle b/vector/build.gradle index efea312bed..3b30919c6d 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -233,7 +233,7 @@ dependencies { kapt libs.dagger.hiltCompiler // Analytics - implementation('com.posthog.android:posthog:2.0.0') { + implementation('com.posthog.android:posthog:2.0.1') { exclude group: 'com.android.support', module: 'support-annotations' } implementation libs.sentry.sentryAndroid From ca8d107968aeebb7aa156834d2d27f415a99590a Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Mon, 30 Jan 2023 18:02:40 +0000 Subject: [PATCH 104/189] Translated using Weblate (Japanese) Currently translated at 95.4% (2479 of 2597 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/ --- .../src/main/res/values-ja/strings.xml | 348 +++++++++++++++--- 1 file changed, 289 insertions(+), 59 deletions(-) diff --git a/library/ui-strings/src/main/res/values-ja/strings.xml b/library/ui-strings/src/main/res/values-ja/strings.xml index 0b987ce683..83bf953d11 100644 --- a/library/ui-strings/src/main/res/values-ja/strings.xml +++ b/library/ui-strings/src/main/res/values-ja/strings.xml @@ -1,5 +1,5 @@ - + %sの招埅 %1$sが%2$sを招埅したした %1$sがあなたを招埅したした @@ -23,7 +23,7 @@ ルヌムぞの招埅 %1$sず%2$s 空のルヌム - %1$sが今埌のルヌム履歎を%2$sに芋えるように蚭定したした。 + %1$sが今埌のルヌム履歎を「%2$s」閲芧可胜に蚭定したした。 ルヌムのメンバヌ党員招埅された時点から ルヌムのメンバヌ党員参加した時点から ルヌムのメンバヌ党員 @@ -76,7 +76,7 @@ すぐに返信 開く 閉じる - クリップボヌドぞコピヌ + クリップボヌドにコピヌしたした è­Šå‘Š お気に入り メンバヌ @@ -115,8 +115,8 @@ いいえ 続行する 最新の未読ぞ移動 - ルヌムを退出 - このルヌムを退出しおよろしいですか + ルヌムから退出 + このルヌムから退出しおよろしいですか 招埅 %sさんが文字を入力しおいたす  %1$sさんず%2$sさんが文字を入力しおいたす  @@ -134,7 +134,7 @@ 削陀 参加 このルヌムで発蚀する暩限がありたせん。 - 自分のアむコン画像 + プロフィヌル画像 衚瀺名 メヌルアドレスを远加 電話番号を远加 @@ -278,7 +278,7 @@ リク゚ストの送信に倱敗したした。 りィゞェットを䜜成できたせん。 りィゞェットをこのルヌムから削陀しおもよろしいですか - 䞀臎しおいない堎合は、コミュニケヌションのセキュリティヌが砎られおいる可胜性がありたす。 + 䞀臎しおいない堎合は、コミュニケヌションのセキュリティヌが損なわれおいる可胜性がありたす。 このセッションでは、未認蚌のセッションに察しお暗号化されたメッセヌゞを送信しない。 認蚌枈のセッションに察しおのみ暗号化 むンポヌト @@ -320,8 +320,8 @@ 埩号゚ラヌ セッションキヌ 未認蚌 - 認蚌する - 他のセッションのナヌザヌ蚭定で、以䞋を比范しお確認しおください + 認蚌 + 他のセッションのナヌザヌ蚭定で、以䞋を比范しお承認しおください ルヌムのディレクトリを遞択 サヌバヌ名 %sサヌバヌ䞊の党おのルヌム @@ -399,7 +399,7 @@ 指定したIDのナヌザヌの管理者暩限を取り消す 指定したナヌザヌを珟圚のルヌムに招埅 指定されたアドレスのルヌムに参加 - ルヌムを退宀 + ルヌムから退出 ルヌムの説明を蚭定 指定したIDのナヌザヌをこのルヌムから远攟 衚瀺するニックネヌムを倉曎 @@ -409,7 +409,7 @@ ゚ラヌ 今すぐ確認 アカりントを停止 - この操䜜により、あなたのアカりントは氞久に䜿えなくなりたす。あなたはログむンできなくなり、誰も同じナヌザヌIDを再登録できなくなりたす。アカりントが参加しおいる党おのルヌムを退出し、IDサヌバヌからアカりントの詳现は削陀されたす。 この操䜜は取り消せたせん。 + この操䜜により、あなたのアカりントは氞久に䜿えなくなりたす。あなたはログむンできなくなり、誰も同じナヌザヌIDを再登録できなくなりたす。アカりントが参加しおいる党おのルヌムから退出し、IDサヌバヌからアカりントの詳现は削陀されたす。 この操䜜は取り消せたせん。 \n \nアカりントを停止しおも、 デフォルトではあなたが送信したメッセヌゞの履歎は消去されたせん。メッセヌゞの履歎の消去を望む堎合は、以䞋のボックスにチェックを入れおください。 \n @@ -431,7 +431,7 @@ 申し蚳ありたせん、゚ラヌが発生したした ゚クスポヌトされた鍵を暗号化するパスフレヌズを䜜成しおください。 鍵をむンポヌトするには、同䞀のパスフレヌズを入力する必芁がありたす。 パスフレヌズの䜜成 - パスフレヌズが䞀臎しおいたせん + パスフレヌズが䞀臎したせん %1$s%2$s %d+ 展開 @@ -465,7 +465,7 @@ パスワヌド パスワヌド 今ここでサむンアりトするず、あなたの暗号化されたメッセヌゞは倱われおしたいたす - 鍵のバックアップは珟圚凊理䞭です。凊理䞭にサむンアりトするず、暗号化されたメッセヌゞにアクセスできなくなりたす。 + 鍵をバックアップしおいたす。凊理䞭にサむンアりトするず、暗号化されたメッセヌゞにアクセスできなくなりたす。 暗号化されたメッセヌゞにアクセスできなくなるこずを防ぐため、鍵の安党なバックアップはあなたのセッション党おで有効化しおください。 暗号化されたメッセヌゞは䞍芁です 鍵をバックアップしおいたす  @@ -491,7 +491,7 @@ バックグラりンド制限の確認 線集 返信 - メッセヌゞが削陀されたした + メッセヌゞを削陀したした 削陀枈のメッセヌゞを衚瀺 削陀されたメッセヌゞに関する通知を衚瀺 ナヌザヌによっお削陀されたむベント @@ -535,7 +535,7 @@ %1$d人の参加者 アップロヌド - ルヌムを退出 + ルヌムから退出 ルヌムから退宀しおいたす  管理者 モデレヌタヌ @@ -560,8 +560,8 @@ クロス眲名は無効です 有効なセッション 党おのセッションを衚瀺 - セッションの管理 - このセッションからログアりト + セッションを管理 + このセッションからサむンアりト %d件のアクティブなセッション @@ -700,7 +700,7 @@ タむムラむンで非衚瀺のむベントを衚瀺 QRコヌドをスキャン QRコヌド - QRコヌドによる远加 + QRコヌドで远加 コヌドを共有 ${app_name}で話したしょう%s 友達を招埅 @@ -858,9 +858,9 @@ %sがこのルヌムのサヌバヌアクセス制埡リストを蚭定したした。 %sがここをアップグレヌドしたした。 %sがこのルヌムをアップグレヌドしたした。 - 今埌のメッセヌゞを%1$sに芋えるように蚭定したした。 - 今埌のルヌム履歎を%1$sに芋えるように蚭定したした。 - %1$sが今埌のメッセヌゞを%2$sに芋えるように蚭定したした。 + 今埌のメッセヌゞを「%1$s」閲芧可胜に蚭定したした。 + 今埌のルヌム履歎を「%1$s」閲芧可胜に蚭定したした。 + %1$sが今埌のメッセヌゞを「%2$s」閲芧可胜に蚭定したした。 %sが通話を蚭定するためにデヌタを送信したした。 通話を開始したした。 ビデオ通話を開始したした。 @@ -874,7 +874,7 @@ %1$sが招埅を拒吊したした。理由%2$s 退出したした。理由%1$s %1$sが退出したした。理由%2$s - このルヌムを退出したした。理由%1$s + このルヌムから退出したした。理由%1$s 初期同期 \n退出したルヌムをむンポヌトしおいたす 初期同期 @@ -882,7 +882,7 @@ 初期同期 \n䌚話を読み蟌んでいたす \n倚くのルヌムに参加しおいる堎合、読み蟌みに時間がかかるかもしれたせん - %1$sがこのルヌムを退出したした。理由%2$s + %1$sがこのルヌムから退出したした。理由%2$s このルヌムに参加したした。理由%1$s %1$sがこのルヌムに参加したした。理由%2$s このルヌムに参加したした。理由%1$s @@ -1014,7 +1014,7 @@ ファむルずしお保存 共有 完了 - 成功 + 成功したした バックアップを䜜成しおいたす パスフレヌズを蚭定 手動で鍵を゚クスポヌト @@ -1076,7 +1076,7 @@ バックアップには認蚌枈のセッション %s による䞍正な眲名がありたす バックアップには未認蚌のセッション %s による有効な眲名がありたす バックアップには認蚌枈のセッション %s による眲名がありたす。 - バックアップはこのセッションによる有効な眲名がありたす。 + バックアップにはこのセッションによる有効な眲名がありたす。 バックアップには%sずいうIDの䞍明のセッションによる眲名がありたす。 このセッションでは鍵がバックアップされおいたせん。 このセッションでは鍵のバックアップが無効になっおいたす。 @@ -1088,7 +1088,7 @@ %d個のキヌが含たれたバックアップを埩元したした。 - バックアップが埩元されたした %s + バックアップを埩元したした %s このリカバリヌキヌではバックアップを埩号化できたせんでした。正しいリカバリヌキヌを入力したこずを確認しおください。 リカバリヌキヌを入力しおください 履歎のロックを解陀 @@ -1097,11 +1097,11 @@ リカバリヌキヌを蚈算しおいたす  バックアップを埩元しおいたす このパスフレヌズではバックアップを埩号化できたせんでした。正しい埩旧甚のパスフレヌズを入力したこずを確認しおください。 - リカバリヌキヌを喪倱したしたか 蚭定で新しいリカバリヌキヌを蚭定できたす。 + リカバリヌキヌを無くしたしたか 蚭定で新しいリカバリヌキヌを蚭定できたす。 バックアップのバヌゞョンを取埗しおいたす  暗号化されたメッセヌゞ履歎のロックを解陀するには、埩旧甚のパスフレヌズを䜿甚しおください 埩旧甚のパスフレヌズが分からなければ、%sできたす。 - リカバリヌキヌを䜿甚しお、暗号化されたメッセヌゞの履歎のロックを解陀 + リカバリヌキヌを䜿うず、暗号化されたメッセヌゞの履歎のロックを解陀できたす リカバリヌキヌを入力 リカバリヌキヌを䜿甚 ログアりトしたりこの端末を倱くしたりするず、メッセヌゞにアクセスできなくなる可胜性がありたす。 @@ -1161,7 +1161,7 @@ \n%s りィゞェットを再読み蟌み これを䜿甚するず、クッキヌが蚭定され、デヌタが%sず共有される可胜性がありたす - りィゞェットの远加者: + りィゞェットを远加した人 **送信に倱敗 - ルヌムを開いおください 新しい招埅 %1$sず%2$s @@ -1363,16 +1363,16 @@ メンションずキヌワヌド 通知のデフォルト - %d個の䞍圚着信ビデオ + %d件の䞍圚着信ビデオ - %d個の䞍圚着信音声 + %d件の䞍圚着信音声 デフォルトで䜿いもう尋ねない 鍵の共有リク゚ストの履歎を送信 結果がありたせん 自分に電話をかけるこずはできたせん。参加者が招埅を受け入れるたでお埅ちください - ミヌティングはJitsiのセキュリティヌずパヌミッションポリシヌを䜿甚したす。䌚議䞭は、珟圚ルヌムにいる党おの人に招埅状が衚瀺されたす。 + ミヌティングはJitsiのセキュリティヌずパヌミッションポリシヌを䜿甚したす。䌚議䞭は、珟圚ルヌムにいる党おの人に招埅が衚瀺されたす。 暩限がありたせん 音声メッセヌゞを送信するには、マむクの暩限を蚱可しおください。 この操䜜を実行するには、システム蚭定からカメラの暩限を蚱可しおください。 @@ -1400,7 +1400,7 @@ 利甚芏玄 線集履歎を衚瀺 提案 - クリップボヌドにコピヌされたリンク + リンクをクリップボヌドにコピヌしたした メむン画面に未読通知専甚のタブを远加する。 ルヌム名を怜玢 名前もしくはID (#䟋えば:matrix.org) @@ -1421,7 +1421,7 @@ \n- あなたの端末が䜿甚しおいるむンタヌネット接続 \n \n蚭定画面からパスワヌドずリカバリヌキヌを早急に倉曎するこずを掚奚したす。 - 電子メヌル + メヌルアドレス アドレス 続行する ファむル @@ -1472,10 +1472,10 @@ このルヌムにファむルはありたせん このルヌムにメディアはありたせん 公開ルヌムをアップグレヌド - 非公開スペヌス + 非公開のスペヌス 公開スペヌス - 送信枈 - 送信䞭 + 送信したした + 送信しおいたす 皮類 確認枈 遞択枈 @@ -1595,7 +1595,7 @@ %sぞのメヌル通知を有効にする ヒントメッセヌゞを長抌ししお「%s」を遞択。 スレッドを甚いるず、䌚話のテヌマを保ったり、䌚話を远跡したりするのが容易になりたす。 - あなたの非公開スペヌス + あなたの非公開のスペヌス あなたの公開スペヌス 自分のみ スレッドで議論を敎理しお管理 @@ -1757,7 +1757,7 @@ あなたの連絡先は非公開です。端末の連絡先からナヌザヌを発芋するためには、連絡先の情報をIDサヌバヌに送信する蚱可が必芁です。 ディスカバリヌ蚭定を開く トピックを远加 - %sはルヌムを䜜成し蚭定したした。 + %sがルヌムを䜜成し蚭定したした。 参加したした。 %sが参加したした。 このルヌムで䜿甚されおいる暗号化はサポヌトされおいたせん @@ -1850,11 +1850,11 @@ 玙吹雪🎉を送る 降雪❄を送る あなたのチヌムのメッセヌゞングに。 - ゚ンドツヌ゚ンドで暗号化され、電話番号䞍芁。広告やデヌタマむニング無し。 + ゚ンドツヌ゚ンドで暗号化されおおり、登録に電話番号は䞍芁です。広告もデヌタ収集もありたせん。 䌚話の保存先を自分で決められ、自分で管理できる独立したコミュニケヌション。Matrixをもずに。 - お宅での察面䌚話ず同じぐらいのプラむバシヌを提䟛する、セキュアで独立したコミュニケヌション。 - セキュアメッセヌゞング - 管理暩を握るのは、あなたです。 + オンラむン䞊でも察面の䌚話ず同じレベルでプラむバシヌを守る、安党で独立したコミュニケヌション。 + 安党なメッセヌゞ。 + 䞻導暩を握るのは、あなたです。 ${app_name}の䜿甚に関するヘルプ 詳现なログは、むラむラシェむクでログを送信する際に、より倚くのログを提䟛するこずで、開発者にずっおの助けになりたす。有効にした堎合でも、メッセヌゞの内容やその他のプラむベヌトな情報は蚘録されたせん。 ルヌムのアップグレヌドは高床な䜜業であり、䞍具合や欠けおいる機胜、セキュリティヌ䞊の脆匱性がある堎合に掚奚されたす。 @@ -1869,7 +1869,7 @@ %sに招埅 ナヌザヌ名かメヌルアドレスで招埅 - %sを退出しおよろしいですか + %sから退出しおよろしいですか スペヌスは、ルヌムや連絡先をグルヌプ化する新しい方法です。 招埅されおいたす 新しいスペヌスを、あなたが管理するスペヌスに远加。 @@ -1970,7 +1970,7 @@ %1$sを読み蟌み䞭に゚ラヌが発生したした%2$d 利甚したいサヌバヌのアドレスを入力しおください 利甚したいModular Elementたたはサヌバヌのアドレスを入力しおください - 迷っおいたすか%sしおもいいです + 迷っおいたすか%s みんなず繋がる手助けをいたしたす。 自分のコヌド @@ -2059,9 +2059,7 @@ ただいた%1$sにメヌルを送信したした。 \nアカりント登録を続行するにはメヌル内のリンクをクリックしおください。 CAPTCHA認蚌を行っおください - アカりントがただ登録されおいたせん。 -\n -\n登録を䞭止したすか + アカりントがただ䜜成されおいたせん。登録を䞭止したすか %1$sにアカりント登録 あなたはすべおのセッションからログアりトしおおり、これ以䞊プッシュ通知を受け取れたせん。通知を再び有効にするには、各端末でサむンむンしおください。 セキュリティヌフレヌズを蚭定 @@ -2133,9 +2131,9 @@ 暗号化の蚭定が正しくありたせん。 暗号化を埩元 暗号化を有効な状態に取り戻すために、管理者に連絡しおください。 - このナヌザヌずのメッセヌゞぱンドツヌ゚ンド暗号化されおおり、第䞉者には読めたせん。 + このナヌザヌずのメッセヌゞぱンドツヌ゚ンドで暗号化されおおり、第䞉者が解読するこずはできたせん。 このコヌドを盞手の画面に珟れおいるコヌドず比范しおください。 - 絵文字を比范しお、同じ順番に珟れおいるのを確認しおください。 + 絵文字を比范しお、同じ順番で珟れおいるのを確認しおください。 セキュリティヌを高めるために、察面で行うか、他の信頌できる通信手段を利甚したしょう。 遞択された゚モヌトを虹色にしお送信したす 遞択されたテキストを虹色にしお送信したす @@ -2173,7 +2171,7 @@ %1$sが%2$sの暩限レベルを倉曎したした。 %1$sの暩限レベルを倉曎したした。 誰ず䜿いたすか - どんなスペヌスを䜜りたすか + 䜜成するスペヌスの皮類を遞択しおください 自分ず仲間の非公開のスペヌス ルヌムを敎理するためのプラむベヌトスペヌス ここが䌚話のスタヌト地点です。 @@ -2221,7 +2219,7 @@ このルヌムが発芋できたせん。存圚するこずを確認しおください。 指王や顔画像など、端末に固有の生䜓認蚌を有効にする。 絵文字で認蚌 - テキストで認蚌 + テキストを䜿っお手動で認蚌 埩旧甚の手段を党お無くしおしたいたしたか党おリセットする クロス眲名に察応した他のMatrixのクラむアントでも䜿甚できたす。 どのような議論を%sで行いたいですか @@ -2278,7 +2276,7 @@ ゚ラヌのためメッセヌゞが送信されたせんでした %sにいない人を探しおいたすか 盎接${app_name}で招埅を受け取るには、蚭定画面から%sしおください。 - PINコヌドでしか${app_name}のロックを解陀するこずはできたせん。 + PINコヌドでしか${app_name}のロックを解陀できたせん。 ${app_name}を開く際には、毎回PINコヌドの入力が必芁です。 あなたがブロックされおいるルヌムを開くこずはできたせん。 PINコヌドの認蚌に倱敗したした。新しいコヌドを入力しおください。 @@ -2291,7 +2289,7 @@ 確認のため、セキュリティヌフレヌズを再入力しおください。 ホヌムサヌバヌ%1$sは、IDサヌバヌに%2$sを蚭定するように提案しおいたす IDサヌバヌ %s から切断したすか - ダむレクトメッセヌゞを䜜成できたせんでした。招埅したナヌザヌを確認し、もう䞀床やり盎しおください。 + ダむレクトメッセヌゞを䜜成できたせんでした。招埅したいナヌザヌを確認し、もう䞀床やり盎しおください。 セキュリティヌフレヌズ 自分ず仲間 メッセヌゞの皮類がありたせん @@ -2310,7 +2308,7 @@ 操䜜を実行できたせん。ホヌムサヌバヌは最新のバヌゞョンではありたせん。 ビデオ通話が拒吊されたした 音声通話が拒吊されたした - %1$sは通話を拒吊したした + %1$sは着信を拒吊したした このデバむスを認蚌可胜な他の端末が党くない堎合にのみ、続行しおください。 このセッションを信頌枈ずしお認蚌するず、暗号化されたメッセヌゞにアクセスするこずができたす。このアカりントにサむンむンしなかった堎合は、あなたのアカりントのセキュリティヌが砎られおいる可胜性がありたす アカりントのセキュリティヌが砎られおいる可胜性がありたす @@ -2331,7 +2329,7 @@ %d個の゚ントリヌ 保存しお続行 - 蚭定を保存したした。 + 蚭定画面からい぀でもプロフィヌルを曎新できたす これは埌から倉曎できたす。 プロフィヌル画像を远加 これは埌から倉曎できたす @@ -2342,7 +2340,7 @@ 䜍眮情報ラむブを共有 äž­æ­¢ 衚瀺名を遞択 - あなたのアカりント %s が䜜成されたした。 + あなたのアカりント %s が䜜成されたした おめでずうございたす 近日䞭にスレッドはベヌタ版ずなりたす。 \n @@ -2355,7 +2353,7 @@ フィヌドバックを送信 ベヌタ版 ベヌタ版 - 詊す + 詊しおみる オフラむンモヌド 新着はありたせん。 - ナヌザヌの無芖が解陀されたした @@ -2472,7 +2470,7 @@ QRコヌドが䞍正です。 スペヌスは、ルヌムず連絡先をたずめる新しい方法です。はじめに、スペヌスを䜜成したしょう。 最近の履歎を衚瀺 - この暗号化されたメッセヌゞの信頌性はこの端末では保蚌できたせん。 + この暗号化されたメッセヌゞの真正性はこの端末では保蚌できたせん。 アカりントが安党かどうか確認しおください 未認蚌のセッションがありたす 連絡先 @@ -2500,4 +2498,236 @@ リッチテキスト゚ディタヌを詊しおみるプレヌンテキストモヌドは近日公開 タブを䜿甚しおElementの衚瀺をシンプルにする セッションの詳现 + + %1$d日以䞊䜿甚されおいたせん + + + %1$d日以䞊䜿甚されおいたせん%2$s + + 地図を読み蟌めたせん +\nこのホヌムサヌバヌは地図が読み蟌むよう蚭定されおいないおそれがありたす。 + スペヌスは、ルヌムや連絡先をグルヌプ化する新しい方法です。右䞋のボタンを䜿うず、既存のルヌムを远加したり新たに䜜成したりできたす。 + セキュリティヌに関する勧告 + その他のセッション + セキュリティヌを最倧限に高めるには、䞍明なセッションや利甚しおいないセッションからサむンアりトしおください。 + 生䜓認蚌を有効にできたせんでした。 + 関連付けに倱敗したした。 + おかえりなさい + たたは + %sに返信しおいたす + アニメヌション画像がタむムラむンに衚瀺されたらすぐに再生 + 斜字䜓にする + 非アクティブなセッションは、しばらく䜿甚されおいたせんが、暗号鍵を受信しおいるセッションです。 +\n +\n䜿甚しおいないセッションを削陀するず、セキュリティヌずパフォヌマンスが改善されたす。たた、新しいセッションが疑わしい堎合に、より容易に特定できるようになりたす。 + 非アクティブなセッション + 改善したセッションの管理画面を䜿甚したす。 + 未認蚌のセッション + ルヌムのタむムラむンで音声配信を録音しお送信するこずを可胜にしたす。 + 音声配信を有効にする + 未読のメッセヌゞがある堎合は、ここに衚瀺されたす。 + 報告するこずはありたせん。 + クラむアントの情報の保存を有効にする + セッション名は連絡先にも衚瀺されたす。 + セッション名を蚭定するず、端末をより簡単に認識できるようになりたす。 + このセッションでプッシュ通知を受信。 + 絞り蟌みを解陀 + 絞り蟌み + アプリケヌション、端末、アクティビティヌに関する情報。 + 盎近のアクティビティヌ + セッション名 + + %1$d件のセッションからサむンアりト + + 䜿甚しおいないセッションはありたせん。 + 未認蚌のセッションはありたせん。 + 認蚌枈のセッションはありたせん。 + + 䜿甚しおいない叀いセッション%1$d日以䞊䜿甚されおいたせんからサむンアりトするこずを怜蚎しおください。 + + 非アクティブ + セッションを認蚌するず、より安党なメッセヌゞのやりずりが可胜になりたす。芋芚えのない、たたは䜿甚しおいないセッションがあれば、サむンアりトしたしょう。 + 未認蚌 + 認蚌枈 + 倪字にする + 端末に接続しおいたす + ホヌムサヌバヌはQRコヌドによるサむンむンをサポヌトしおいたせん。 + 安党な接続を確立したした + 確認 + リンクを蚭定 + 党画面モヌドを切り替える + テキスト + リンク + リンクを䜜成 + リンクを線集 + 返信先 + アンケヌト + アクセストヌクン + アクセストヌクンを甚いるず、あなたのアカりントの党おの情報にアクセスできたす。倖郚に公開したり、誰かず共有したりしないでください。 + セッションを遞択 + このセッションからサむンアりト + IPアドレスを衚瀺しない + IPアドレスを衚瀺 + 他の党おのセッションからサむンアりト + サむンアりト + 非アクティブ + 安党なメッセヌゞのやりずりの準備ができおいたせん + 未認蚌 + 安党なメッセヌゞのやりずりの準備ができたした + 認蚌枈 + 党おのセッション + 端末 + セッション + 珟圚のセッション + 非アクティブなセッション + 未認蚌のセッション + 以䞋の勧告に埓い、アカりントのセキュリティヌを改善したしょう。 + 党お衚瀺%1$d + 詳现を衚瀺 + セッションを認蚌 + このセッションは暗号化をサポヌトしおいないため、認蚌できたせん。 + セキュリティヌず安定性の芳点から、このセッションを認蚌するかサむンアりトしおください。 + より安党なメッセヌゞのやりずりのために、珟圚のセッションを認蚌したしょう。 + このセッションは安党なメッセヌゞのやりずりの準備ができおいたす。 + 珟圚のセッションは安党なメッセヌゞのやりずりに察応しおいたす。 + 認蚌の状態が䞍明です + 未認蚌のセッション + 認蚌枈のセッション + 端末の皮類が䞍明です + デスクトップ + りェブ + 携垯端末 + + %d件のメッセヌゞを削陀したした + + 䜍眮情報の共有を有効にする + 泚意これは䞀時的な実装による詊隓機胜です。䜍眮情報の履歎を削陀するこずはできたせん。高床なナヌザヌは、あなたがこのルヌムで䜍眮情報ラむブの共有を停止した埌でも、あなたの䜍眮情報の履歎を閲芧するこずができたす。 + 䜍眮情報ラむブの共有 + ゚ンドポむントが芋぀かりたせん。 + 珟圚の゚ンドポむント%s + ゚ンドポむント + 珟圚%sを䜿甚しおいたす。 + 方法 + バックグラりンド同期 + バックグラりンド同期以倖の方法がありたせん。 + Google Playサヌビス以倖の方法がありたせん。 + 利甚可胜な方法 + 通知方法 + Googleサヌビス + 通知の受信方法を遞択しおください + 画面を共有しおいたす + ${app_name}画面共有 + テキストの装食 + 連絡先 + カメラ + 䜍眮情報 + アンケヌト + 音声配信 + 添付ファむル + ステッカヌ + 音声配信を開始 + 䜍眮情報ラむブ + 䜍眮情報を共有 + このルヌムでの䜍眮情報ラむブの共有には適切な暩限が必芁です。 + 䜍眮情報ラむブの共有に必芁な暩限がありたせん + 䞀時的な実装。䜍眮情報がルヌムの履歎に残りたす + 䜍眮情報ラむブの共有を有効にする + 䜍眮情報を共有しおいたす + ${app_name}䜍眮情報ラむブ + 残り%1$d + 䜍眮情報ラむブを衚瀺 + 䜍眮情報ラむブが終了したした + 䜍眮情報ラむブを読み蟌んでいたす  + 8時間 + 1時間 + 15分 + 䜍眮情報ラむブを共有する時間 + 珟圚の䜍眮にズヌム + 地図で遞択した䜍眮情報のピン + アンケヌトの取埗䞭に゚ラヌが発生したした。 + アンケヌトを衚瀺しおいたす + このルヌムに過去のアンケヌトはありたせん + 過去のアンケヌト + このルヌムに実斜䞭のアンケヌトはありたせん + 実斜䞭のアンケヌト + 埩号゚ラヌにより、いく぀かの投祚はカりントできたせん + アンケヌトを終了したした。 + アンケヌトが終了するたで結果は衚瀺できたせん + 履歎を共有しおいる暗号化されたルヌムに招埅する際、暗号化された履歎が衚瀺されるようになりたす。 + MSC3061過去のメッセヌゞ甚にルヌムの鍵を共有 + ラむブ配信を終了しおよろしいですか配信を終了し、録音をこのルヌムで利甚できるよう蚭定したす。 + ラむブ配信を停止したすか + 残り%1$d + 接続゚ラヌ - 録音を停止したした + この音声配信を再生できたせん。 + 既に音声配信を録音しおいたす。新しく始めるには今の音声配信を終了しおください。 + 他の人が既に音声配信を録音しおいたす。新しく始めるには音声配信が終わるたで埅機しおください。 + このルヌムで音声配信を開始する暩限がありたせん。ルヌムの管理者に連絡しお暩限の付䞎を䟝頌しおください。 + 新しい音声配信を開始できたせん + 音声配信を䞀時停止 + 音声配信の録音を再開 + バッファリングしおいたす  + ラむブ配信 + ラむブ + %1$s + %1$s%2$s + %1$sを再生できたせん + %1$sを䞀時停止 + %1$sを再生 + %1$d分%2$d秒 + ラむブ配信を録音しおいるため、音声メッセヌゞを開始できたせん。音声メッセヌゞの録音を開始するには、ラむブ配信を終了しおください + 音声メッセヌゞを開始できたせん + 党おのメッセヌゞに最新のプロフィヌル情報アバタヌず衚瀺名を衚瀺。 + 最新のナヌザヌ情報を衚瀺 + 怜玢結果がありたせん + 退出しない + 党お退出 + 珟圚、この゚むリアスにはアクセスできたせん。 +\n埌でもう䞀床やり盎すか、ルヌムの管理者にアクセス暩があるかどうかを確認するよう䟝頌しおください。 + %sのメンバヌにはなりたせん + 蚭定を開く + ビデオ通話の着信䞭 + 音声通話の着信䞭 + 暗号化されたルヌム内の珟圚のアりトバりンドグルヌプセッションを匷制的に砎棄 + 䌚話で入力したデヌタに基づいお入力履歎や蟞曞などに関する個人甚デヌタを倉曎しないようキヌボヌドに指瀺したす。いく぀かのキヌボヌドでは、この蚭定が無芖されるこずがありたす。 + プラむベヌトキヌボヌド + このチャットのメッセヌゞぱンドツヌ゚ンドで暗号化されたす。 + このQRコヌドは䞍正な圢匏です。他の方法で認蚌を詊しおください。 + 最新版を入手泚意サむンむンの際に問題が起こる可胜性がありたす + 暗号化されたメッセヌゞの履歎にアクセスできたせん。鍵の安党なバックアップず認蚌甚の鍵をリセットしお、やり盎しおください。 + この端末を認蚌できたせん + セッション + アンケヌトの履歎 + 音声配信を開始したした + 䜍眮情報ラむブを共有したした + プレヌンテキストメッセヌゞの前に (╯°□°╯ ┻━┻ を付ける + このリンクを開けたせんコミュニティヌ機胜はスペヌス機胜に倉曎されたした + 電子メヌルを入力しおください + %sの利甚芏玄ず運営方針を確認しおください + サヌバヌの運営方針 + 問い合わせる + 自分でサヌバヌを運営したいですか + サヌバヌのURL + あなたのサヌバヌのアドレスを入力しおください + あなたのサヌバヌのアドレスを指定しおください。サヌバヌにはあなたの党おのデヌタが保管されたす + サヌバヌを遞択 + 線集 + 8文字以䞊にしおください + アカりントを䜜成 + ホヌムに移動 + プロフィヌルを倉曎 + ${app_name}は職堎利甚にも最適です。䞖界で最も安党な組織によっお信頌されおいたす。 + 音声配信 + スペヌスの䞀芧を開く + 新しい䌚話たたはルヌムを䜜成 + 通知方法をリセット + セッションID + 続行 + デヌタをアップデヌトしおいたす  + 線集䞭 + バックアップにはこのナヌザヌによる有効な眲名がありたす。 + 開発者ツヌルの画面を開く + ルヌムが芋぀かりたせんでした。 +\n埌でやり盎しおください。%s + 盎接共有を有効にする \ No newline at end of file From 935b2aab88d04414dc064c8ffbd0f415b3f6bb92 Mon Sep 17 00:00:00 2001 From: Didek Date: Sat, 28 Jan 2023 13:15:29 +0000 Subject: [PATCH 105/189] Translated using Weblate (Polish) Currently translated at 93.1% (2418 of 2597 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/pl/ --- library/ui-strings/src/main/res/values-pl/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values-pl/strings.xml b/library/ui-strings/src/main/res/values-pl/strings.xml index 4419187ba5..2bdd6e806d 100644 --- a/library/ui-strings/src/main/res/values-pl/strings.xml +++ b/library/ui-strings/src/main/res/values-pl/strings.xml @@ -345,7 +345,7 @@ Importuj klucze z lokalnego pliku Importuj Szyfruj wiadomości tylko do zaufanych sesji - Nigdy nie wysyłaj szyfrowanych wiadomości do niezweryfikowanych sesji (bez zielonej tarczy) z tego urządzenia. + Nigdy nie wysyłaj szyfrowanych wiadomości do niezweryfikowanych sesji (bez zielonej tarczy) z tej sesji. Aby sprawdzić czy ta sesja jest zaufana, skontaktuj się z jej właścicielem uÅŒywając innych form (np. osobiście lub telefonicznie) i zapytaj czy klucz, który widzą w ustawieniach uÅŒytkownika dla tego urządzenia pasuje do klucza poniÅŒej: Jeśli klucz pasuje, potwierdź to przyciskiem poniÅŒej. Jeśli nie, to ktoś inny najprawdopodobniej przejmuje lub podszywa się pod tą sesję i powinieneś dodać tę sesję do czarnej listy. W przyszłości proces weryfikacji będzie bardziej skomplikowany. Wyślij naklejkę From 47a2dc5adb00047ad02ec13f6306e396b7494f8e Mon Sep 17 00:00:00 2001 From: DarkCoder15 Date: Sat, 28 Jan 2023 09:18:19 +0000 Subject: [PATCH 106/189] Translated using Weblate (Russian) Currently translated at 100.0% (2597 of 2597 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ru/ --- library/ui-strings/src/main/res/values-ru/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values-ru/strings.xml b/library/ui-strings/src/main/res/values-ru/strings.xml index 5938200c1e..bf329b89ec 100644 --- a/library/ui-strings/src/main/res/values-ru/strings.xml +++ b/library/ui-strings/src/main/res/values-ru/strings.xml @@ -2540,7 +2540,7 @@ ДПЌашМОй сервер Ме прОМОЌает ОЌя пПльзПвателя, сПстПящее тПлькП Оз цОфр. ПрПпустОть этПт шаг СПхраМОть О прПЎПлжОть - ЗайЎОте в МастрПйкО чтПбы ОзЌеМОть Ваш прПфОль + ВашО преЎпПчтеМОя былО сПхраМеМы ВыгляЎОт хПрПшП! ${app_name} также ПтлОчМП пПЎхПЎОт Ўля рабПты. ЕЌу ЎПверяют саЌые МаЎёжМые ПргаМОзацОО в ЌОре. РезервМая кПпОя ОЌеет ЎействОтельМую пПЎпОсь Ўля ЎаММПгП пПльзПвателя. From 694abe72a6dd94d2b83eb70978bc9d876bd5711d Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Fri, 27 Jan 2023 19:19:50 +0000 Subject: [PATCH 107/189] Translated using Weblate (Swedish) Currently translated at 100.0% (2597 of 2597 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/sv/ --- .../src/main/res/values-sv/strings.xml | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values-sv/strings.xml b/library/ui-strings/src/main/res/values-sv/strings.xml index 877a95f2de..caf9913299 100644 --- a/library/ui-strings/src/main/res/values-sv/strings.xml +++ b/library/ui-strings/src/main/res/values-sv/strings.xml @@ -2898,4 +2898,25 @@ Omröstningshistorik Din hemserver har inte stöd för att lista trÃ¥dar Àn. Ja, sluta - + Fel vid hÀmtning av omröstningar. + Laddar fler omröstning + Visar omröstningar + + Det finns inga aktiva omröstningar frÃ¥n förra dagen. +\nLadda fler omröstningar för att se omröstningar frÃ¥n tidigare dagar. + Det finns inga aktiva omröstningar frÃ¥n senaste %1$d dagarna. +\nLadda fler omröstningar för att se omröstningar frÃ¥n tidigare dagar. + + + Det finns inga omröstningar frÃ¥n förra dagen. +\nLadda fler omröstningar för att se omröstningar frÃ¥n tidigare dagar. + Det finns inga omröstningar frÃ¥n senaste %1$d dagarna. +\nLadda fler omröstningar för att se omröstningar frÃ¥n tidigare dagar. + + PÃ¥ grund av avkrypteringsfel sÃ¥ kanske vissa röster inte rÀknas + Anslutningsfel - Inspelning pausad + Kan inte spela den hÀr röstsÀndningen. + Du kan inte pÃ¥börja ett röstmeddelande eftersom du för nÀrvarande spelar in en röstsÀndning. VÀnligen avsluta din röstsÀndning för att börja spela in ett röstmeddelande + Kan inte starta röstsÀndning + Startade en röstsÀndning + \ No newline at end of file From 3488c83542ee5dc9935f8499c3189582de0fa9f5 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Mon, 30 Jan 2023 07:03:27 +0000 Subject: [PATCH 108/189] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (2597 of 2597 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/zh_Hant/ --- library/ui-strings/src/main/res/values-zh-rTW/strings.xml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values-zh-rTW/strings.xml b/library/ui-strings/src/main/res/values-zh-rTW/strings.xml index c650a1e6b2..4b9888e122 100644 --- a/library/ui-strings/src/main/res/values-zh-rTW/strings.xml +++ b/library/ui-strings/src/main/res/values-zh-rTW/strings.xml @@ -2856,4 +2856,7 @@ 過去 %1$d 倩沒有掻躍的投祚。 \n茉入曎倚投祚以檢芖過去幟倩的投祚。 - + 連線錯誀 - 錄補已暫停 + 悚無法開始語音蚊息因為悚目前正圚錄補盎播。請結束悚的盎播以開始錄補語音蚊息 + 無法開始語音蚊息 + \ No newline at end of file From 4e0159ee35a623578345896feee7fd97307c4a84 Mon Sep 17 00:00:00 2001 From: Vri Date: Fri, 27 Jan 2023 16:17:35 +0000 Subject: [PATCH 109/189] Translated using Weblate (German) Currently translated at 100.0% (91 of 91 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/de/ --- fastlane/metadata/android/de-DE/changelogs/40105220.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/de-DE/changelogs/40105220.txt diff --git a/fastlane/metadata/android/de-DE/changelogs/40105220.txt b/fastlane/metadata/android/de-DE/changelogs/40105220.txt new file mode 100644 index 0000000000..0f8b294746 --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/40105220.txt @@ -0,0 +1,2 @@ +Die wichtigsten Änderungen in dieser Version: HauptsÀchlich Verbesserungen fÃŒr SprachÃŒbertragungen. +VollstÀndiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases From b40c1185740d0b3994b2bda69649d44fa7e50f92 Mon Sep 17 00:00:00 2001 From: Glandos Date: Sat, 28 Jan 2023 13:36:23 +0000 Subject: [PATCH 110/189] Translated using Weblate (French) Currently translated at 100.0% (91 of 91 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/fr/ --- fastlane/metadata/android/fr-FR/changelogs/40105220.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/fr-FR/changelogs/40105220.txt diff --git a/fastlane/metadata/android/fr-FR/changelogs/40105220.txt b/fastlane/metadata/android/fr-FR/changelogs/40105220.txt new file mode 100644 index 0000000000..2c871c4b17 --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/40105220.txt @@ -0,0 +1,2 @@ +Principaux changements pour cette version : Principalement des améliorations sur la fonction de diffusion audio. +Intégralité des changements : https://github.com/vector-im/element-android/releases From 82ee118af22b991ccf261b15c359004603a1623b Mon Sep 17 00:00:00 2001 From: Jozef Gaal Date: Sat, 28 Jan 2023 22:48:10 +0000 Subject: [PATCH 111/189] Translated using Weblate (Slovak) Currently translated at 100.0% (91 of 91 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/sk/ --- fastlane/metadata/android/sk/changelogs/40105220.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/sk/changelogs/40105220.txt diff --git a/fastlane/metadata/android/sk/changelogs/40105220.txt b/fastlane/metadata/android/sk/changelogs/40105220.txt new file mode 100644 index 0000000000..95063822ef --- /dev/null +++ b/fastlane/metadata/android/sk/changelogs/40105220.txt @@ -0,0 +1,2 @@ +Hlavné zmeny v tejto verzii: VylepÅ¡enia funkcie hlasového vysielania. +ÚplnÃœ zoznam zmien: https://github.com/vector-im/element-android/releases From 017f880aae81f5aa96f6acf72b153dbcde5cb84c Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Fri, 27 Jan 2023 19:08:08 +0000 Subject: [PATCH 112/189] Translated using Weblate (Swedish) Currently translated at 100.0% (91 of 91 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/sv/ --- fastlane/metadata/android/sv-SE/changelogs/40105220.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/sv-SE/changelogs/40105220.txt diff --git a/fastlane/metadata/android/sv-SE/changelogs/40105220.txt b/fastlane/metadata/android/sv-SE/changelogs/40105220.txt new file mode 100644 index 0000000000..c213f01a58 --- /dev/null +++ b/fastlane/metadata/android/sv-SE/changelogs/40105220.txt @@ -0,0 +1,2 @@ +Huvudsakliga Àndringar i den hÀr versionen: Huvudsakligen förbÀttringar för röstsÀndningsfunktion. +Full Àndringslogg: https://github.com/vector-im/element-android/releases From f3e4a92b8f74bf937354b0df3c417d80c045922e Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Fri, 27 Jan 2023 16:35:06 +0000 Subject: [PATCH 113/189] Translated using Weblate (Ukrainian) Currently translated at 100.0% (91 of 91 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/uk/ --- fastlane/metadata/android/uk/changelogs/40105220.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/uk/changelogs/40105220.txt diff --git a/fastlane/metadata/android/uk/changelogs/40105220.txt b/fastlane/metadata/android/uk/changelogs/40105220.txt new file mode 100644 index 0000000000..bd5669f116 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/40105220.txt @@ -0,0 +1,2 @@ +ОсМПвМі зЌіМО в цій версії: ГПлПвМОЌ чОМПЌ пПліпшеМП фуМкцію гПлПсПвПї траМсляції. +ЖурМал усіх зЌіМ: https://github.com/vector-im/element-android/releases From f578b7b1f0a611192d4afb2768092ad2820f2221 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Sat, 28 Jan 2023 12:58:53 +0000 Subject: [PATCH 114/189] Translated using Weblate (Estonian) Currently translated at 100.0% (91 of 91 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/et/ --- fastlane/metadata/android/et/changelogs/40105220.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/et/changelogs/40105220.txt diff --git a/fastlane/metadata/android/et/changelogs/40105220.txt b/fastlane/metadata/android/et/changelogs/40105220.txt new file mode 100644 index 0000000000..c085958fd8 --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/40105220.txt @@ -0,0 +1,2 @@ +Põhilised muutused selles versioonis: tÀiendused ringhÀÀlingukõnede lahendusele. +Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases From bd9bc0ca4234499cae458ef05f90577c7b60226f Mon Sep 17 00:00:00 2001 From: Danial Behzadi Date: Sat, 28 Jan 2023 06:41:46 +0000 Subject: [PATCH 115/189] Translated using Weblate (Persian) Currently translated at 100.0% (91 of 91 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/fa/ --- fastlane/metadata/android/fa/changelogs/40105220.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/fa/changelogs/40105220.txt diff --git a/fastlane/metadata/android/fa/changelogs/40105220.txt b/fastlane/metadata/android/fa/changelogs/40105220.txt new file mode 100644 index 0000000000..be91236ca0 --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40105220.txt @@ -0,0 +1,2 @@ +تغییرات عمده در این نگار؎: ؚی؎‌تر ؚهؚود در ویژگی ٟخ؎ صوتی. +گزار؎ دگرگونی کامل: https://github.com/vector-im/element-android/releases From cf8436db9f6fab972f2e835bbb9f88f9708a1c00 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Mon, 30 Jan 2023 07:02:19 +0000 Subject: [PATCH 116/189] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (91 of 91 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/zh_Hant/ --- fastlane/metadata/android/zh-TW/changelogs/40105220.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/zh-TW/changelogs/40105220.txt diff --git a/fastlane/metadata/android/zh-TW/changelogs/40105220.txt b/fastlane/metadata/android/zh-TW/changelogs/40105220.txt new file mode 100644 index 0000000000..a47d30aeb3 --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/40105220.txt @@ -0,0 +1,2 @@ +歀版本䞭的䞻芁變動䞻芁改善音蚊廣播功胜。 +完敎的變曎玀錄https://github.com/vector-im/element-android/releases From e0016dd46ed8d3a39a43665432fffdbdc42e1e8a Mon Sep 17 00:00:00 2001 From: waclaw66 Date: Mon, 30 Jan 2023 06:49:19 +0000 Subject: [PATCH 117/189] Translated using Weblate (Czech) Currently translated at 100.0% (91 of 91 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/cs/ --- fastlane/metadata/android/cs-CZ/changelogs/40105220.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/cs-CZ/changelogs/40105220.txt diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40105220.txt b/fastlane/metadata/android/cs-CZ/changelogs/40105220.txt new file mode 100644 index 0000000000..afbcb7ef34 --- /dev/null +++ b/fastlane/metadata/android/cs-CZ/changelogs/40105220.txt @@ -0,0 +1,2 @@ +Hlavní změny v této verzi: Především vylepÅ¡ení funkce hlasového vysílání. +ÚplnÃœ seznam změn: https://github.com/vector-im/element-android/releases From f25bc7c38de186463e4aac494ec1922b8af4e35c Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Mon, 30 Jan 2023 05:39:26 +0000 Subject: [PATCH 118/189] Translated using Weblate (Japanese) Currently translated at 95.6% (87 of 91 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/ja/ --- fastlane/metadata/android/ja-JP/changelogs/40100100.txt | 2 +- fastlane/metadata/android/ja-JP/changelogs/40100140.txt | 2 +- fastlane/metadata/android/ja-JP/changelogs/40100160.txt | 2 +- fastlane/metadata/android/ja-JP/changelogs/40100170.txt | 2 +- fastlane/metadata/android/ja-JP/changelogs/40101000.txt | 2 +- fastlane/metadata/android/ja-JP/changelogs/40101010.txt | 2 +- fastlane/metadata/android/ja-JP/changelogs/40101020.txt | 2 +- fastlane/metadata/android/ja-JP/changelogs/40101030.txt | 2 +- fastlane/metadata/android/ja-JP/changelogs/40101050.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40101060.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40101070.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40101080.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40101090.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40101110.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40101120.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40101130.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40101140.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40101150.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40102000.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40102010.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40103000.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40103010.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40103020.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40104020.txt | 2 +- fastlane/metadata/android/ja-JP/changelogs/40104040.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40104060.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40104070.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40104080.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40104100.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40104110.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40104120.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40104130.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40104140.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40104160.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40104180.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40104190.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40104200.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40104220.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40104230.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40104240.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40104250.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40104260.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40104270.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40104280.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40104300.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40104310.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40104320.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40104340.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40104360.txt | 3 +++ fastlane/metadata/android/ja-JP/changelogs/40105000.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40105020.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40105040.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40105060.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40105070.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40105080.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40105100.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40105110.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40105120.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40105130.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40105140.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40105160.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40105180.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40105200.txt | 2 ++ fastlane/metadata/android/ja-JP/changelogs/40105220.txt | 2 ++ fastlane/metadata/android/ja-JP/full_description.txt | 2 +- 65 files changed, 121 insertions(+), 10 deletions(-) create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40101050.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40101060.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40101070.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40101080.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40101090.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40101110.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40101120.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40101130.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40101140.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40101150.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40102000.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40102010.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40103000.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40103010.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40103020.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40104040.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40104060.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40104070.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40104080.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40104100.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40104110.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40104120.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40104130.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40104140.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40104160.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40104180.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40104190.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40104200.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40104220.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40104230.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40104240.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40104250.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40104260.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40104270.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40104280.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40104300.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40104310.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40104320.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40104340.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40104360.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40105000.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40105020.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40105040.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40105060.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40105070.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40105080.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40105100.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40105110.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40105120.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40105130.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40105140.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40105160.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40105180.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40105200.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40105220.txt diff --git a/fastlane/metadata/android/ja-JP/changelogs/40100100.txt b/fastlane/metadata/android/ja-JP/changelogs/40100100.txt index 0f9fc720a9..2034b963a9 100644 --- a/fastlane/metadata/android/ja-JP/changelogs/40100100.txt +++ b/fastlane/metadata/android/ja-JP/changelogs/40100100.txt @@ -1,2 +1,2 @@ -今回の新バヌゞョンでは、䞻にバグの修正ず改善が行われおいたす。メッセヌゞの送信がより速くなりたした。 +今回の新バヌゞョンでは、䞻に䞍具合の修正ず改善が行われおいたす。メッセヌゞの送信がより速くなりたした。 曎新履歎https://github.com/vector-im/element-android/releases/tag/v1.0.10 diff --git a/fastlane/metadata/android/ja-JP/changelogs/40100140.txt b/fastlane/metadata/android/ja-JP/changelogs/40100140.txt index 8fa9848d0b..8c7bdcfd73 100644 --- a/fastlane/metadata/android/ja-JP/changelogs/40100140.txt +++ b/fastlane/metadata/android/ja-JP/changelogs/40100140.txt @@ -1,2 +1,2 @@ -このバヌゞョンの䞻な倉曎点郚屋の蚱可、自動のテヌマ切替、そしお倚くのバグを修正したした。 +このバヌゞョンの䞻な倉曎点郚屋の蚱可、自動のテヌマ切替、そしお倚くの䞍具合を修正したした。 曎新履歎https://github.com/vector-im/element-android/releases/tag/v1.0.14 diff --git a/fastlane/metadata/android/ja-JP/changelogs/40100160.txt b/fastlane/metadata/android/ja-JP/changelogs/40100160.txt index ae947f1781..169b4ce67f 100644 --- a/fastlane/metadata/android/ja-JP/changelogs/40100160.txt +++ b/fastlane/metadata/android/ja-JP/changelogs/40100160.txt @@ -1,2 +1,2 @@ -このバヌゞョンの䞻な倉曎点パフォヌマンスの向䞊ず、バグを修正したした +このバヌゞョンの䞻な倉曎点パフォヌマンスの向䞊ず、䞍具合を修正したした 曎新履歎https://github.com/vector-im/element-android/releases/tag/v1.0.15 and https://github.com/vector-im/element-android/releases/tag/v1.0.16 diff --git a/fastlane/metadata/android/ja-JP/changelogs/40100170.txt b/fastlane/metadata/android/ja-JP/changelogs/40100170.txt index 01b742a9a2..6d19fcaa2d 100644 --- a/fastlane/metadata/android/ja-JP/changelogs/40100170.txt +++ b/fastlane/metadata/android/ja-JP/changelogs/40100170.txt @@ -1,2 +1,2 @@ -このバヌゞョンの䞻な倉曎点バグを修正したした +このバヌゞョンの䞻な倉曎点䞍具合を修正したした 曎新履歎https://github.com/vector-im/element-android/releases/tag/v1.0.17 diff --git a/fastlane/metadata/android/ja-JP/changelogs/40101000.txt b/fastlane/metadata/android/ja-JP/changelogs/40101000.txt index 0c09cee3dd..1b85fe8725 100644 --- a/fastlane/metadata/android/ja-JP/changelogs/40101000.txt +++ b/fastlane/metadata/android/ja-JP/changelogs/40101000.txt @@ -1,2 +1,2 @@ -このバヌゞョンの䞻な倉曎点パフォヌマンスの向䞊ず、バグを修正したした +このバヌゞョンの䞻な倉曎点パフォヌマンスの向䞊ず、䞍具合を修正したした 曎新履歎https://github.com/vector-im/element-android/releases/tag/v1.1.0 diff --git a/fastlane/metadata/android/ja-JP/changelogs/40101010.txt b/fastlane/metadata/android/ja-JP/changelogs/40101010.txt index 25ac73b449..c5b26dd26f 100644 --- a/fastlane/metadata/android/ja-JP/changelogs/40101010.txt +++ b/fastlane/metadata/android/ja-JP/changelogs/40101010.txt @@ -1,2 +1,2 @@ -このバヌゞョンの䞻な倉曎点パフォヌマンスの向䞊ず、バグを修正したした +このバヌゞョンの䞻な倉曎点パフォヌマンスの向䞊ず、䞍具合を修正したした 曎新履歎https://github.com/vector-im/element-android/releases/tag/v1.1.1 diff --git a/fastlane/metadata/android/ja-JP/changelogs/40101020.txt b/fastlane/metadata/android/ja-JP/changelogs/40101020.txt index 762879a281..763dcd18c8 100644 --- a/fastlane/metadata/android/ja-JP/changelogs/40101020.txt +++ b/fastlane/metadata/android/ja-JP/changelogs/40101020.txt @@ -1,2 +1,2 @@ -このバヌゞョンの䞻な倉曎点パフォヌマンスの向䞊ず、バグを修正したした +このバヌゞョンの䞻な倉曎点パフォヌマンスの向䞊ず、䞍具合を修正したした 曎新履歎https://github.com/vector-im/element-android/releases/tag/v1.1.2 diff --git a/fastlane/metadata/android/ja-JP/changelogs/40101030.txt b/fastlane/metadata/android/ja-JP/changelogs/40101030.txt index 3c641c09ac..5b65e8247c 100644 --- a/fastlane/metadata/android/ja-JP/changelogs/40101030.txt +++ b/fastlane/metadata/android/ja-JP/changelogs/40101030.txt @@ -1,2 +1,2 @@ -このバヌゞョンの䞻な倉曎点パフォヌマンスの向䞊ず、バグを修正したした +このバヌゞョンの䞻な倉曎点パフォヌマンスの向䞊ず、䞍具合を修正したした 曎新履歎https://github.com/vector-im/element-android/releases/tag/v1.1.3 diff --git a/fastlane/metadata/android/ja-JP/changelogs/40101050.txt b/fastlane/metadata/android/ja-JP/changelogs/40101050.txt new file mode 100644 index 0000000000..3b30ef25a1 --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40101050.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点1.1.4のホットフィックス +曎新履歎https://github.com/vector-im/element-android/releases/tag/v1.1.5 diff --git a/fastlane/metadata/android/ja-JP/changelogs/40101060.txt b/fastlane/metadata/android/ja-JP/changelogs/40101060.txt new file mode 100644 index 0000000000..0c3e5663c4 --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40101060.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点1.1.5のホットフィックス +曎新履歎https://github.com/vector-im/element-android/releases/tag/v1.1.6 diff --git a/fastlane/metadata/android/ja-JP/changelogs/40101070.txt b/fastlane/metadata/android/ja-JP/changelogs/40101070.txt new file mode 100644 index 0000000000..8551b3fb30 --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40101070.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点スペヌスのベヌタ版。送信前に動画を圧瞮。 +曎新履歎https://github.com/vector-im/element-android/releases/tag/v1.1.7 diff --git a/fastlane/metadata/android/ja-JP/changelogs/40101080.txt b/fastlane/metadata/android/ja-JP/changelogs/40101080.txt new file mode 100644 index 0000000000..9ef3a834e6 --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40101080.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点スペヌスの改善。 +曎新履歎https://github.com/vector-im/element-android/releases/tag/v1.1.8 diff --git a/fastlane/metadata/android/ja-JP/changelogs/40101090.txt b/fastlane/metadata/android/ja-JP/changelogs/40101090.txt new file mode 100644 index 0000000000..615681a617 --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40101090.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点gitter.imに察応。 +曎新履歎https://github.com/vector-im/element-android/releases/tag/v1.1.9 diff --git a/fastlane/metadata/android/ja-JP/changelogs/40101110.txt b/fastlane/metadata/android/ja-JP/changelogs/40101110.txt new file mode 100644 index 0000000000..ee107872e2 --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40101110.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点テヌマずスタむルの曎新、スペヌスの新しい機胜1.1.10の䞍具合の修正 +曎新履歎https://github.com/vector-im/element-android/releases/tag/v1.1.11 diff --git a/fastlane/metadata/android/ja-JP/changelogs/40101120.txt b/fastlane/metadata/android/ja-JP/changelogs/40101120.txt new file mode 100644 index 0000000000..1b6c3cdeb2 --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40101120.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点テヌマずスタむルの曎新、ビデオ通話の埌のクラッシュを修正 +曎新履歎https://github.com/vector-im/element-android/releases/tag/v1.1.12 diff --git a/fastlane/metadata/android/ja-JP/changelogs/40101130.txt b/fastlane/metadata/android/ja-JP/changelogs/40101130.txt new file mode 100644 index 0000000000..3dce47fd25 --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40101130.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点安定性の改善ず䞍具合の修正。 +曎新履歎https://github.com/vector-im/element-android/releases/tag/v1.1.13 diff --git a/fastlane/metadata/android/ja-JP/changelogs/40101140.txt b/fastlane/metadata/android/ja-JP/changelogs/40101140.txt new file mode 100644 index 0000000000..79d2a9aacb --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40101140.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点暗号化したメッセヌゞに関する䞍具合の修正。 +曎新履歎https://github.com/vector-im/element-android/releases/tag/v1.1.14 diff --git a/fastlane/metadata/android/ja-JP/changelogs/40101150.txt b/fastlane/metadata/android/ja-JP/changelogs/40101150.txt new file mode 100644 index 0000000000..2509a1a714 --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40101150.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点ラボの蚭定に音声メッセヌゞの実装を远加。 +曎新履歎https://github.com/vector-im/element-android/releases/tag/v1.1.15 diff --git a/fastlane/metadata/android/ja-JP/changelogs/40102000.txt b/fastlane/metadata/android/ja-JP/changelogs/40102000.txt new file mode 100644 index 0000000000..76ccb8d0b5 --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40102000.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点音声メッセヌゞを既定で有効化。 +曎新履歎https://github.com/vector-im/element-android/releases/tag/v1.2.0 diff --git a/fastlane/metadata/android/ja-JP/changelogs/40102010.txt b/fastlane/metadata/android/ja-JP/changelogs/40102010.txt new file mode 100644 index 0000000000..204ffdc52d --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40102010.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点VoIPずスペヌスベヌタ版に関する改善。 +曎新履歎https://github.com/vector-im/element-android/releases/tag/v1.2.1 diff --git a/fastlane/metadata/android/ja-JP/changelogs/40103000.txt b/fastlane/metadata/android/ja-JP/changelogs/40103000.txt new file mode 100644 index 0000000000..7243945833 --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40103000.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点スペヌス機胜の実装 +曎新履歎https://github.com/vector-im/element-android/releases/tag/v1.3.0 diff --git a/fastlane/metadata/android/ja-JP/changelogs/40103010.txt b/fastlane/metadata/android/ja-JP/changelogs/40103010.txt new file mode 100644 index 0000000000..76fd62ee41 --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40103010.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点スペヌス機胜。1.3.0のクラッシュの修正。 +曎新履歎https://github.com/vector-im/element-android/releases/tag/v1.3.1 diff --git a/fastlane/metadata/android/ja-JP/changelogs/40103020.txt b/fastlane/metadata/android/ja-JP/changelogs/40103020.txt new file mode 100644 index 0000000000..5c05c18486 --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40103020.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点Android Autoのサポヌト。䞍具合の修正。 +曎新履歎https://github.com/vector-im/element-android/releases/tag/v1.3.2 diff --git a/fastlane/metadata/android/ja-JP/changelogs/40104020.txt b/fastlane/metadata/android/ja-JP/changelogs/40104020.txt index e792008faf..49bc764ac9 100644 --- a/fastlane/metadata/android/ja-JP/changelogs/40104020.txt +++ b/fastlane/metadata/android/ja-JP/changelogs/40104020.txt @@ -1,2 +1,2 @@ -このバヌゞョンの䞻な倉曎点@roomの察応、非公開の投祚など。 +このバヌゞョンの䞻な倉曎点@roomの察応、非公開のアンケヌトなど。 曎新履歎https://github.com/vector-im/element-android/releases/tag/v1.4.2 diff --git a/fastlane/metadata/android/ja-JP/changelogs/40104040.txt b/fastlane/metadata/android/ja-JP/changelogs/40104040.txt new file mode 100644 index 0000000000..1c37d1c948 --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40104040.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点入力䞭のむンゞケヌタヌのUIを曎新。䞍具合の修正ず安定性の改善。 +曎新履歎https://github.com/vector-im/element-android/releases/tag/v1.4.4 diff --git a/fastlane/metadata/android/ja-JP/changelogs/40104060.txt b/fastlane/metadata/android/ja-JP/changelogs/40104060.txt new file mode 100644 index 0000000000..bb27f21a19 --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40104060.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点スレッドのタむムラむンの有効化ず高速化。䞍具合の修正ず安定性の改善。 +曎新履歎https://github.com/vector-im/element-android/releases/tag/v1.4.6 diff --git a/fastlane/metadata/android/ja-JP/changelogs/40104070.txt b/fastlane/metadata/android/ja-JP/changelogs/40104070.txt new file mode 100644 index 0000000000..a2b55f615e --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40104070.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点䞍具合の修正ず安定性の改善。 +曎新履歎https://github.com/vector-im/element-android/releases/tag/v1.4.7 diff --git a/fastlane/metadata/android/ja-JP/changelogs/40104080.txt b/fastlane/metadata/android/ja-JP/changelogs/40104080.txt new file mode 100644 index 0000000000..f0377e0d91 --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40104080.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点スレッドのタむムラむンの有効化ず高速化。䞍具合の修正ず安定性の改善。 +曎新履歎https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ja-JP/changelogs/40104100.txt b/fastlane/metadata/android/ja-JP/changelogs/40104100.txt new file mode 100644 index 0000000000..6bd2ce71f9 --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40104100.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点音声メッセヌゞでのスクロヌル。䞍具合の修正ず安定性の改善。 +曎新履歎https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ja-JP/changelogs/40104110.txt b/fastlane/metadata/android/ja-JP/changelogs/40104110.txt new file mode 100644 index 0000000000..6dfe0935ea --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40104110.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点䞍具合の修正ず安定性の改善。 +曎新履歎https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ja-JP/changelogs/40104120.txt b/fastlane/metadata/android/ja-JP/changelogs/40104120.txt new file mode 100644 index 0000000000..a830059d63 --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40104120.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点オンラむンの状態を衚瀺しない蚭定を远加。音声の添付ファむルのプレむダヌを远加 +曎新履歎https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ja-JP/changelogs/40104130.txt b/fastlane/metadata/android/ja-JP/changelogs/40104130.txt new file mode 100644 index 0000000000..a830059d63 --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40104130.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点オンラむンの状態を衚瀺しない蚭定を远加。音声の添付ファむルのプレむダヌを远加 +曎新履歎https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ja-JP/changelogs/40104140.txt b/fastlane/metadata/android/ja-JP/changelogs/40104140.txt new file mode 100644 index 0000000000..265c306a33 --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40104140.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点無芖したナヌザヌの管理を改善。䞍具合の修正ず安定性の改善。 +曎新履歎https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ja-JP/changelogs/40104160.txt b/fastlane/metadata/android/ja-JP/changelogs/40104160.txt new file mode 100644 index 0000000000..899acf0262 --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40104160.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点暗号化されたメッセヌゞの管理を改善。䞍具合の修正ず安定性の改善。 +曎新履歎https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ja-JP/changelogs/40104180.txt b/fastlane/metadata/android/ja-JP/changelogs/40104180.txt new file mode 100644 index 0000000000..6dfe0935ea --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40104180.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点䞍具合の修正ず安定性の改善。 +曎新履歎https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ja-JP/changelogs/40104190.txt b/fastlane/metadata/android/ja-JP/changelogs/40104190.txt new file mode 100644 index 0000000000..6dfe0935ea --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40104190.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点䞍具合の修正ず安定性の改善。 +曎新履歎https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ja-JP/changelogs/40104200.txt b/fastlane/metadata/android/ja-JP/changelogs/40104200.txt new file mode 100644 index 0000000000..6dfe0935ea --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40104200.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点䞍具合の修正ず安定性の改善。 +曎新履歎https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ja-JP/changelogs/40104220.txt b/fastlane/metadata/android/ja-JP/changelogs/40104220.txt new file mode 100644 index 0000000000..6dfe0935ea --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40104220.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点䞍具合の修正ず安定性の改善。 +曎新履歎https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ja-JP/changelogs/40104230.txt b/fastlane/metadata/android/ja-JP/changelogs/40104230.txt new file mode 100644 index 0000000000..6dfe0935ea --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40104230.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点䞍具合の修正ず安定性の改善。 +曎新履歎https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ja-JP/changelogs/40104240.txt b/fastlane/metadata/android/ja-JP/changelogs/40104240.txt new file mode 100644 index 0000000000..6dfe0935ea --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40104240.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点䞍具合の修正ず安定性の改善。 +曎新履歎https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ja-JP/changelogs/40104250.txt b/fastlane/metadata/android/ja-JP/changelogs/40104250.txt new file mode 100644 index 0000000000..6dfe0935ea --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40104250.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点䞍具合の修正ず安定性の改善。 +曎新履歎https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ja-JP/changelogs/40104260.txt b/fastlane/metadata/android/ja-JP/changelogs/40104260.txt new file mode 100644 index 0000000000..2180774da8 --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40104260.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点UnifiedPushを採甚し、FCMなしでプッシュ通知を送信する機胜を远加。 +曎新履歎https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ja-JP/changelogs/40104270.txt b/fastlane/metadata/android/ja-JP/changelogs/40104270.txt new file mode 100644 index 0000000000..6dfe0935ea --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40104270.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点䞍具合の修正ず安定性の改善。 +曎新履歎https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ja-JP/changelogs/40104280.txt b/fastlane/metadata/android/ja-JP/changelogs/40104280.txt new file mode 100644 index 0000000000..6dfe0935ea --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40104280.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点䞍具合の修正ず安定性の改善。 +曎新履歎https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ja-JP/changelogs/40104300.txt b/fastlane/metadata/android/ja-JP/changelogs/40104300.txt new file mode 100644 index 0000000000..82685764cc --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40104300.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点サむンむンずサむンアップのプロセスを改善。 +曎新履歎https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ja-JP/changelogs/40104310.txt b/fastlane/metadata/android/ja-JP/changelogs/40104310.txt new file mode 100644 index 0000000000..82685764cc --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40104310.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点サむンむンずサむンアップのプロセスを改善。 +曎新履歎https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ja-JP/changelogs/40104320.txt b/fastlane/metadata/android/ja-JP/changelogs/40104320.txt new file mode 100644 index 0000000000..6dfe0935ea --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40104320.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点䞍具合の修正ず安定性の改善。 +曎新履歎https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ja-JP/changelogs/40104340.txt b/fastlane/metadata/android/ja-JP/changelogs/40104340.txt new file mode 100644 index 0000000000..6dfe0935ea --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40104340.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点䞍具合の修正ず安定性の改善。 +曎新履歎https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ja-JP/changelogs/40104360.txt b/fastlane/metadata/android/ja-JP/changelogs/40104360.txt new file mode 100644 index 0000000000..5a1d73c08a --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40104360.txt @@ -0,0 +1,3 @@ +新しいレむアりトをラボの蚭定で有効にできたす。詊しおみおください +通知に関する問題、同期に必芁な時間に関する䞍具合を修正したした。 +曎新履歎https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ja-JP/changelogs/40105000.txt b/fastlane/metadata/android/ja-JP/changelogs/40105000.txt new file mode 100644 index 0000000000..980ff9b873 --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40105000.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点遅延DMを既定で有効化。 +曎新履歎https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ja-JP/changelogs/40105020.txt b/fastlane/metadata/android/ja-JP/changelogs/40105020.txt new file mode 100644 index 0000000000..47aba6a1d7 --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40105020.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点新しいレむアりトを既定で有効化 +曎新履歎https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ja-JP/changelogs/40105040.txt b/fastlane/metadata/android/ja-JP/changelogs/40105040.txt new file mode 100644 index 0000000000..8269b4b35e --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40105040.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点ラボの蚭定に新しい機胜リッチテキスト゚ディタヌ、端末の新しい管理画面、音声配信を远加。開発䞭です +曎新履歎https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ja-JP/changelogs/40105060.txt b/fastlane/metadata/android/ja-JP/changelogs/40105060.txt new file mode 100644 index 0000000000..ddf18871f8 --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40105060.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点添付ファむルの遞択画面の曎新。 +曎新履歎https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ja-JP/changelogs/40105070.txt b/fastlane/metadata/android/ja-JP/changelogs/40105070.txt new file mode 100644 index 0000000000..ddf18871f8 --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40105070.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点添付ファむルの遞択画面の曎新。 +曎新履歎https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ja-JP/changelogs/40105080.txt b/fastlane/metadata/android/ja-JP/changelogs/40105080.txt new file mode 100644 index 0000000000..f1b740f1d7 --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40105080.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点䞍具合の修正ず改善。 +曎新履歎https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ja-JP/changelogs/40105100.txt b/fastlane/metadata/android/ja-JP/changelogs/40105100.txt new file mode 100644 index 0000000000..eb8c3f1077 --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40105100.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点リッチテキスト゚ディタヌの党画面モヌドを新たに実装。䞍具合の修正。 +曎新履歎https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ja-JP/changelogs/40105110.txt b/fastlane/metadata/android/ja-JP/changelogs/40105110.txt new file mode 100644 index 0000000000..eb8c3f1077 --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40105110.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点リッチテキスト゚ディタヌの党画面モヌドを新たに実装。䞍具合の修正。 +曎新履歎https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ja-JP/changelogs/40105120.txt b/fastlane/metadata/android/ja-JP/changelogs/40105120.txt new file mode 100644 index 0000000000..a476e0961b --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40105120.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点スレッド機胜を既定で有効化。 +曎新履歎https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ja-JP/changelogs/40105130.txt b/fastlane/metadata/android/ja-JP/changelogs/40105130.txt new file mode 100644 index 0000000000..a476e0961b --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40105130.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点スレッド機胜を既定で有効化。 +曎新履歎https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ja-JP/changelogs/40105140.txt b/fastlane/metadata/android/ja-JP/changelogs/40105140.txt new file mode 100644 index 0000000000..a476e0961b --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40105140.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点スレッド機胜を既定で有効化。 +曎新履歎https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ja-JP/changelogs/40105160.txt b/fastlane/metadata/android/ja-JP/changelogs/40105160.txt new file mode 100644 index 0000000000..a476e0961b --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40105160.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点スレッド機胜を既定で有効化。 +曎新履歎https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ja-JP/changelogs/40105180.txt b/fastlane/metadata/android/ja-JP/changelogs/40105180.txt new file mode 100644 index 0000000000..a476e0961b --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40105180.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点スレッド機胜を既定で有効化。 +曎新履歎https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ja-JP/changelogs/40105200.txt b/fastlane/metadata/android/ja-JP/changelogs/40105200.txt new file mode 100644 index 0000000000..9d001b89be --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40105200.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点䞍具合の修正。 +曎新履歎https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ja-JP/changelogs/40105220.txt b/fastlane/metadata/android/ja-JP/changelogs/40105220.txt new file mode 100644 index 0000000000..da0627e6ac --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40105220.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点音声配信機胜の改善。 +曎新履歎https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/ja-JP/full_description.txt b/fastlane/metadata/android/ja-JP/full_description.txt index ce1550acb0..657acd77d2 100644 --- a/fastlane/metadata/android/ja-JP/full_description.txt +++ b/fastlane/metadata/android/ja-JP/full_description.txt @@ -36,7 +36,7 @@ Elementでは、どのサヌバヌを䜿うかをご自身で決めるこずが メッセヌゞング、音声およびビデオ通話、ファむル共有、画面共有、その他倚くの機胜統合、ボット、りィゞェットを提䟛したす。ルヌムやコミュニティヌを立ち䞊げお連絡を取り合い、物事をスムヌズに成し遂げたしょう。 い぀でも、どこにいおも -メッセヌゞの履歎は、党おの端末ずりェブhttps://app.element.ioで完党に同期されるので、どこからでも連絡を取り合うこずができたす。 +メッセヌゞの履歎は、党おの端末ずりェブ https://app.element.io で完党に同期されるので、どこからでも連絡を取り合うこずができたす。 オヌプン゜ヌス Element Androidは、GitHubで開発されおいるオヌプン゜ヌスのプロゞェクトです。 䞍具合の報告や開発ぞの貢献は https://github.com/vector-im/element-android にお受け付けおいたす。 From 8c9fc55669697cf9a1fd07cf3c9a7cfc59fc7ccf Mon Sep 17 00:00:00 2001 From: Linerly Date: Fri, 27 Jan 2023 16:03:13 +0000 Subject: [PATCH 119/189] Translated using Weblate (Indonesian) Currently translated at 100.0% (91 of 91 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/id/ --- fastlane/metadata/android/id/changelogs/40105220.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/id/changelogs/40105220.txt diff --git a/fastlane/metadata/android/id/changelogs/40105220.txt b/fastlane/metadata/android/id/changelogs/40105220.txt new file mode 100644 index 0000000000..849ffbee9a --- /dev/null +++ b/fastlane/metadata/android/id/changelogs/40105220.txt @@ -0,0 +1,2 @@ +Perubahan utama dalam versi ini: Banyak perbaikan terutama pada fitur siaran suara. +Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases From f74c7fa997dc246d7f8dec873f01c2934cec7433 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 31 Jan 2023 10:14:28 +0100 Subject: [PATCH 120/189] Handle the potential offset on the chunk position used by the media player --- .../listening/VoiceBroadcastPlayerImpl.kt | 18 +++++++++++------- .../listening/VoiceBroadcastPlaylist.kt | 4 ++-- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt index 61e4bf640c..bc07e174d3 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt @@ -223,28 +223,32 @@ class VoiceBroadcastPlayerImpl @Inject constructor( } } - private fun startPlayback(position: Int) { + private fun startPlayback(playbackPosition: Int) { stopPlayer() playingState = State.Buffering - val playlistItem = playlist.findByPosition(position) ?: run { - Timber.w("## Voice Broadcast | No content to play at position $position"); stop(); return + val playlistItem = playlist.findByPosition(playbackPosition) ?: run { + Timber.w("## Voice Broadcast | No content to play at position $playbackPosition"); stop(); return } val sequence = playlistItem.sequence ?: run { Timber.w("## Voice Broadcast | Playlist item has no sequence"); stop(); return } - val sequencePosition = position - playlistItem.startTime currentVoiceBroadcast?.let { - val percentage = tryOrNull { position.toFloat() / playlist.duration } ?: 0f - playbackTracker.updatePausedAtPlaybackTime(it.voiceBroadcastId, position, percentage) + val percentage = tryOrNull { playbackPosition.toFloat() / playlist.duration } ?: 0f + playbackTracker.updatePausedAtPlaybackTime(it.voiceBroadcastId, playbackPosition, percentage) } prepareCurrentPlayerJob = sessionScope.launch { try { val mp = prepareMediaPlayer(playlistItem.audioEvent.content) - playlist.currentSequence = sequence - 1 // will be incremented in onNextMediaPlayerStarted + // Take the difference between the duration given from the media player and the duration given from the chunk event + // If the offset is smaller than 500ms, we consider there is no offset to keep the normal behaviour + val offset = (mp.duration - playlistItem.duration).takeUnless { it < 500 }?.coerceAtLeast(0) ?: 0 + val sequencePosition = offset + (playbackPosition - playlistItem.startTime) + + playlist.currentSequence = sequence - 1 // will be incremented in onNextMediaPlayerStarted mp.start() if (sequencePosition > 0) { mp.seekTo(sequencePosition) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlaylist.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlaylist.kt index 36b737f23f..437b216d77 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlaylist.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlaylist.kt @@ -65,6 +65,6 @@ class VoiceBroadcastPlaylist( } data class PlaylistItem(val audioEvent: MessageAudioEvent, val startTime: Int) { - val sequence: Int? - get() = audioEvent.sequence + val sequence: Int? = audioEvent.sequence + val duration: Int = audioEvent.duration } From 2152c1d7bac3a39d5a08e037727fcfe38fc89638 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 30 Jan 2023 17:24:27 +0100 Subject: [PATCH 121/189] Do not compute live broadcast on last message if the feature is disabled in the lab --- .../usecase/GetLatestPreviewableEventUseCase.kt | 14 +++++++++++--- .../GetLatestPreviewableEventUseCaseTest.kt | 4 ++++ .../vector/app/test/fakes/FakeVectorPreferences.kt | 4 ++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/usecase/GetLatestPreviewableEventUseCase.kt b/vector/src/main/java/im/vector/app/features/home/room/list/usecase/GetLatestPreviewableEventUseCase.kt index 6a50e87562..94f9136a2f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/usecase/GetLatestPreviewableEventUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/usecase/GetLatestPreviewableEventUseCase.kt @@ -17,6 +17,7 @@ package im.vector.app.features.home.room.list.usecase import im.vector.app.core.di.ActiveSessionHolder +import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.voicebroadcast.isLive import im.vector.app.features.voicebroadcast.isVoiceBroadcast import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent @@ -35,14 +36,21 @@ import javax.inject.Inject class GetLatestPreviewableEventUseCase @Inject constructor( private val sessionHolder: ActiveSessionHolder, private val getRoomLiveVoiceBroadcastsUseCase: GetRoomLiveVoiceBroadcastsUseCase, + private val vectorPreferences: VectorPreferences, ) { 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) + // FIXME Observing live broadcasts results in many db requests, + // to prevent performances issues, we only enable this mechanism if the voice broadcast flag is enabled + return if (vectorPreferences.isVoiceBroadcastEnabled()) { + getCallEvent(roomSummary) + ?: getLiveVoiceBroadcastEvent(room) + ?: getDefaultLatestEvent(room, roomSummary) + } else { + roomSummary.latestPreviewableEvent + } } private fun getCallEvent(roomSummary: RoomSummary): TimelineEvent? { diff --git a/vector/src/test/java/im/vector/app/features/home/room/list/usecase/GetLatestPreviewableEventUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/home/room/list/usecase/GetLatestPreviewableEventUseCaseTest.kt index 5d526c783b..f7dd5da30e 100644 --- a/vector/src/test/java/im/vector/app/features/home/room/list/usecase/GetLatestPreviewableEventUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/home/room/list/usecase/GetLatestPreviewableEventUseCaseTest.kt @@ -23,6 +23,7 @@ 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 im.vector.app.test.fakes.FakeVectorPreferences import io.mockk.every import io.mockk.mockk import org.amshove.kluent.shouldBe @@ -46,10 +47,12 @@ internal class GetLatestPreviewableEventUseCaseTest { private val fakeSessionHolder = FakeActiveSessionHolder() private val fakeRoomSummary = mockk() private val fakeGetRoomLiveVoiceBroadcastsUseCase = mockk() + private val fakeVectorPreferences = FakeVectorPreferences() private val getLatestPreviewableEventUseCase = GetLatestPreviewableEventUseCase( fakeSessionHolder.instance, fakeGetRoomLiveVoiceBroadcastsUseCase, + fakeVectorPreferences.instance, ) @Before @@ -62,6 +65,7 @@ internal class GetLatestPreviewableEventUseCaseTest { every { eventId } returns firstArg() } } + fakeVectorPreferences.givenIsVoiceBroadcastEnabled(true) } @Test diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeVectorPreferences.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeVectorPreferences.kt index 3d3f415778..7a5831ffed 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeVectorPreferences.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeVectorPreferences.kt @@ -85,4 +85,8 @@ class FakeVectorPreferences { fun verifySetIpAddressVisibilityInDeviceManagerScreens(isVisible: Boolean) { verify { instance.setIpAddressVisibilityInDeviceManagerScreens(isVisible) } } + + fun givenIsVoiceBroadcastEnabled(isEnabled: Boolean) { + every { instance.isVoiceBroadcastEnabled() } returns isEnabled + } } From 2267d599ed78d4dc9a28169b660441c69fed890d Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 31 Jan 2023 10:33:15 +0100 Subject: [PATCH 122/189] Changelog --- changelog.d/8042.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/8042.misc diff --git a/changelog.d/8042.misc b/changelog.d/8042.misc new file mode 100644 index 0000000000..dbfe98140c --- /dev/null +++ b/changelog.d/8042.misc @@ -0,0 +1 @@ +[Voice Broadcast] Show Live broadcast in the room list only if the feature flag is enabled in the lab From a9152dfe0bd90d40af96562da14a79cb6b1c942f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 31 Jan 2023 10:29:25 +0000 Subject: [PATCH 123/189] Bump alex-page/github-project-automation-plus from 0.8.2 to 0.8.3 (#8035) Bumps [alex-page/github-project-automation-plus](https://github.com/alex-page/github-project-automation-plus) from 0.8.2 to 0.8.3. - [Release notes](https://github.com/alex-page/github-project-automation-plus/releases) - [Commits](https://github.com/alex-page/github-project-automation-plus/compare/1f8873e97e3c8f58161a323b7c568c1f623a1c4d...7ffb872c64bd809d23563a130a0a97d01dfa8f43) --- updated-dependencies: - dependency-name: alex-page/github-project-automation-plus dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/triage-incoming.yml | 2 +- .github/workflows/triage-priority-bugs.yml | 4 ++-- .github/workflows/triage-unlabelled.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/triage-incoming.yml b/.github/workflows/triage-incoming.yml index 4dadc25ab4..56bad8a2d7 100644 --- a/.github/workflows/triage-incoming.yml +++ b/.github/workflows/triage-incoming.yml @@ -10,7 +10,7 @@ jobs: # Skip in forks if: github.repository == 'vector-im/element-android' steps: - - uses: alex-page/github-project-automation-plus@1f8873e97e3c8f58161a323b7c568c1f623a1c4d + - uses: alex-page/github-project-automation-plus@7ffb872c64bd809d23563a130a0a97d01dfa8f43 with: project: Issue triage column: Incoming diff --git a/.github/workflows/triage-priority-bugs.yml b/.github/workflows/triage-priority-bugs.yml index 07e73fe805..c109b06b7e 100644 --- a/.github/workflows/triage-priority-bugs.yml +++ b/.github/workflows/triage-priority-bugs.yml @@ -24,7 +24,7 @@ jobs: contains(github.event.issue.labels.*.name, 'A11y') && contains(github.event.issue.labels.*.name, 'O-Frequent')) steps: - - uses: alex-page/github-project-automation-plus@1f8873e97e3c8f58161a323b7c568c1f623a1c4d + - uses: alex-page/github-project-automation-plus@7ffb872c64bd809d23563a130a0a97d01dfa8f43 with: project: Android App Team column: Important Issues & Topics (P1) @@ -50,7 +50,7 @@ jobs: contains(github.event.issue.labels.*.name, 'A11y') && contains(github.event.issue.labels.*.name, 'O-Frequent'))) steps: - - uses: alex-page/github-project-automation-plus@1f8873e97e3c8f58161a323b7c568c1f623a1c4d + - uses: alex-page/github-project-automation-plus@7ffb872c64bd809d23563a130a0a97d01dfa8f43 with: project: Crypto Team column: Ready diff --git a/.github/workflows/triage-unlabelled.yml b/.github/workflows/triage-unlabelled.yml index 98d6579958..e29a79e2e7 100644 --- a/.github/workflows/triage-unlabelled.yml +++ b/.github/workflows/triage-unlabelled.yml @@ -28,7 +28,7 @@ jobs: echo "ALREADY_IN_BOARD=false" >> $GITHUB_ENV fi - name: Move issue - uses: alex-page/github-project-automation-plus@1f8873e97e3c8f58161a323b7c568c1f623a1c4d + uses: alex-page/github-project-automation-plus@7ffb872c64bd809d23563a130a0a97d01dfa8f43 if: ${{ env.ALREADY_IN_BOARD == 'true' }} with: project: Issue triage From b904548ba0a911d2253b8d317b48834947466e8a Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 31 Jan 2023 15:45:41 +0100 Subject: [PATCH 124/189] Cancel and start a new timer on pause & resume --- .../lib/core/utils/timer/CountUpTimer.kt | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/CountUpTimer.kt b/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/CountUpTimer.kt index c96b51a85d..77b94d45df 100644 --- a/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/CountUpTimer.kt +++ b/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/CountUpTimer.kt @@ -19,31 +19,29 @@ package im.vector.lib.core.utils.timer import im.vector.lib.core.utils.flow.tickerFlow import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job import kotlinx.coroutines.cancel -import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach -import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicLong @OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class) class CountUpTimer(initialTime: Long = 0L, private val intervalInMs: Long = 1_000) { private val coroutineScope = CoroutineScope(Dispatchers.Main) - private val resumed: AtomicBoolean = AtomicBoolean(false) + private var counterJob: Job? = null private val clock: Clock = DefaultClock() private val lastTime: AtomicLong = AtomicLong() private val elapsedTime: AtomicLong = AtomicLong(initialTime) - init { - startCounter() - } - private fun startCounter() { - tickerFlow(coroutineScope, intervalInMs) - .filter { resumed.get() } + counterJob = tickerFlow( + scope = coroutineScope, + delayMillis = intervalInMs, + initialDelayMillis = intervalInMs - (elapsedTime() % intervalInMs) + ) .map { elapsedTime() } .onEach { tickListener?.onTick(it) } .launchIn(coroutineScope) @@ -52,7 +50,7 @@ class CountUpTimer(initialTime: Long = 0L, private val intervalInMs: Long = 1_00 var tickListener: TickListener? = null fun elapsedTime(): Long { - return if (resumed.get()) { + return if (counterJob?.isActive == true) { val now = clock.epochMillis() elapsedTime.addAndGet(now - lastTime.getAndSet(now)) } else { @@ -62,12 +60,12 @@ class CountUpTimer(initialTime: Long = 0L, private val intervalInMs: Long = 1_00 fun pause() { tickListener?.onTick(elapsedTime()) - resumed.set(false) + coroutineScope.cancel() } fun resume() { lastTime.set(clock.epochMillis()) - resumed.set(true) + startCounter() } fun stop() { From 757bd5ff32ca509c23707e757fad0eb512ec43ff Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 31 Jan 2023 16:08:30 +0100 Subject: [PATCH 125/189] Remove clock from core-utils module --- .../im/vector/lib/core/utils/timer/Clock.kt | 34 ------------------- tools/check/forbidden_strings_in_code.txt | 2 +- 2 files changed, 1 insertion(+), 35 deletions(-) delete mode 100644 library/core-utils/src/main/java/im/vector/lib/core/utils/timer/Clock.kt diff --git a/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/Clock.kt b/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/Clock.kt deleted file mode 100644 index 47e2c6532a..0000000000 --- a/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/Clock.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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.lib.core.utils.timer - -interface Clock { - fun epochMillis(): Long -} - -class DefaultClock : Clock { - - /** - * Provides a UTC epoch in milliseconds - * - * This value is not guaranteed to be correct with reality - * as a User can override the system time and date to any values. - */ - override fun epochMillis(): Long { - return System.currentTimeMillis() - } -} diff --git a/tools/check/forbidden_strings_in_code.txt b/tools/check/forbidden_strings_in_code.txt index d12ace911b..b4d7ebae1f 100755 --- a/tools/check/forbidden_strings_in_code.txt +++ b/tools/check/forbidden_strings_in_code.txt @@ -176,7 +176,7 @@ PreferenceManager\.getDefaultSharedPreferences==2 R\.string\.template_ ### Use the Clock interface, or use `measureTimeMillis` -System\.currentTimeMillis\(\)===3 +System\.currentTimeMillis\(\)===2 ### Remove extra space between the name and the description \* @\w+ \w+ + From 9a31aa3b918e6709c22e4141d71fa5ad1b7fb207 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 31 Jan 2023 16:44:07 +0100 Subject: [PATCH 126/189] Move clock to core-utils module --- .../java/im/vector/lib/core/utils/timer}/Clock.kt | 8 +++----- .../im/vector/lib/core/utils/timer/CountUpTimer.kt | 7 +++++-- vector-app/build.gradle | 1 + .../im/vector/app/features/debug/DebugMenuActivity.kt | 2 +- .../im/vector/app/fdroid/BackgroundSyncStarter.kt | 2 +- .../app/fdroid/receiver/AlarmSyncBroadcastReceiver.kt | 2 +- .../im/vector/app/nightly/FirebaseNightlyProxy.kt | 2 +- .../java/im/vector/app/core/di/SingletonModule.kt | 11 ++++++----- .../im/vector/app/core/date/VectorDateFormatter.kt | 2 +- .../java/im/vector/app/core/di/SingletonEntryPoint.kt | 2 +- .../app/core/dialogs/GalleryOrCameraDialogHelper.kt | 2 +- .../dialogs/GalleryOrCameraDialogHelperFactory.kt | 2 +- .../app/core/services/VectorSyncAndroidService.kt | 4 ++-- .../features/analytics/DecryptionFailureTracker.kt | 2 +- .../attachments/preview/AttachmentsPreviewFragment.kt | 2 +- .../app/features/call/conference/JitsiService.kt | 2 +- .../call/webrtc/ScreenCaptureAndroidService.kt | 2 +- .../setup/KeysBackupSetupSharedViewModel.kt | 2 +- .../IncomingVerificationRequestHandler.kt | 2 +- .../home/SetUnverifiedSessionsAlertShownUseCase.kt | 2 +- .../home/ShouldShowUnverifiedSessionsAlertUseCase.kt | 2 +- .../home/UnknownDeviceDetectorSharedViewModel.kt | 2 +- .../features/home/room/detail/ChatEffectManager.kt | 2 +- .../room/detail/RoomMessageTouchHelperCallback.kt | 2 +- .../app/features/home/room/detail/TimelineFragment.kt | 2 +- .../room/detail/composer/MessageComposerViewModel.kt | 2 +- .../detail/composer/voice/VoiceMessageRecorderView.kt | 2 +- .../detail/composer/voice/VoiceRecorderFragment.kt | 2 +- .../home/room/detail/search/SearchResultController.kt | 2 +- .../room/detail/timeline/TimelineEventController.kt | 2 +- .../edithistory/ViewEditHistoryEpoxyController.kt | 2 +- .../detail/timeline/factory/MessageItemFactory.kt | 2 +- .../detail/timeline/item/VerificationRequestItem.kt | 2 +- .../live/map/LiveLocationBottomSheetController.kt | 2 +- .../location/live/map/LiveLocationUserItem.kt | 2 +- .../live/tracking/LiveLocationNotificationBuilder.kt | 2 +- .../media/domain/usecase/DownloadMediaUseCase.kt | 2 +- .../features/notifications/NotifiableEventResolver.kt | 2 +- .../notifications/NotificationBroadcastReceiver.kt | 2 +- .../app/features/notifications/NotificationUtils.kt | 2 +- .../im/vector/app/features/popup/PopupAlertManager.kt | 2 +- .../roomprofile/uploads/RoomUploadsFragment.kt | 2 +- .../vector/app/features/settings/VectorPreferences.kt | 2 +- .../v2/list/CheckIfSessionIsInactiveUseCase.kt | 2 +- .../features/settings/devtools/KeyRequestsFragment.kt | 2 +- .../test/java/im/vector/app/test/fakes/FakeClock.kt | 2 +- 46 files changed, 58 insertions(+), 55 deletions(-) rename {vector/src/main/java/im/vector/app/core/time => library/core-utils/src/main/java/im/vector/lib/core/utils/timer}/Clock.kt (85%) diff --git a/vector/src/main/java/im/vector/app/core/time/Clock.kt b/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/Clock.kt similarity index 85% rename from vector/src/main/java/im/vector/app/core/time/Clock.kt rename to library/core-utils/src/main/java/im/vector/lib/core/utils/timer/Clock.kt index b7b6e88f8d..47e2c6532a 100644 --- a/vector/src/main/java/im/vector/app/core/time/Clock.kt +++ b/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/Clock.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 New Vector Ltd + * Copyright (c) 2023 New Vector Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,15 +14,13 @@ * limitations under the License. */ -package im.vector.app.core.time - -import javax.inject.Inject +package im.vector.lib.core.utils.timer interface Clock { fun epochMillis(): Long } -class DefaultClock @Inject constructor() : Clock { +class DefaultClock : Clock { /** * Provides a UTC epoch in milliseconds diff --git a/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/CountUpTimer.kt b/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/CountUpTimer.kt index 77b94d45df..8f39fb01a6 100644 --- a/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/CountUpTimer.kt +++ b/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/CountUpTimer.kt @@ -27,12 +27,15 @@ import kotlinx.coroutines.flow.onEach import java.util.concurrent.atomic.AtomicLong @OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class) -class CountUpTimer(initialTime: Long = 0L, private val intervalInMs: Long = 1_000) { +class CountUpTimer( + private val clock: Clock = DefaultClock(), + private val intervalInMs: Long = 1_000, + initialTime: Long = 0L, +) { private val coroutineScope = CoroutineScope(Dispatchers.Main) private var counterJob: Job? = null - private val clock: Clock = DefaultClock() private val lastTime: AtomicLong = AtomicLong() private val elapsedTime: AtomicLong = AtomicLong(initialTime) diff --git a/vector-app/build.gradle b/vector-app/build.gradle index b2c2e79bcb..8df513ae58 100644 --- a/vector-app/build.gradle +++ b/vector-app/build.gradle @@ -347,6 +347,7 @@ android { dependencies { implementation project(':vector') implementation project(':vector-config') + implementation project(':library:core-utils') debugImplementation project(':library:ui-styles') implementation libs.dagger.hilt implementation 'androidx.multidex:multidex:2.0.1' diff --git a/vector-app/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt b/vector-app/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt index af63e6eae0..487283c70d 100644 --- a/vector-app/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt +++ b/vector-app/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt @@ -28,7 +28,6 @@ import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.platform.VectorBaseActivity -import im.vector.app.core.time.Clock import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO import im.vector.app.core.utils.checkPermissions import im.vector.app.core.utils.registerForPermissionsResult @@ -41,6 +40,7 @@ import im.vector.app.features.debug.sas.DebugSasEmojiActivity import im.vector.app.features.debug.settings.DebugPrivateSettingsActivity import im.vector.app.features.qrcode.QrCodeScannerActivity import im.vector.application.databinding.ActivityDebugMenuBinding +import im.vector.lib.core.utils.timer.Clock import im.vector.lib.ui.styles.debug.DebugMaterialThemeDarkDefaultActivity import im.vector.lib.ui.styles.debug.DebugMaterialThemeDarkTestActivity import im.vector.lib.ui.styles.debug.DebugMaterialThemeDarkVectorActivity diff --git a/vector-app/src/fdroid/java/im/vector/app/fdroid/BackgroundSyncStarter.kt b/vector-app/src/fdroid/java/im/vector/app/fdroid/BackgroundSyncStarter.kt index eaa3d57d42..ac43d2f8c1 100644 --- a/vector-app/src/fdroid/java/im/vector/app/fdroid/BackgroundSyncStarter.kt +++ b/vector-app/src/fdroid/java/im/vector/app/fdroid/BackgroundSyncStarter.kt @@ -18,10 +18,10 @@ package im.vector.app.fdroid import android.content.Context import im.vector.app.core.di.ActiveSessionHolder -import im.vector.app.core.time.Clock import im.vector.app.fdroid.receiver.AlarmSyncBroadcastReceiver import im.vector.app.features.settings.BackgroundSyncMode import im.vector.app.features.settings.VectorPreferences +import im.vector.lib.core.utils.timer.Clock import timber.log.Timber import javax.inject.Inject diff --git a/vector-app/src/fdroid/java/im/vector/app/fdroid/receiver/AlarmSyncBroadcastReceiver.kt b/vector-app/src/fdroid/java/im/vector/app/fdroid/receiver/AlarmSyncBroadcastReceiver.kt index bccbf42e92..a4da6990bf 100644 --- a/vector-app/src/fdroid/java/im/vector/app/fdroid/receiver/AlarmSyncBroadcastReceiver.kt +++ b/vector-app/src/fdroid/java/im/vector/app/fdroid/receiver/AlarmSyncBroadcastReceiver.kt @@ -28,7 +28,7 @@ import androidx.core.content.getSystemService import im.vector.app.core.extensions.singletonEntryPoint import im.vector.app.core.platform.PendingIntentCompat import im.vector.app.core.services.VectorSyncAndroidService -import im.vector.app.core.time.Clock +import im.vector.lib.core.utils.timer.Clock import org.matrix.android.sdk.api.session.sync.job.SyncAndroidService import timber.log.Timber diff --git a/vector-app/src/gplay/java/im/vector/app/nightly/FirebaseNightlyProxy.kt b/vector-app/src/gplay/java/im/vector/app/nightly/FirebaseNightlyProxy.kt index 71ffda7c36..05097702a8 100644 --- a/vector-app/src/gplay/java/im/vector/app/nightly/FirebaseNightlyProxy.kt +++ b/vector-app/src/gplay/java/im/vector/app/nightly/FirebaseNightlyProxy.kt @@ -22,8 +22,8 @@ import com.google.firebase.appdistribution.FirebaseAppDistribution import com.google.firebase.appdistribution.FirebaseAppDistributionException import im.vector.app.core.di.DefaultPreferences import im.vector.app.core.resources.BuildMeta -import im.vector.app.core.time.Clock import im.vector.app.features.home.NightlyProxy +import im.vector.lib.core.utils.timer.Clock import timber.log.Timber import javax.inject.Inject diff --git a/vector-app/src/main/java/im/vector/app/core/di/SingletonModule.kt b/vector-app/src/main/java/im/vector/app/core/di/SingletonModule.kt index a6d6fcd14b..68b09be8e8 100644 --- a/vector-app/src/main/java/im/vector/app/core/di/SingletonModule.kt +++ b/vector-app/src/main/java/im/vector/app/core/di/SingletonModule.kt @@ -40,8 +40,6 @@ import im.vector.app.core.dispatchers.CoroutineDispatchers import im.vector.app.core.error.DefaultErrorFormatter import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.resources.BuildMeta -import im.vector.app.core.time.Clock -import im.vector.app.core.time.DefaultClock import im.vector.app.core.utils.AndroidSystemSettingsProvider import im.vector.app.core.utils.SystemSettingsProvider import im.vector.app.features.analytics.AnalyticsTracker @@ -63,6 +61,8 @@ import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.ui.SharedPreferencesUiStateRepository import im.vector.app.features.ui.UiStateRepository import im.vector.application.BuildConfig +import im.vector.lib.core.utils.timer.Clock +import im.vector.lib.core.utils.timer.DefaultClock import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers @@ -106,9 +106,6 @@ import javax.inject.Singleton @Binds abstract fun bindAutoAcceptInvites(autoAcceptInvites: CompileTimeAutoAcceptInvites): AutoAcceptInvites - @Binds - abstract fun bindDefaultClock(clock: DefaultClock): Clock - @Binds abstract fun bindEmojiSpanify(emojiCompatWrapper: EmojiCompatWrapper): EmojiSpanify @@ -243,4 +240,8 @@ import javax.inject.Singleton fun providesDefaultSharedPreferences(context: Context): SharedPreferences { return PreferenceManager.getDefaultSharedPreferences(context.applicationContext) } + + @Singleton + @Provides + fun providesDefaultClock(): Clock = DefaultClock() } diff --git a/vector/src/main/java/im/vector/app/core/date/VectorDateFormatter.kt b/vector/src/main/java/im/vector/app/core/date/VectorDateFormatter.kt index 780a3ab705..69c99ce14f 100644 --- a/vector/src/main/java/im/vector/app/core/date/VectorDateFormatter.kt +++ b/vector/src/main/java/im/vector/app/core/date/VectorDateFormatter.kt @@ -22,7 +22,7 @@ import android.text.format.DateUtils import im.vector.app.core.resources.DateProvider import im.vector.app.core.resources.LocaleProvider import im.vector.app.core.resources.toTimestamp -import im.vector.app.core.time.Clock +import im.vector.lib.core.utils.timer.Clock import org.threeten.bp.LocalDateTime import org.threeten.bp.Period import org.threeten.bp.format.DateTimeFormatter diff --git a/vector/src/main/java/im/vector/app/core/di/SingletonEntryPoint.kt b/vector/src/main/java/im/vector/app/core/di/SingletonEntryPoint.kt index dc88229a10..acf2250449 100644 --- a/vector/src/main/java/im/vector/app/core/di/SingletonEntryPoint.kt +++ b/vector/src/main/java/im/vector/app/core/di/SingletonEntryPoint.kt @@ -21,7 +21,6 @@ import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import im.vector.app.core.dialogs.UnrecognizedCertificateDialog import im.vector.app.core.error.ErrorFormatter -import im.vector.app.core.time.Clock import im.vector.app.features.analytics.AnalyticsTracker import im.vector.app.features.call.webrtc.WebRtcCallManager import im.vector.app.features.home.AvatarRenderer @@ -31,6 +30,7 @@ import im.vector.app.features.rageshake.BugReporter import im.vector.app.features.session.SessionListener import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.ui.UiStateRepository +import im.vector.lib.core.utils.timer.Clock import kotlinx.coroutines.CoroutineScope @InstallIn(SingletonComponent::class) diff --git a/vector/src/main/java/im/vector/app/core/dialogs/GalleryOrCameraDialogHelper.kt b/vector/src/main/java/im/vector/app/core/dialogs/GalleryOrCameraDialogHelper.kt index c2af05123b..cec43dfc08 100644 --- a/vector/src/main/java/im/vector/app/core/dialogs/GalleryOrCameraDialogHelper.kt +++ b/vector/src/main/java/im/vector/app/core/dialogs/GalleryOrCameraDialogHelper.kt @@ -27,12 +27,12 @@ import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper.Listener import im.vector.app.core.extensions.insertBeforeLast import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.resources.ColorProvider -import im.vector.app.core.time.Clock import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO import im.vector.app.core.utils.checkPermissions import im.vector.app.core.utils.onPermissionDeniedDialog import im.vector.app.core.utils.registerForPermissionsResult import im.vector.app.features.media.createUCropWithDefaultSettings +import im.vector.lib.core.utils.timer.Clock import im.vector.lib.multipicker.MultiPicker import im.vector.lib.multipicker.entity.MultiPickerImageType import java.io.File diff --git a/vector/src/main/java/im/vector/app/core/dialogs/GalleryOrCameraDialogHelperFactory.kt b/vector/src/main/java/im/vector/app/core/dialogs/GalleryOrCameraDialogHelperFactory.kt index 0e8dc1d0d1..ee747629bf 100644 --- a/vector/src/main/java/im/vector/app/core/dialogs/GalleryOrCameraDialogHelperFactory.kt +++ b/vector/src/main/java/im/vector/app/core/dialogs/GalleryOrCameraDialogHelperFactory.kt @@ -18,7 +18,7 @@ package im.vector.app.core.dialogs import androidx.fragment.app.Fragment import im.vector.app.core.resources.ColorProvider -import im.vector.app.core.time.Clock +import im.vector.lib.core.utils.timer.Clock import javax.inject.Inject /** diff --git a/vector/src/main/java/im/vector/app/core/services/VectorSyncAndroidService.kt b/vector/src/main/java/im/vector/app/core/services/VectorSyncAndroidService.kt index f746c0749b..5fa263ff7b 100644 --- a/vector/src/main/java/im/vector/app/core/services/VectorSyncAndroidService.kt +++ b/vector/src/main/java/im/vector/app/core/services/VectorSyncAndroidService.kt @@ -34,10 +34,10 @@ import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.extensions.startForegroundCompat import im.vector.app.core.platform.PendingIntentCompat -import im.vector.app.core.time.Clock -import im.vector.app.core.time.DefaultClock import im.vector.app.features.notifications.NotificationUtils import im.vector.app.features.settings.BackgroundSyncMode +import im.vector.lib.core.utils.timer.Clock +import im.vector.lib.core.utils.timer.DefaultClock import org.matrix.android.sdk.api.Matrix import org.matrix.android.sdk.api.session.sync.job.SyncAndroidService import timber.log.Timber diff --git a/vector/src/main/java/im/vector/app/features/analytics/DecryptionFailureTracker.kt b/vector/src/main/java/im/vector/app/features/analytics/DecryptionFailureTracker.kt index d97c46c3cb..b38a4d934b 100644 --- a/vector/src/main/java/im/vector/app/features/analytics/DecryptionFailureTracker.kt +++ b/vector/src/main/java/im/vector/app/features/analytics/DecryptionFailureTracker.kt @@ -16,10 +16,10 @@ package im.vector.app.features.analytics -import im.vector.app.core.time.Clock import im.vector.app.features.analytics.plan.Error import im.vector.lib.core.utils.compat.removeIfCompat import im.vector.lib.core.utils.flow.tickerFlow +import im.vector.lib.core.utils.timer.Clock import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob diff --git a/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewFragment.kt b/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewFragment.kt index e7ab8c9804..5f7776bc95 100644 --- a/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewFragment.kt +++ b/vector/src/main/java/im/vector/app/features/attachments/preview/AttachmentsPreviewFragment.kt @@ -47,12 +47,12 @@ import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorMenuProvider import im.vector.app.core.resources.ColorProvider -import im.vector.app.core.time.Clock import im.vector.app.core.utils.OnSnapPositionChangeListener import im.vector.app.core.utils.SnapOnScrollListener import im.vector.app.core.utils.attachSnapHelperWithListener import im.vector.app.databinding.FragmentAttachmentsPreviewBinding import im.vector.app.features.media.createUCropWithDefaultSettings +import im.vector.lib.core.utils.timer.Clock import kotlinx.parcelize.Parcelize import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.content.ContentAttachmentData diff --git a/vector/src/main/java/im/vector/app/features/call/conference/JitsiService.kt b/vector/src/main/java/im/vector/app/features/call/conference/JitsiService.kt index d14f358801..aba2fe445f 100644 --- a/vector/src/main/java/im/vector/app/features/call/conference/JitsiService.kt +++ b/vector/src/main/java/im/vector/app/features/call/conference/JitsiService.kt @@ -19,7 +19,6 @@ package im.vector.app.features.call.conference import im.vector.app.R import im.vector.app.core.network.await import im.vector.app.core.resources.StringProvider -import im.vector.app.core.time.Clock import im.vector.app.core.utils.ensureProtocol import im.vector.app.core.utils.toBase32String import im.vector.app.features.call.conference.jwt.JitsiJWTFactory @@ -27,6 +26,7 @@ import im.vector.app.features.displayname.getBestName import im.vector.app.features.raw.wellknown.getElementWellknown import im.vector.app.features.settings.VectorLocaleProvider import im.vector.app.features.themes.ThemeProvider +import im.vector.lib.core.utils.timer.Clock import okhttp3.Request import org.jitsi.meet.sdk.JitsiMeetUserInfo import org.matrix.android.sdk.api.extensions.tryOrNull diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/ScreenCaptureAndroidService.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/ScreenCaptureAndroidService.kt index 00b6bc40d2..ab2a39ace8 100644 --- a/vector/src/main/java/im/vector/app/features/call/webrtc/ScreenCaptureAndroidService.kt +++ b/vector/src/main/java/im/vector/app/features/call/webrtc/ScreenCaptureAndroidService.kt @@ -22,8 +22,8 @@ import android.os.IBinder import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.extensions.startForegroundCompat import im.vector.app.core.services.VectorAndroidService -import im.vector.app.core.time.Clock import im.vector.app.features.notifications.NotificationUtils +import im.vector.lib.core.utils.timer.Clock import javax.inject.Inject @AndroidEntryPoint diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupSharedViewModel.kt index dfa7d1aaa3..9d75999bc9 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/keysbackup/setup/KeysBackupSetupSharedViewModel.kt @@ -22,8 +22,8 @@ import androidx.lifecycle.ViewModel import com.nulabinc.zxcvbn.Strength import im.vector.app.R import im.vector.app.core.platform.WaitingViewData -import im.vector.app.core.time.Clock import im.vector.app.core.utils.LiveEvent +import im.vector.lib.core.utils.timer.Clock import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.listeners.ProgressListener import org.matrix.android.sdk.api.session.Session diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/IncomingVerificationRequestHandler.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/IncomingVerificationRequestHandler.kt index 0f8f5c633e..b15999e7e5 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/IncomingVerificationRequestHandler.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/IncomingVerificationRequestHandler.kt @@ -18,7 +18,6 @@ package im.vector.app.features.crypto.verification import android.content.Context import im.vector.app.R import im.vector.app.core.platform.VectorBaseActivity -import im.vector.app.core.time.Clock import im.vector.app.features.analytics.plan.ViewRoom import im.vector.app.features.displayname.getBestName import im.vector.app.features.home.AvatarRenderer @@ -27,6 +26,7 @@ import im.vector.app.features.home.room.detail.arguments.TimelineArgs import im.vector.app.features.popup.PopupAlertManager import im.vector.app.features.popup.VerificationVectorAlert import im.vector.lib.core.utils.compat.getParcelableCompat +import im.vector.lib.core.utils.timer.Clock import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest import org.matrix.android.sdk.api.session.crypto.verification.VerificationService diff --git a/vector/src/main/java/im/vector/app/features/home/SetUnverifiedSessionsAlertShownUseCase.kt b/vector/src/main/java/im/vector/app/features/home/SetUnverifiedSessionsAlertShownUseCase.kt index 4580ac0f31..fef3be3c1d 100644 --- a/vector/src/main/java/im/vector/app/features/home/SetUnverifiedSessionsAlertShownUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/home/SetUnverifiedSessionsAlertShownUseCase.kt @@ -16,8 +16,8 @@ package im.vector.app.features.home -import im.vector.app.core.time.Clock import im.vector.app.features.settings.VectorPreferences +import im.vector.lib.core.utils.timer.Clock import javax.inject.Inject class SetUnverifiedSessionsAlertShownUseCase @Inject constructor( diff --git a/vector/src/main/java/im/vector/app/features/home/ShouldShowUnverifiedSessionsAlertUseCase.kt b/vector/src/main/java/im/vector/app/features/home/ShouldShowUnverifiedSessionsAlertUseCase.kt index 18c7ed9689..301a8c6ccc 100644 --- a/vector/src/main/java/im/vector/app/features/home/ShouldShowUnverifiedSessionsAlertUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/home/ShouldShowUnverifiedSessionsAlertUseCase.kt @@ -17,9 +17,9 @@ package im.vector.app.features.home import im.vector.app.config.Config -import im.vector.app.core.time.Clock import im.vector.app.features.VectorFeatures import im.vector.app.features.settings.VectorPreferences +import im.vector.lib.core.utils.timer.Clock import javax.inject.Inject class ShouldShowUnverifiedSessionsAlertUseCase @Inject constructor( diff --git a/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt index 665498153a..b8cb34ca94 100644 --- a/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/UnknownDeviceDetectorSharedViewModel.kt @@ -32,7 +32,7 @@ import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModelAction import im.vector.app.core.session.clientinfo.DeleteUnusedClientInformationUseCase -import im.vector.app.core.time.Clock +import im.vector.lib.core.utils.timer.Clock import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.launchIn diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/ChatEffectManager.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/ChatEffectManager.kt index 69ee6fe4fc..cb64da0655 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/ChatEffectManager.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/ChatEffectManager.kt @@ -16,7 +16,7 @@ package im.vector.app.features.home.room.detail -import im.vector.app.core.time.Clock +import im.vector.lib.core.utils.timer.Clock import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageType diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomMessageTouchHelperCallback.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomMessageTouchHelperCallback.kt index 5a1342b7da..9efea7c33d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomMessageTouchHelperCallback.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomMessageTouchHelperCallback.kt @@ -33,8 +33,8 @@ import com.airbnb.epoxy.EpoxyModel import com.airbnb.epoxy.EpoxyTouchHelperCallback import com.airbnb.epoxy.EpoxyViewHolder import im.vector.app.R -import im.vector.app.core.time.Clock import im.vector.app.features.themes.ThemeUtils +import im.vector.lib.core.utils.timer.Clock import timber.log.Timber import kotlin.math.abs import kotlin.math.min diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt index 7b94508b37..daf833a00d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt @@ -86,7 +86,6 @@ import im.vector.app.core.platform.VectorMenuProvider import im.vector.app.core.platform.showOptimizedSnackbar import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.UserPreferencesProvider -import im.vector.app.core.time.Clock import im.vector.app.core.ui.views.CurrentCallsView import im.vector.app.core.ui.views.CurrentCallsViewPresenter import im.vector.app.core.ui.views.FailedMessagesWarningView @@ -186,6 +185,7 @@ import im.vector.app.features.widgets.WidgetActivity import im.vector.app.features.widgets.WidgetArgs import im.vector.app.features.widgets.WidgetKind import im.vector.app.features.widgets.permissions.RoomWidgetPermissionBottomSheet +import im.vector.lib.core.utils.timer.Clock import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt index fc79c069fe..d52cf0cbd3 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt @@ -29,7 +29,6 @@ import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.extensions.getVectorLastMessageContent import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider -import im.vector.app.core.time.Clock import im.vector.app.features.analytics.AnalyticsTracker import im.vector.app.features.analytics.extensions.toAnalyticsComposer import im.vector.app.features.analytics.extensions.toAnalyticsJoinedRoom @@ -52,6 +51,7 @@ import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import im.vector.app.features.voicebroadcast.usecase.GetVoiceBroadcastStateEventLiveUseCase import im.vector.app.features.voicebroadcast.voiceBroadcastId +import im.vector.lib.core.utils.timer.Clock import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flatMapLatest diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt index b5c4b4a537..526c774b6d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt @@ -23,10 +23,10 @@ import androidx.constraintlayout.widget.ConstraintLayout import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.hardware.vibrate -import im.vector.app.core.time.Clock import im.vector.app.core.utils.DimensionConverter import im.vector.app.databinding.ViewVoiceMessageRecorderBinding import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker +import im.vector.lib.core.utils.timer.Clock import im.vector.lib.core.utils.timer.CountUpTimer import javax.inject.Inject import kotlin.math.floor diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceRecorderFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceRecorderFragment.kt index 90b813d347..e5f93368b1 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceRecorderFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceRecorderFragment.kt @@ -27,7 +27,6 @@ import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.hardware.vibrate import im.vector.app.core.platform.VectorBaseFragment -import im.vector.app.core.time.Clock import im.vector.app.core.utils.PERMISSIONS_FOR_VOICE_MESSAGE import im.vector.app.core.utils.checkPermissions import im.vector.app.core.utils.onPermissionDeniedSnackbar @@ -41,6 +40,7 @@ import im.vector.app.features.home.room.detail.composer.MessageComposerViewState import im.vector.app.features.home.room.detail.composer.SendMode import im.vector.app.features.home.room.detail.composer.boolean import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker +import im.vector.lib.core.utils.timer.Clock import javax.inject.Inject @AndroidEntryPoint diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultController.kt index 81e4d8fd5f..71c0427c97 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultController.kt @@ -30,11 +30,11 @@ import im.vector.app.core.epoxy.loadingItem import im.vector.app.core.epoxy.noResultItem import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.UserPreferencesProvider -import im.vector.app.core.time.Clock import im.vector.app.core.ui.list.GenericHeaderItem_ import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.timeline.format.DisplayableEventFormatter import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence +import im.vector.lib.core.utils.timer.Clock import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Event diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt index f845a42dcd..fcdbcd777c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt @@ -31,7 +31,6 @@ import im.vector.app.core.epoxy.LoadingItem_ import im.vector.app.core.extensions.localDateTime import im.vector.app.core.extensions.nextOrNull import im.vector.app.core.extensions.prevOrNull -import im.vector.app.core.time.Clock import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.JitsiState import im.vector.app.features.home.room.detail.RoomDetailAction @@ -64,6 +63,7 @@ import im.vector.app.features.media.AttachmentData import im.vector.app.features.media.ImageContentRenderer import im.vector.app.features.media.VideoContentRenderer import im.vector.app.features.settings.VectorPreferences +import im.vector.lib.core.utils.timer.Clock import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryEpoxyController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryEpoxyController.kt index 52ecc6d588..6c18c78615 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryEpoxyController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryEpoxyController.kt @@ -26,13 +26,13 @@ import im.vector.app.core.date.DateFormatKind import im.vector.app.core.date.VectorDateFormatter import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.StringProvider -import im.vector.app.core.time.Clock import im.vector.app.core.ui.list.genericFooterItem import im.vector.app.core.ui.list.genericHeaderItem import im.vector.app.core.ui.list.genericItem import im.vector.app.core.ui.list.genericLoaderItem import im.vector.app.features.html.EventHtmlRenderer import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence +import im.vector.lib.core.utils.timer.Clock import me.gujun.android.span.span import name.fraser.neil.plaintext.diff_match_patch import org.matrix.android.sdk.api.session.events.model.Event 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 9cb1608415..67983fc351 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 @@ -32,7 +32,6 @@ import im.vector.app.core.extensions.getVectorLastMessageContent import im.vector.app.core.files.LocalFilesHelper import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.StringProvider -import im.vector.app.core.time.Clock import im.vector.app.core.utils.DimensionConverter import im.vector.app.core.utils.containsOnlyEmojis import im.vector.app.features.home.room.detail.timeline.TimelineEventController @@ -83,6 +82,7 @@ import im.vector.app.features.voice.AudioWaveformView import im.vector.app.features.voicebroadcast.isVoiceBroadcast import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence +import im.vector.lib.core.utils.timer.Clock import me.gujun.android.span.span import org.matrix.android.sdk.api.MatrixUrls.isMxcUrl import org.matrix.android.sdk.api.session.Session diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/VerificationRequestItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/VerificationRequestItem.kt index 25656080f8..47b9a1afa9 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/VerificationRequestItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/VerificationRequestItem.kt @@ -31,11 +31,11 @@ import com.airbnb.epoxy.EpoxyModelClass import im.vector.app.R import im.vector.app.core.epoxy.ClickListener import im.vector.app.core.epoxy.onClick -import im.vector.app.core.time.Clock import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.RoomDetailAction import im.vector.app.features.home.room.detail.timeline.MessageColorProvider import im.vector.app.features.home.room.detail.timeline.TimelineEventController +import im.vector.lib.core.utils.timer.Clock import org.matrix.android.sdk.api.session.crypto.verification.VerificationService import org.matrix.android.sdk.api.session.crypto.verification.VerificationState diff --git a/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationBottomSheetController.kt b/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationBottomSheetController.kt index 0616ea84d9..80f845c3b2 100644 --- a/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationBottomSheetController.kt +++ b/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationBottomSheetController.kt @@ -23,8 +23,8 @@ import im.vector.app.core.date.VectorDateFormatter import im.vector.app.core.resources.DateProvider import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.toTimestamp -import im.vector.app.core.time.Clock import im.vector.app.features.home.AvatarRenderer +import im.vector.lib.core.utils.timer.Clock import javax.inject.Inject class LiveLocationBottomSheetController @Inject constructor( diff --git a/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationUserItem.kt b/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationUserItem.kt index 724b2c9b6f..6d87ef99f7 100644 --- a/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationUserItem.kt +++ b/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationUserItem.kt @@ -27,9 +27,9 @@ import im.vector.app.core.epoxy.VectorEpoxyHolder import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.epoxy.onClick import im.vector.app.core.resources.StringProvider -import im.vector.app.core.time.Clock import im.vector.app.core.utils.TextUtils import im.vector.app.features.home.AvatarRenderer +import im.vector.lib.core.utils.timer.Clock import im.vector.lib.core.utils.timer.CountUpTimer import org.matrix.android.sdk.api.util.MatrixItem import org.threeten.bp.Duration diff --git a/vector/src/main/java/im/vector/app/features/location/live/tracking/LiveLocationNotificationBuilder.kt b/vector/src/main/java/im/vector/app/features/location/live/tracking/LiveLocationNotificationBuilder.kt index 5bc730f3d7..34cf0b0589 100644 --- a/vector/src/main/java/im/vector/app/features/location/live/tracking/LiveLocationNotificationBuilder.kt +++ b/vector/src/main/java/im/vector/app/features/location/live/tracking/LiveLocationNotificationBuilder.kt @@ -25,7 +25,6 @@ import im.vector.app.R import im.vector.app.core.extensions.createIgnoredUri import im.vector.app.core.platform.PendingIntentCompat import im.vector.app.core.resources.StringProvider -import im.vector.app.core.time.Clock import im.vector.app.features.home.HomeActivity import im.vector.app.features.home.room.detail.RoomDetailActivity import im.vector.app.features.home.room.detail.arguments.TimelineArgs @@ -34,6 +33,7 @@ import im.vector.app.features.location.live.map.LiveLocationMapViewArgs import im.vector.app.features.notifications.NotificationActionIds import im.vector.app.features.notifications.NotificationUtils import im.vector.app.features.themes.ThemeUtils +import im.vector.lib.core.utils.timer.Clock import javax.inject.Inject import javax.inject.Singleton diff --git a/vector/src/main/java/im/vector/app/features/media/domain/usecase/DownloadMediaUseCase.kt b/vector/src/main/java/im/vector/app/features/media/domain/usecase/DownloadMediaUseCase.kt index 6a24ac4fd7..506622e71f 100644 --- a/vector/src/main/java/im/vector/app/features/media/domain/usecase/DownloadMediaUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/media/domain/usecase/DownloadMediaUseCase.kt @@ -20,9 +20,9 @@ import android.content.Context import androidx.core.net.toUri import dagger.hilt.android.qualifiers.ApplicationContext import im.vector.app.core.intent.getMimeTypeFromUri -import im.vector.app.core.time.Clock import im.vector.app.core.utils.saveMedia import im.vector.app.features.notifications.NotificationUtils +import im.vector.lib.core.utils.timer.Clock import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.session.Session import java.io.File diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt b/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt index 988ab01ef8..a69958ef25 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt @@ -21,10 +21,10 @@ import im.vector.app.core.extensions.getVectorLastMessageContent import im.vector.app.core.extensions.takeAs import im.vector.app.core.resources.BuildMeta import im.vector.app.core.resources.StringProvider -import im.vector.app.core.time.Clock import im.vector.app.features.displayname.getBestName import im.vector.app.features.home.room.detail.timeline.format.DisplayableEventFormatter import im.vector.app.features.home.room.detail.timeline.format.NoticeEventFormatter +import im.vector.lib.core.utils.timer.Clock import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.content.ContentUrlResolver diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationBroadcastReceiver.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationBroadcastReceiver.kt index e231686c27..008ec7a0c9 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationBroadcastReceiver.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationBroadcastReceiver.kt @@ -23,11 +23,11 @@ import androidx.core.app.RemoteInput import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder -import im.vector.app.core.time.Clock import im.vector.app.features.analytics.AnalyticsTracker import im.vector.app.features.analytics.extensions.toAnalyticsJoinedRoom import im.vector.app.features.analytics.plan.JoinedRoom import im.vector.app.features.session.coroutineScope +import im.vector.lib.core.utils.timer.Clock import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.Session diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt index 5b3a244137..908a1ed340 100755 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt @@ -50,7 +50,6 @@ import im.vector.app.core.extensions.createIgnoredUri import im.vector.app.core.platform.PendingIntentCompat import im.vector.app.core.resources.StringProvider import im.vector.app.core.services.CallAndroidService -import im.vector.app.core.time.Clock import im.vector.app.core.utils.startNotificationChannelSettingsIntent import im.vector.app.features.MainActivity import im.vector.app.features.call.VectorCallActivity @@ -65,6 +64,7 @@ import im.vector.app.features.home.room.threads.arguments.ThreadTimelineArgs import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.troubleshoot.TestNotificationReceiver import im.vector.app.features.themes.ThemeUtils +import im.vector.lib.core.utils.timer.Clock import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton diff --git a/vector/src/main/java/im/vector/app/features/popup/PopupAlertManager.kt b/vector/src/main/java/im/vector/app/features/popup/PopupAlertManager.kt index e0310b340e..d3c6e83589 100644 --- a/vector/src/main/java/im/vector/app/features/popup/PopupAlertManager.kt +++ b/vector/src/main/java/im/vector/app/features/popup/PopupAlertManager.kt @@ -24,7 +24,6 @@ import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS import com.tapadoo.alerter.Alerter import im.vector.app.R import im.vector.app.core.platform.VectorBaseActivity -import im.vector.app.core.time.Clock import im.vector.app.core.utils.isAnimationEnabled import im.vector.app.features.MainActivity import im.vector.app.features.analytics.ui.consent.AnalyticsOptInActivity @@ -32,6 +31,7 @@ import im.vector.app.features.home.room.list.home.release.ReleaseNotesActivity import im.vector.app.features.pin.PinActivity import im.vector.app.features.signout.hard.SignedOutActivity import im.vector.app.features.themes.ThemeUtils +import im.vector.lib.core.utils.timer.Clock import timber.log.Timber import java.lang.ref.WeakReference import javax.inject.Inject diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsFragment.kt index d982ab3e32..f318ecc638 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsFragment.kt @@ -31,7 +31,6 @@ import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.intent.getMimeTypeFromUri import im.vector.app.core.platform.VectorBaseFragment -import im.vector.app.core.time.Clock import im.vector.app.core.utils.saveMedia import im.vector.app.core.utils.shareMedia import im.vector.app.databinding.FragmentRoomUploadsBinding @@ -39,6 +38,7 @@ import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.notifications.NotificationUtils import im.vector.app.features.roomprofile.RoomProfileArgs +import im.vector.lib.core.utils.timer.Clock import kotlinx.coroutines.launch import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt index 2d5fb351f9..d405d458c0 100755 --- a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt @@ -27,12 +27,12 @@ import im.vector.app.R import im.vector.app.core.di.DefaultPreferences import im.vector.app.core.resources.BuildMeta import im.vector.app.core.resources.StringProvider -import im.vector.app.core.time.Clock import im.vector.app.features.VectorFeatures import im.vector.app.features.disclaimer.SHARED_PREF_KEY import im.vector.app.features.home.ShortcutsHandler import im.vector.app.features.homeserver.ServerUrlsRepository import im.vector.app.features.themes.ThemeUtils +import im.vector.lib.core.utils.timer.Clock import org.matrix.android.sdk.api.extensions.tryOrNull import timber.log.Timber import javax.inject.Inject diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/CheckIfSessionIsInactiveUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/CheckIfSessionIsInactiveUseCase.kt index f3670793bd..beffbf251a 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/CheckIfSessionIsInactiveUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/CheckIfSessionIsInactiveUseCase.kt @@ -16,7 +16,7 @@ package im.vector.app.features.settings.devices.v2.list -import im.vector.app.core.time.Clock +import im.vector.lib.core.utils.timer.Clock import java.util.concurrent.TimeUnit import javax.inject.Inject diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestsFragment.kt index f7e4a12793..a07013ea57 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/KeyRequestsFragment.kt @@ -36,9 +36,9 @@ import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.extensions.safeOpenOutputStream import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorMenuProvider -import im.vector.app.core.time.Clock import im.vector.app.core.utils.selectTxtFileToWrite import im.vector.app.databinding.FragmentDevtoolKeyrequestsBinding +import im.vector.lib.core.utils.timer.Clock import org.matrix.android.sdk.api.extensions.tryOrNull import javax.inject.Inject diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeClock.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeClock.kt index 1d531f147f..a292c3eb9b 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeClock.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeClock.kt @@ -16,7 +16,7 @@ package im.vector.app.test.fakes -import im.vector.app.core.time.Clock +import im.vector.lib.core.utils.timer.Clock import io.mockk.every import io.mockk.mockk From 7a95ece7381c17802e718f6473ca574fea5d621a Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Tue, 31 Jan 2023 17:13:36 +0100 Subject: [PATCH 127/189] Fix body of edited reply --- .../internal/session/room/send/LocalEchoEventFactory.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt index 25965cd291..1d6586c266 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt @@ -319,7 +319,7 @@ internal class LocalEchoEventFactory @Inject constructor( val permalink = permalinkFactory.createPermalink(roomId, originalEvent.root.eventId ?: "", false) val userLink = originalEvent.root.senderId?.let { permalinkFactory.createPermalink(it, false) } ?: "" - val body = bodyForReply(originalEvent.getLastMessageContent(), originalEvent.isReply()) + val body = bodyForReply(timelineEvent = originalEvent) // As we always supply formatted body for replies we should force the MarkdownParser to produce html. val newBodyFormatted = markdownParser.parse(newBodyText, force = true, advanced = autoMarkdown).takeFormatted() // Body of the original message may not have formatted version, so may also have to convert to html. @@ -612,7 +612,7 @@ internal class LocalEchoEventFactory @Inject constructor( val userId = eventReplied.root.senderId ?: return null val userLink = permalinkFactory.createPermalink(userId, false) ?: return null - val body = bodyForReply(eventReplied, isRedactedEvent) + val body = bodyForReply(timelineEvent = eventReplied, isRedactedEvent = isRedactedEvent) // As we always supply formatted body for replies we should force the MarkdownParser to produce html. val finalReplyTextFormatted = replyTextFormatted?.toString() ?: markdownParser.parse(replyText, force = true, advanced = autoMarkdown).takeFormatted() @@ -727,13 +727,14 @@ internal class LocalEchoEventFactory @Inject constructor( private fun bodyForReply(timelineEvent: TimelineEvent, isRedactedEvent: Boolean = false): TextContent { val content = when (timelineEvent.root.getClearType()) { in EventType.POLL_END.values -> { + // for end poll event, we use the content of the start poll event localEchoRepository .getRelatedPollEvent(timelineEvent) ?.getLastMessageContent() } else -> timelineEvent.getLastMessageContent() } - return bodyForReply(content, timelineEvent.isReply(), isRedactedEvent) + return bodyForReply(content = content, isReply = timelineEvent.isReply(), isRedactedEvent = isRedactedEvent) } /** From 8fb38f57dcab7f30f9c781bde42165bbb0dff797 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Tue, 31 Jan 2023 17:15:26 +0100 Subject: [PATCH 128/189] Fallback to message content of end message if no event found for start message --- .../sdk/internal/session/room/send/LocalEchoEventFactory.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt index 1d6586c266..642b13a6fe 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt @@ -731,6 +731,7 @@ internal class LocalEchoEventFactory @Inject constructor( localEchoRepository .getRelatedPollEvent(timelineEvent) ?.getLastMessageContent() + ?: timelineEvent.getLastMessageContent() } else -> timelineEvent.getLastMessageContent() } From 3f6b60c63db6724c804736ee497febd795f47ffc Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 31 Jan 2023 17:34:10 +0100 Subject: [PATCH 129/189] Replace ticker flow with simple coroutine --- .../lib/core/utils/timer/CountUpTimer.kt | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/CountUpTimer.kt b/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/CountUpTimer.kt index 8f39fb01a6..8485398ffa 100644 --- a/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/CountUpTimer.kt +++ b/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/CountUpTimer.kt @@ -16,14 +16,12 @@ package im.vector.lib.core.utils.timer -import im.vector.lib.core.utils.flow.tickerFlow import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.cancel -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import java.util.concurrent.atomic.AtomicLong @OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class) @@ -40,14 +38,12 @@ class CountUpTimer( private val elapsedTime: AtomicLong = AtomicLong(initialTime) private fun startCounter() { - counterJob = tickerFlow( - scope = coroutineScope, - delayMillis = intervalInMs, - initialDelayMillis = intervalInMs - (elapsedTime() % intervalInMs) - ) - .map { elapsedTime() } - .onEach { tickListener?.onTick(it) } - .launchIn(coroutineScope) + counterJob = coroutineScope.launch { + while (true) { + delay(intervalInMs - elapsedTime() % intervalInMs) + tickListener?.onTick(elapsedTime()) + } + } } var tickListener: TickListener? = null @@ -63,7 +59,8 @@ class CountUpTimer( fun pause() { tickListener?.onTick(elapsedTime()) - coroutineScope.cancel() + counterJob?.cancel() + counterJob = null } fun resume() { @@ -74,6 +71,7 @@ class CountUpTimer( fun stop() { tickListener?.onTick(elapsedTime()) coroutineScope.cancel() + counterJob = null } fun interface TickListener { From 58201851ad196978f00df96bf1b76257a242c256 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Tue, 31 Jan 2023 17:37:47 +0100 Subject: [PATCH 130/189] Avoid nested when in ProcessBodyOfReplyToEventUseCase --- .../ProcessBodyOfReplyToEventUseCase.kt | 54 +++++++++++-------- 1 file changed, 32 insertions(+), 22 deletions(-) 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 d8c69de138..d6e97e0ef9 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 @@ -19,13 +19,13 @@ package im.vector.app.features.home.room.detail.timeline.render import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.resources.StringProvider +import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.getPollQuestion import org.matrix.android.sdk.api.session.events.model.getRelationContent 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.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 @@ -97,29 +97,22 @@ class ProcessBodyOfReplyToEventUseCase @Inject constructor( stringProvider.getString(R.string.message_reply_to_sender_sent_sticker) ) } - 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 -> "" - } - val repliedText = when { - repliedToEvent.isPollEnd() -> { - val eventId = repliedToEvent.getRelationContent()?.eventId - val relatedPollContent = activeSessionHolder - .getSafeActiveSession() - ?.getRoom(repliedToEvent.roomId.orEmpty()) - ?.getTimelineEvent(eventId.orEmpty()) - ?.getLastMessageContent() as? MessagePollContent - - relatedPollContent?.getBestPollCreationInfo()?.question?.getBestQuestion() - } - else -> repliedToEvent.getPollQuestion() - } + repliedToEvent.isPollEnd() -> { + val fallbackText = stringProvider.getString(R.string.message_reply_to_sender_ended_poll) + val repliedText = getPollQuestionFromPollEnd(repliedToEvent) matrixFormattedBody.replaceRange( afterBreakingLineIndex, endOfBlockQuoteIndex, - repliedText ?: fallbackText + repliedText ?: fallbackText, + ) + } + repliedToEvent.isPollStart() -> { + val fallbackText = stringProvider.getString(R.string.message_reply_to_sender_created_poll) + val repliedText = repliedToEvent.getPollQuestion() + matrixFormattedBody.replaceRange( + afterBreakingLineIndex, + endOfBlockQuoteIndex, + repliedText ?: fallbackText, ) } repliedToEvent.isLiveLocation() -> { @@ -142,8 +135,25 @@ class ProcessBodyOfReplyToEventUseCase @Inject constructor( } private fun getEvent(eventId: String, roomId: String) = + getTimelineEvent(eventId, roomId) + ?.root + + private fun getTimelineEvent(eventId: String, roomId: String) = activeSessionHolder.getSafeActiveSession() ?.getRoom(roomId) ?.getTimelineEvent(eventId) - ?.root + + private fun getPollQuestionFromPollEnd(event: Event): String? { + val eventId = event.getRelationContent()?.eventId.orEmpty() + val roomId = event.roomId.orEmpty() + return if (eventId.isEmpty() || roomId.isEmpty()) { + null + } else { + (getTimelineEvent(eventId, roomId) + ?.getLastMessageContent() as? MessagePollContent) + ?.getBestPollCreationInfo() + ?.question + ?.getBestQuestion() + } + } } From a0bb3af871160ae07e96f2a8cd7c2d85271a6580 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 31 Jan 2023 23:05:14 +0000 Subject: [PATCH 131/189] Bump io.element.android:wysiwyg from 0.18.0 to 0.23.0 Bumps [io.element.android:wysiwyg](https://github.com/matrix-org/matrix-wysiwyg) from 0.18.0 to 0.23.0. - [Release notes](https://github.com/matrix-org/matrix-wysiwyg/releases) - [Changelog](https://github.com/matrix-org/matrix-rich-text-editor/blob/main/CHANGELOG.md) - [Commits](https://github.com/matrix-org/matrix-wysiwyg/compare/0.18.0...0.23.0) --- updated-dependencies: - dependency-name: io.element.android:wysiwyg dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index bab9229b3b..2e0a7011be 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -103,7 +103,7 @@ ext.libs = [ ], element : [ 'opusencoder' : "io.element.android:opusencoder:1.1.0", - 'wysiwyg' : "io.element.android:wysiwyg:0.18.0" + 'wysiwyg' : "io.element.android:wysiwyg:0.23.0" ], squareup : [ 'moshi' : "com.squareup.moshi:moshi:$moshi", From 7683b8325c3535af3b478d3759ac7e00431c05fd Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Wed, 1 Feb 2023 09:40:56 +0100 Subject: [PATCH 132/189] Updating unit tests --- .../ProcessBodyOfReplyToEventUseCaseTest.kt | 92 ++++++++++++++----- .../poll/create/CreatePollViewModelTest.kt | 2 +- .../app/test/fakes/FakeTimelineService.kt | 4 +- 3 files changed, 70 insertions(+), 28 deletions(-) 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 c38afe20ec..63a86d2a20 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 @@ -31,16 +31,23 @@ 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.getRelationContent 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.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 +import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent +import org.matrix.android.sdk.api.session.room.model.message.PollCreationInfo +import org.matrix.android.sdk.api.session.room.model.message.PollQuestion +import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent import org.matrix.android.sdk.api.session.room.model.relation.ReplyToContent import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent private const val A_ROOM_ID = "room-id" private const val AN_EVENT_ID = "event-id" @@ -93,6 +100,7 @@ class ProcessBodyOfReplyToEventUseCaseTest { fun setup() { givenNewPrefix() mockkStatic("org.matrix.android.sdk.api.session.events.model.EventKt") + mockkStatic("org.matrix.android.sdk.api.session.room.timeline.TimelineEventKt") } @After @@ -104,7 +112,7 @@ class ProcessBodyOfReplyToEventUseCaseTest { fun `given a replied event of type file message when process the formatted body then content is replaced by correct string`() { // Given givenTypeOfRepliedEvent(isFileMessage = true) - givenNewContentForId(R.string.message_reply_to_sender_sent_file) + givenContentForId(R.string.message_reply_to_sender_sent_file, content = A_NEW_CONTENT) executeAndAssertResult() } @@ -113,7 +121,7 @@ class ProcessBodyOfReplyToEventUseCaseTest { fun `given a replied event of type voice message when process the formatted body then content is replaced by correct string`() { // Given givenTypeOfRepliedEvent(isVoiceMessage = true) - givenNewContentForId(R.string.message_reply_to_sender_sent_voice_message) + givenContentForId(R.string.message_reply_to_sender_sent_voice_message, content = A_NEW_CONTENT) executeAndAssertResult() } @@ -122,7 +130,7 @@ class ProcessBodyOfReplyToEventUseCaseTest { fun `given a replied event of type audio message when process the formatted body then content is replaced by correct string`() { // Given givenTypeOfRepliedEvent(isAudioMessage = true) - givenNewContentForId(R.string.message_reply_to_sender_sent_audio_file) + givenContentForId(R.string.message_reply_to_sender_sent_audio_file, content = A_NEW_CONTENT) executeAndAssertResult() } @@ -131,7 +139,7 @@ class ProcessBodyOfReplyToEventUseCaseTest { fun `given a replied event of type image message when process the formatted body then content is replaced by correct string`() { // Given givenTypeOfRepliedEvent(isImageMessage = true) - givenNewContentForId(R.string.message_reply_to_sender_sent_image) + givenContentForId(R.string.message_reply_to_sender_sent_image, content = A_NEW_CONTENT) executeAndAssertResult() } @@ -140,7 +148,7 @@ class ProcessBodyOfReplyToEventUseCaseTest { fun `given a replied event of type video message when process the formatted body then content is replaced by correct string`() { // Given givenTypeOfRepliedEvent(isVideoMessage = true) - givenNewContentForId(R.string.message_reply_to_sender_sent_video) + givenContentForId(R.string.message_reply_to_sender_sent_video, content = A_NEW_CONTENT) executeAndAssertResult() } @@ -149,49 +157,58 @@ class ProcessBodyOfReplyToEventUseCaseTest { fun `given a replied event of type sticker message when process the formatted body then content is replaced by correct string`() { // Given givenTypeOfRepliedEvent(isStickerMessage = true) - givenNewContentForId(R.string.message_reply_to_sender_sent_sticker) + givenContentForId(R.string.message_reply_to_sender_sent_sticker, content = A_NEW_CONTENT) executeAndAssertResult() } @Test - fun `given a replied event of type poll message with null question when process the formatted body then content is replaced by correct string`() { + fun `given a replied event of type poll start message with null question when process the formatted body then content is replaced by correct string`() { // Given - givenTypeOfRepliedEvent(isPollMessage = true) - givenNewContentForId(R.string.message_reply_to_sender_created_poll) - every { fakeRepliedEvent.getClearType() } returns EventType.POLL_START.unstable + givenTypeOfRepliedEvent(isPollStartMessage = true) + givenContentForId(R.string.message_reply_to_sender_created_poll, content = A_NEW_CONTENT) every { fakeRepliedEvent.getPollQuestion() } returns null executeAndAssertResult() } @Test - fun `given a replied event of type poll message with existing question when process the formatted body then content is replaced by correct string`() { + fun `given a replied event of type poll start message with existing question when process the formatted body then content is replaced by correct string`() { // Given - givenTypeOfRepliedEvent(isPollMessage = true) - givenNewContentForId(R.string.message_reply_to_sender_created_poll) - every { fakeRepliedEvent.getClearType() } returns EventType.POLL_START.unstable + givenTypeOfRepliedEvent(isPollStartMessage = true) + givenContentForId(R.string.message_reply_to_sender_created_poll, content = "") 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`() { + fun `given a replied event of type poll end message with null question 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 + givenTypeOfRepliedEvent(isPollEndMessage = true) + givenContentForId(R.string.message_reply_to_sender_ended_poll, content = A_NEW_CONTENT) + givenPollQuestionReturns(fakeRepliedEvent, null) every { fakeRepliedEvent.getPollQuestion() } returns null executeAndAssertResult() } + @Test + fun `given a replied event of type poll end message with existing question when process the formatted body then content is replaced by correct string`() { + // Given + givenTypeOfRepliedEvent(isPollEndMessage = true) + givenContentForId(R.string.message_reply_to_sender_ended_poll, content = "") + every { fakeRepliedEvent.getClearType() } returns EventType.POLL_END.unstable + givenPollQuestionReturns(fakeRepliedEvent, A_NEW_CONTENT) + + 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 givenTypeOfRepliedEvent(isLiveLocationMessage = true) - givenNewContentForId(R.string.live_location_description) + givenContentForId(R.string.live_location_description, content = A_NEW_CONTENT) executeAndAssertResult() } @@ -242,13 +259,16 @@ class ProcessBodyOfReplyToEventUseCaseTest { private fun givenARepliedEvent(timelineEvent: TimelineEvent? = mockk()): Event { val event = mockk() + val eventId = "event-id" + every { event.eventId } returns eventId + every { event.roomId } returns A_ROOM_ID timelineEvent?.let { every { it.root } returns event } fakeActiveSessionHolder .fakeSession .roomService() .getRoom(A_ROOM_ID) .timelineService() - .givenTimelineEvent(timelineEvent) + .givenTimelineEventReturns(eventId, timelineEvent) return event } @@ -259,7 +279,8 @@ class ProcessBodyOfReplyToEventUseCaseTest { isImageMessage: Boolean = false, isVideoMessage: Boolean = false, isStickerMessage: Boolean = false, - isPollMessage: Boolean = false, + isPollEndMessage: Boolean = false, + isPollStartMessage: Boolean = false, isLiveLocationMessage: Boolean = false, ) { every { fakeRepliedEvent.isFileMessage() } returns isFileMessage @@ -268,7 +289,8 @@ class ProcessBodyOfReplyToEventUseCaseTest { every { fakeRepliedEvent.isImageMessage() } returns isImageMessage every { fakeRepliedEvent.isVideoMessage() } returns isVideoMessage every { fakeRepliedEvent.isSticker() } returns isStickerMessage - every { fakeRepliedEvent.isPoll() } returns isPollMessage + every { fakeRepliedEvent.isPollEnd() } returns isPollEndMessage + every { fakeRepliedEvent.isPollStart() } returns isPollStartMessage every { fakeRepliedEvent.isLiveLocation() } returns isLiveLocationMessage } @@ -276,7 +298,27 @@ class ProcessBodyOfReplyToEventUseCaseTest { fakeStringProvider.given(R.string.message_reply_to_prefix, A_NEW_PREFIX) } - private fun givenNewContentForId(@StringRes resId: Int) { - fakeStringProvider.given(resId, A_NEW_CONTENT) + private fun givenContentForId(@StringRes resId: Int, content: String) { + fakeStringProvider.given(resId, content) + } + + private fun givenPollQuestionReturns(pollEndEvent: Event, question: String?) { + val eventId = "start-event-id" + val relationContent = mockk() + every { relationContent.eventId } returns eventId + every { pollEndEvent.getRelationContent() } returns relationContent + val timelineEvent = mockk() + val messagePollContent = MessagePollContent( + pollCreationInfo = PollCreationInfo( + question = PollQuestion(unstableQuestion = question) + ) + ) + every { timelineEvent.getLastMessageContent() } returns messagePollContent + fakeActiveSessionHolder + .fakeSession + .roomService() + .getRoom(A_ROOM_ID) + .timelineService() + .givenTimelineEventReturns(eventId, timelineEvent) } } diff --git a/vector/src/test/java/im/vector/app/features/poll/create/CreatePollViewModelTest.kt b/vector/src/test/java/im/vector/app/features/poll/create/CreatePollViewModelTest.kt index 491834db5b..1979f7872c 100644 --- a/vector/src/test/java/im/vector/app/features/poll/create/CreatePollViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/poll/create/CreatePollViewModelTest.kt @@ -68,7 +68,7 @@ class CreatePollViewModelTest { .roomService() .getRoom(A_FAKE_ROOM_ID) .timelineService() - .givenTimelineEvent(A_POLL_START_TIMELINE_EVENT) + .givenTimelineEventReturns(A_POLL_START_TIMELINE_EVENT.eventId, A_POLL_START_TIMELINE_EVENT) } @After diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeTimelineService.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeTimelineService.kt index a5fac5f1a1..e6a6214c90 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeTimelineService.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeTimelineService.kt @@ -23,7 +23,7 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineService class FakeTimelineService : TimelineService by mockk() { - fun givenTimelineEvent(event: TimelineEvent?) { - every { getTimelineEvent(any()) } returns event + fun givenTimelineEventReturns(eventId: String, event: TimelineEvent?) { + every { getTimelineEvent(eventId) } returns event } } From 48393ee5d1f475895db4596cb62b0a789139bf49 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Wed, 1 Feb 2023 10:17:38 +0100 Subject: [PATCH 133/189] Fixing some previews for end poll events --- .../sdk/api/session/room/summary/RoomSummaryConstants.kt | 1 + .../room/detail/timeline/action/MessageActionsViewModel.kt | 3 +++ 2 files changed, 4 insertions(+) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomSummaryConstants.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomSummaryConstants.kt index 634e71c43b..127b14e5d5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomSummaryConstants.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomSummaryConstants.kt @@ -35,5 +35,6 @@ object RoomSummaryConstants { EventType.REACTION ) + EventType.POLL_START.values + + EventType.POLL_END.values + EventType.STATE_ROOM_BEACON_INFO.values } 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 d442c1f1ba..78178731fa 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 @@ -219,6 +219,9 @@ class MessageActionsViewModel @AssistedInject constructor( (timelineEvent.getVectorLastMessageContent() as? MessagePollContent)?.getBestPollCreationInfo()?.question?.getBestQuestion() ?: stringProvider.getString(R.string.message_reply_to_poll_preview) } + in EventType.POLL_END.values -> { + stringProvider.getString(R.string.message_reply_to_ended_poll_preview) + } else -> null } } From 369c0f166d6113e011172adcd685c958206efdd9 Mon Sep 17 00:00:00 2001 From: yostyle Date: Wed, 1 Feb 2023 17:25:43 +0100 Subject: [PATCH 134/189] Let the user know when we are not able to decrypt the voice broadcast chunks --- changelog.d/7820.misc | 1 + .../src/main/res/values/strings.xml | 1 + .../vector/app/core/error/ErrorFormatter.kt | 4 +- .../timeline/factory/TimelineItemFactory.kt | 16 ++++++++ .../factory/VoiceBroadcastItemFactory.kt | 1 + .../timeline/helper/TimelineEventsGroups.kt | 10 +++++ .../item/AbsMessageVoiceBroadcastItem.kt | 2 + .../MessageVoiceBroadcastListeningItem.kt | 4 ++ .../voicebroadcast/VoiceBroadcastFailure.kt | 1 + .../listening/VoiceBroadcastPlayerImpl.kt | 12 ++++-- .../GetLiveVoiceBroadcastChunksUseCase.kt | 40 ++++++++++++++----- 11 files changed, 77 insertions(+), 15 deletions(-) create mode 100644 changelog.d/7820.misc diff --git a/changelog.d/7820.misc b/changelog.d/7820.misc new file mode 100644 index 0000000000..1f59cb9afe --- /dev/null +++ b/changelog.d/7820.misc @@ -0,0 +1 @@ +Let the user know when we are not able to decrypt the voice broadcast chunks diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index e690f06bbb..de3fa20916 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3120,6 +3120,7 @@ You are already recording a voice broadcast. Please end your current voice broadcast to start a new one. Unable to play this voice broadcast. Connection error - Recording paused + Unable to decrypt this voice broadcast. %1$s left Stop live broadcasting? diff --git a/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt b/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt index 0966227917..84f866d1f3 100644 --- a/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt +++ b/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt @@ -160,7 +160,9 @@ class DefaultErrorFormatter @Inject constructor( RecordingError.BlockedBySomeoneElse -> stringProvider.getString(R.string.error_voice_broadcast_blocked_by_someone_else_message) RecordingError.NoPermission -> stringProvider.getString(R.string.error_voice_broadcast_permission_denied_message) RecordingError.UserAlreadyBroadcasting -> stringProvider.getString(R.string.error_voice_broadcast_already_in_progress_message) - is VoiceBroadcastFailure.ListeningError -> stringProvider.getString(R.string.error_voice_broadcast_unable_to_play) + is VoiceBroadcastFailure.ListeningError.UnableToPlay, + is VoiceBroadcastFailure.ListeningError.PrepareMediaPlayerError -> stringProvider.getString(R.string.error_voice_broadcast_unable_to_play) + is VoiceBroadcastFailure.ListeningError.UnableToDecrypt -> stringProvider.getString(R.string.error_voice_broadcast_unable_to_decrypt) } } 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 61b2385d1d..9bcf3e1b6b 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 @@ -19,11 +19,17 @@ package im.vector.app.features.home.room.detail.timeline.factory import im.vector.app.core.epoxy.TimelineEmptyItem import im.vector.app.core.epoxy.TimelineEmptyItem_ import im.vector.app.core.epoxy.VectorEpoxyModel +import im.vector.app.core.extensions.isVoiceBroadcast import im.vector.app.features.analytics.DecryptionFailureTracker import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventVisibilityHelper import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants +import org.matrix.android.sdk.api.session.Session 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.getTimelineEvent import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import org.matrix.android.sdk.api.session.room.timeline.getRelationContent import timber.log.Timber import javax.inject.Inject @@ -39,6 +45,7 @@ class TimelineItemFactory @Inject constructor( private val callItemFactory: CallItemFactory, private val decryptionFailureTracker: DecryptionFailureTracker, private val timelineEventVisibilityHelper: TimelineEventVisibilityHelper, + private val session: Session, ) { /** @@ -130,9 +137,18 @@ class TimelineItemFactory @Inject constructor( EventType.CALL_ANSWER -> callItemFactory.create(params) // Crypto EventType.ENCRYPTED -> { + val relationContent = event.getRelationContent() if (event.root.isRedacted()) { // Redacted event, let the MessageItemFactory handle it messageItemFactory.create(params) + } else if (relationContent?.type == RelationType.REFERENCE) { + // Hide the decryption error for VoiceBroadcast chunks + val startEvent = relationContent.eventId?.let { session.getRoom(event.roomId)?.getTimelineEvent(it) } + if (startEvent?.isVoiceBroadcast() == false) { + encryptedItemFactory.create(params) + } else { + null + } } else { encryptedItemFactory.create(params) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt index 3439fb1f57..7d05463b28 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VoiceBroadcastItemFactory.kt @@ -75,6 +75,7 @@ class VoiceBroadcastItemFactory @Inject constructor( voiceBroadcast = voiceBroadcast, voiceBroadcastState = voiceBroadcastContent.voiceBroadcastState, duration = voiceBroadcastEventsGroup.getDuration(), + hasUnableToDecryptEvent = voiceBroadcastEventsGroup.hasUnableToDecryptEvent(), recorderName = params.event.senderInfo.disambiguatedDisplayName, recorder = voiceBroadcastRecorder, player = voiceBroadcastPlayer, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventsGroups.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventsGroups.kt index a4bfa9e155..a3e3f502b6 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventsGroups.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventsGroups.kt @@ -25,6 +25,8 @@ import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent 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.events.model.RelationType +import org.matrix.android.sdk.api.session.events.model.getRelationContent import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent import org.matrix.android.sdk.api.session.room.model.message.asMessageAudioEvent @@ -61,6 +63,7 @@ class TimelineEventsGroups { private fun TimelineEvent.getGroupIdOrNull(): String? { val type = root.getClearType() val content = root.getClearContent() + val relationContent = root.getRelationContent() return when { EventType.isCallEvent(type) -> (content?.get("call_id") as? String) type == VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO -> root.asVoiceBroadcastEvent()?.reference?.eventId @@ -69,6 +72,9 @@ class TimelineEventsGroups { // Group voice messages with a reference to an eventId root.asMessageAudioEvent()?.getVoiceBroadcastEventId() } + type == EventType.ENCRYPTED && relationContent?.type == RelationType.REFERENCE -> { + relationContent.eventId + } else -> { null } @@ -153,4 +159,8 @@ class VoiceBroadcastEventsGroup(private val group: TimelineEventsGroup) { fun getDuration(): Int { return group.events.mapNotNull { it.root.asMessageAudioEvent()?.duration }.sum() } + + fun hasUnableToDecryptEvent(): Boolean { + return group.events.any { it.root.getClearType() == EventType.ENCRYPTED } + } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageVoiceBroadcastItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageVoiceBroadcastItem.kt index 7cde978e42..21d1abbdf2 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageVoiceBroadcastItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageVoiceBroadcastItem.kt @@ -45,6 +45,7 @@ abstract class AbsMessageVoiceBroadcastItem + if (events.any { it.getClearType() == EventType.ENCRYPTED }) { + playingState = State.Error(VoiceBroadcastFailure.ListeningError.UnableToDecrypt) + } else { + playlist.setItems(events.mapNotNull { it.asMessageAudioEvent() }) + onPlaylistUpdated() + } } .launchIn(sessionScope) } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/usecase/GetLiveVoiceBroadcastChunksUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/usecase/GetLiveVoiceBroadcastChunksUseCase.kt index 6f7444849a..05a465fb13 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/usecase/GetLiveVoiceBroadcastChunksUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/usecase/GetLiveVoiceBroadcastChunksUseCase.kt @@ -33,7 +33,10 @@ import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.runningReduce +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.events.model.getRelationContent import org.matrix.android.sdk.api.session.room.model.message.MessageAudioEvent import org.matrix.android.sdk.api.session.room.model.message.asMessageAudioEvent import org.matrix.android.sdk.api.session.room.timeline.Timeline @@ -49,14 +52,22 @@ class GetLiveVoiceBroadcastChunksUseCase @Inject constructor( private val getVoiceBroadcastEventUseCase: GetVoiceBroadcastStateEventUseCase, ) { - fun execute(voiceBroadcast: VoiceBroadcast): Flow> { + fun execute(voiceBroadcast: VoiceBroadcast): Flow> { val session = activeSessionHolder.getSafeActiveSession() ?: return emptyFlow() val room = session.roomService().getRoom(voiceBroadcast.roomId) ?: return emptyFlow() val timeline = room.timelineService().createTimeline(null, TimelineSettings(5)) // Get initial chunks val existingChunks = room.timelineService().getTimelineEventsRelatedTo(RelationType.REFERENCE, voiceBroadcast.voiceBroadcastId) - .mapNotNull { timelineEvent -> timelineEvent.root.asMessageAudioEvent().takeIf { it.isVoiceBroadcast() } } + .mapNotNull { timelineEvent -> + val event = timelineEvent.root + val relationContent = event.getRelationContent() + when { + event.getClearType() == EventType.MESSAGE -> event.takeIf { it.asMessageAudioEvent().isVoiceBroadcast() } + event.getClearType() == EventType.ENCRYPTED && relationContent?.type == RelationType.REFERENCE -> event + else -> null + } + } val voiceBroadcastEvent = getVoiceBroadcastEventUseCase.execute(voiceBroadcast) val voiceBroadcastState = voiceBroadcastEvent?.content?.voiceBroadcastState @@ -93,7 +104,7 @@ class GetLiveVoiceBroadcastChunksUseCase @Inject constructor( } // Automatically stop observing the timeline if the last chunk has been received - if (lastSequence != null && newChunks.any { it.sequence == lastSequence }) { + if (lastSequence != null && newChunks.any { it.asMessageAudioEvent()?.sequence == lastSequence }) { timeline.removeListener(this) timeline.dispose() } @@ -109,8 +120,8 @@ class GetLiveVoiceBroadcastChunksUseCase @Inject constructor( timeline.dispose() } } - .runningReduce { accumulator: List, value: List -> accumulator.plus(value) } - .map { events -> events.distinctBy { it.sequence } } + .runningReduce { accumulator: List, value: List -> accumulator.plus(value) } + .map { events -> events.distinctBy { it.eventId } } } } @@ -124,12 +135,19 @@ class GetLiveVoiceBroadcastChunksUseCase @Inject constructor( /** * Transform the list of [TimelineEvent] to a mapped list of [MessageAudioEvent] related to a given voice broadcast. */ - private fun List.mapToChunkEvents(voiceBroadcastId: String, senderId: String?): List = + private fun List.mapToChunkEvents(voiceBroadcastId: String, senderId: String?): List = this.mapNotNull { timelineEvent -> - timelineEvent.root.asMessageAudioEvent() - ?.takeIf { - it.isVoiceBroadcast() && it.getVoiceBroadcastEventId() == voiceBroadcastId && - it.root.senderId == senderId - } + val event = timelineEvent.root + val relationContent = event.getRelationContent() + when { + event.getClearType() == EventType.MESSAGE -> { + event.asMessageAudioEvent() + ?.takeIf { + it.isVoiceBroadcast() && it.getVoiceBroadcastEventId() == voiceBroadcastId && it.root.senderId == senderId + }?.root + } + event.getClearType() == EventType.ENCRYPTED && relationContent?.type == RelationType.REFERENCE -> event + else -> null + } } } From 6f18c020ea77d39d78d8b83d87ae0b84c823cf3f Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 1 Feb 2023 17:35:38 +0100 Subject: [PATCH 135/189] Add unit test on count up timer --- library/core-utils/build.gradle | 8 ++ .../lib/core/utils/timer/CountUpTimer.kt | 7 +- .../lib/core/utils/test/fakes/FakeClock.kt | 27 ++++++ .../lib/core/utils/timer/CountUpTimerTest.kt | 96 +++++++++++++++++++ 4 files changed, 134 insertions(+), 4 deletions(-) create mode 100644 library/core-utils/src/test/java/im/vector/lib/core/utils/test/fakes/FakeClock.kt create mode 100644 library/core-utils/src/test/java/im/vector/lib/core/utils/timer/CountUpTimerTest.kt diff --git a/library/core-utils/build.gradle b/library/core-utils/build.gradle index b985127ec6..d1e82fcb9a 100644 --- a/library/core-utils/build.gradle +++ b/library/core-utils/build.gradle @@ -53,4 +53,12 @@ android { dependencies { implementation libs.jetbrains.coroutinesAndroid + + // TESTS + testImplementation libs.tests.junit + testImplementation libs.tests.kluent + testImplementation libs.mockk.mockk + testImplementation(libs.jetbrains.coroutinesTest) { + exclude group: "org.jetbrains.kotlinx", module: "kotlinx-coroutines-debug" + } } diff --git a/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/CountUpTimer.kt b/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/CountUpTimer.kt index 8485398ffa..435a8603e8 100644 --- a/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/CountUpTimer.kt +++ b/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/CountUpTimer.kt @@ -19,22 +19,21 @@ package im.vector.lib.core.utils.timer import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job -import kotlinx.coroutines.cancel import kotlinx.coroutines.delay import kotlinx.coroutines.launch import java.util.concurrent.atomic.AtomicLong @OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class) class CountUpTimer( + private val coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.Main), private val clock: Clock = DefaultClock(), private val intervalInMs: Long = 1_000, initialTime: Long = 0L, ) { - private val coroutineScope = CoroutineScope(Dispatchers.Main) private var counterJob: Job? = null - private val lastTime: AtomicLong = AtomicLong() + private val lastTime: AtomicLong = AtomicLong(clock.epochMillis()) private val elapsedTime: AtomicLong = AtomicLong(initialTime) private fun startCounter() { @@ -70,7 +69,7 @@ class CountUpTimer( fun stop() { tickListener?.onTick(elapsedTime()) - coroutineScope.cancel() + counterJob?.cancel() counterJob = null } diff --git a/library/core-utils/src/test/java/im/vector/lib/core/utils/test/fakes/FakeClock.kt b/library/core-utils/src/test/java/im/vector/lib/core/utils/test/fakes/FakeClock.kt new file mode 100644 index 0000000000..4bad4471f1 --- /dev/null +++ b/library/core-utils/src/test/java/im/vector/lib/core/utils/test/fakes/FakeClock.kt @@ -0,0 +1,27 @@ +/* + * 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.lib.core.utils.test.fakes + +import im.vector.lib.core.utils.timer.Clock +import io.mockk.every +import io.mockk.mockk + +class FakeClock : Clock by mockk() { + fun givenEpoch(epoch: Long) { + every { epochMillis() } returns epoch + } +} diff --git a/library/core-utils/src/test/java/im/vector/lib/core/utils/timer/CountUpTimerTest.kt b/library/core-utils/src/test/java/im/vector/lib/core/utils/timer/CountUpTimerTest.kt new file mode 100644 index 0000000000..df517751ed --- /dev/null +++ b/library/core-utils/src/test/java/im/vector/lib/core/utils/timer/CountUpTimerTest.kt @@ -0,0 +1,96 @@ +/* + * 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.lib.core.utils.timer + +import im.vector.lib.core.utils.test.fakes.FakeClock +import io.mockk.every +import io.mockk.mockk +import io.mockk.verifySequence +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.advanceTimeBy +import kotlinx.coroutines.test.currentTime +import kotlinx.coroutines.test.runTest +import org.junit.Test + +private const val AN_INTERVAL = 500L +private const val AN_INITIAL_TIME = 2_333L + +@OptIn(ExperimentalCoroutinesApi::class) +internal class CountUpTimerTest { + + private val fakeClock = FakeClock() + + @Test + fun `when pausing and resuming the timer, the timer ticks the right values at the right moments`() = runTest { + every { fakeClock.epochMillis() } answers { currentTime } + val tickListener = mockk(relaxed = true) + val timer = CountUpTimer( + coroutineScope = this, + clock = fakeClock, + intervalInMs = AN_INTERVAL, + initialTime = 0, + ).also { it.tickListener = tickListener } + + timer.resume() + advanceTimeBy(AN_INTERVAL / 2) // no tick + timer.pause() // tick + advanceTimeBy(AN_INTERVAL * 10) // no tick + timer.resume() // no tick + advanceTimeBy(AN_INTERVAL * 4) // tick * 4 + timer.stop() // tick + + verifySequence { + tickListener.onTick(AN_INTERVAL / 2) + tickListener.onTick(AN_INTERVAL) + tickListener.onTick(AN_INTERVAL * 2) + tickListener.onTick(AN_INTERVAL * 3) + tickListener.onTick(AN_INTERVAL * 4) + tickListener.onTick(AN_INTERVAL * 4 + AN_INTERVAL / 2) + } + } + + @Test + fun `given an initial time, the timer ticks the right values at the right moments`() = runTest { + every { fakeClock.epochMillis() } answers { currentTime } + val tickListener = mockk(relaxed = true) + val timer = CountUpTimer( + coroutineScope = this, + clock = fakeClock, + intervalInMs = AN_INTERVAL, + initialTime = AN_INITIAL_TIME, + ).also { it.tickListener = tickListener } + + timer.resume() + advanceTimeBy(AN_INTERVAL) // tick + timer.pause() // tick + advanceTimeBy(AN_INTERVAL * 10) // no tick + timer.resume() // no tick + advanceTimeBy(AN_INTERVAL * 4) // tick * 4 + timer.stop() // tick + + val offset = AN_INITIAL_TIME % AN_INTERVAL + verifySequence { + tickListener.onTick(AN_INITIAL_TIME + AN_INTERVAL - offset) + tickListener.onTick(AN_INITIAL_TIME + AN_INTERVAL) + tickListener.onTick(AN_INITIAL_TIME + AN_INTERVAL * 2 - offset) + tickListener.onTick(AN_INITIAL_TIME + AN_INTERVAL * 3 - offset) + tickListener.onTick(AN_INITIAL_TIME + AN_INTERVAL * 4 - offset) + tickListener.onTick(AN_INITIAL_TIME + AN_INTERVAL * 5 - offset) + tickListener.onTick(AN_INITIAL_TIME + AN_INTERVAL * 5) + } + } +} From 524680fced7f43bb0a2a17069477b2d6b7d97571 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 1 Feb 2023 18:12:18 +0100 Subject: [PATCH 136/189] changelog --- changelog.d/8058.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/8058.misc diff --git a/changelog.d/8058.misc b/changelog.d/8058.misc new file mode 100644 index 0000000000..d864b3c7a8 --- /dev/null +++ b/changelog.d/8058.misc @@ -0,0 +1 @@ +Improve the `CountUpTimer` implementation From 9383319b37cc0095e689f2a7e96b0d653928c797 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 1 Feb 2023 23:26:49 +0100 Subject: [PATCH 137/189] Fix import --- vector-app/src/androidTest/java/im/vector/app/EspressoExt.kt | 2 +- .../java/im/vector/app/espresso/tools/ScreenshotFailureRule.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/vector-app/src/androidTest/java/im/vector/app/EspressoExt.kt b/vector-app/src/androidTest/java/im/vector/app/EspressoExt.kt index 68a54e9901..7139af49a2 100644 --- a/vector-app/src/androidTest/java/im/vector/app/EspressoExt.kt +++ b/vector-app/src/androidTest/java/im/vector/app/EspressoExt.kt @@ -43,8 +43,8 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialogFragment import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment -import im.vector.app.core.time.DefaultClock import im.vector.app.espresso.tools.waitUntilViewVisible +import im.vector.lib.core.utils.timer.DefaultClock import org.hamcrest.Matcher import org.hamcrest.Matchers import org.hamcrest.StringDescription diff --git a/vector-app/src/androidTest/java/im/vector/app/espresso/tools/ScreenshotFailureRule.kt b/vector-app/src/androidTest/java/im/vector/app/espresso/tools/ScreenshotFailureRule.kt index 5e131479bf..69b43770c5 100644 --- a/vector-app/src/androidTest/java/im/vector/app/espresso/tools/ScreenshotFailureRule.kt +++ b/vector-app/src/androidTest/java/im/vector/app/espresso/tools/ScreenshotFailureRule.kt @@ -24,7 +24,7 @@ import android.os.Build import android.os.Environment import android.provider.MediaStore import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation -import im.vector.app.core.time.DefaultClock +import im.vector.lib.core.utils.timer.DefaultClock import org.junit.rules.TestWatcher import org.junit.runner.Description import timber.log.Timber From bf113b757134627228c83c33053f86c57ba5072d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Feb 2023 10:21:45 +0000 Subject: [PATCH 138/189] Bump com.android.tools.build:gradle from 7.3.1 to 7.4.1 (#8059) Bumps com.android.tools.build:gradle from 7.3.1 to 7.4.1. --- updated-dependencies: - dependency-name: com.android.tools.build:gradle dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index bab9229b3b..413199363a 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -6,7 +6,7 @@ ext.versions = [ 'targetCompat' : JavaVersion.VERSION_11, ] -def gradle = "7.3.1" +def gradle = "7.4.1" // Ref: https://kotlinlang.org/releases.html def kotlin = "1.8.0" def kotlinCoroutines = "1.6.4" From e7f3cf6d5781eea0cf74cb1d9e4f31fff073117f Mon Sep 17 00:00:00 2001 From: yostyle Date: Thu, 2 Feb 2023 11:28:20 +0100 Subject: [PATCH 139/189] Fix PR comments --- .../timeline/factory/TimelineItemFactory.kt | 18 ++++++--------- .../MessageVoiceBroadcastListeningItem.kt | 22 +++++++++++-------- .../GetLiveVoiceBroadcastChunksUseCase.kt | 4 +++- 3 files changed, 23 insertions(+), 21 deletions(-) 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 9bcf3e1b6b..d44713f404 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 @@ -138,19 +138,15 @@ class TimelineItemFactory @Inject constructor( // Crypto EventType.ENCRYPTED -> { val relationContent = event.getRelationContent() - if (event.root.isRedacted()) { + when { // Redacted event, let the MessageItemFactory handle it - messageItemFactory.create(params) - } else if (relationContent?.type == RelationType.REFERENCE) { - // Hide the decryption error for VoiceBroadcast chunks - val startEvent = relationContent.eventId?.let { session.getRoom(event.roomId)?.getTimelineEvent(it) } - if (startEvent?.isVoiceBroadcast() == false) { - encryptedItemFactory.create(params) - } else { - null + event.root.isRedacted() -> messageItemFactory.create(params) + relationContent?.type == RelationType.REFERENCE -> { + // Hide the decryption error for VoiceBroadcast chunks + val relatedEvent = relationContent.eventId?.let { session.getRoom(event.roomId)?.getTimelineEvent(it) } + if (relatedEvent?.isVoiceBroadcast() != true) encryptedItemFactory.create(params) else null } - } else { - encryptedItemFactory.create(params) + else -> encryptedItemFactory.create(params) } } EventType.KEY_VERIFICATION_CANCEL, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt index 36a6eb93a2..b9d70b51cb 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastListeningItem.kt @@ -137,15 +137,19 @@ abstract class MessageVoiceBroadcastListeningItem : AbsMessageVoiceBroadcastItem private fun renderPlaybackError(holder: Holder, playbackState: State) { with(holder) { - if (playbackState is State.Error) { - controlsGroup.isVisible = false - errorView.setTextOrHide(errorFormatter.toHumanReadable(playbackState.failure)) - } else if (playbackState is State.Idle && hasUnableToDecryptEvent) { - controlsGroup.isVisible = false - errorView.setTextOrHide(errorFormatter.toHumanReadable(VoiceBroadcastFailure.ListeningError.UnableToDecrypt)) - } else { - errorView.isVisible = false - controlsGroup.isVisible = true + when { + playbackState is State.Error -> { + controlsGroup.isVisible = false + errorView.setTextOrHide(errorFormatter.toHumanReadable(playbackState.failure)) + } + playbackState is State.Idle && hasUnableToDecryptEvent -> { + controlsGroup.isVisible = false + errorView.setTextOrHide(errorFormatter.toHumanReadable(VoiceBroadcastFailure.ListeningError.UnableToDecrypt)) + } + else -> { + errorView.isVisible = false + controlsGroup.isVisible = true + } } } } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/usecase/GetLiveVoiceBroadcastChunksUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/usecase/GetLiveVoiceBroadcastChunksUseCase.kt index 05a465fb13..5a95f1a256 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/usecase/GetLiveVoiceBroadcastChunksUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/usecase/GetLiveVoiceBroadcastChunksUseCase.kt @@ -146,7 +146,9 @@ class GetLiveVoiceBroadcastChunksUseCase @Inject constructor( it.isVoiceBroadcast() && it.getVoiceBroadcastEventId() == voiceBroadcastId && it.root.senderId == senderId }?.root } - event.getClearType() == EventType.ENCRYPTED && relationContent?.type == RelationType.REFERENCE -> event + event.getClearType() == EventType.ENCRYPTED && relationContent?.type == RelationType.REFERENCE -> { + event.takeIf { relationContent.eventId == voiceBroadcastId } + } else -> null } } From ce28b99f7aac83081a63ebec97344baac5a8f867 Mon Sep 17 00:00:00 2001 From: waclaw66 Date: Tue, 31 Jan 2023 08:05:35 +0000 Subject: [PATCH 140/189] Translated using Weblate (Czech) Currently translated at 100.0% (2598 of 2598 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/cs/ --- library/ui-strings/src/main/res/values-cs/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/library/ui-strings/src/main/res/values-cs/strings.xml b/library/ui-strings/src/main/res/values-cs/strings.xml index c122de7798..b1f7df9bb4 100644 --- a/library/ui-strings/src/main/res/values-cs/strings.xml +++ b/library/ui-strings/src/main/res/values-cs/strings.xml @@ -2979,4 +2979,5 @@ Hlasovou zprávu nelze spustit, protoÅŸe právě nahráváte ÅŸivé vysílání. Ukončete prosím ÅŸivé vysílání, abyste mohli začít nahrávat hlasovou zprávu Nelze spustit hlasovou zprávu Chyba připojení - nahrávání pozastaveno + Pouşít formát inline kódu \ No newline at end of file From 47678c1edbc2d32df3a4b96b92137fcd892598c5 Mon Sep 17 00:00:00 2001 From: Vri Date: Tue, 31 Jan 2023 12:06:13 +0000 Subject: [PATCH 141/189] Translated using Weblate (German) Currently translated at 100.0% (2598 of 2598 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/de/ --- library/ui-strings/src/main/res/values-de/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/library/ui-strings/src/main/res/values-de/strings.xml b/library/ui-strings/src/main/res/values-de/strings.xml index 17fa9b6e44..06c477b410 100644 --- a/library/ui-strings/src/main/res/values-de/strings.xml +++ b/library/ui-strings/src/main/res/values-de/strings.xml @@ -2918,4 +2918,5 @@ Du kannst keine Sprachnachricht beginnen, da du im Moment eine EchtzeitÃŒbertragung aufzeichnest. Bitte beende deine SprachÃŒbertragung, um ein GesprÀch zu beginnen Kann Sprachnachricht nicht beginnen Verbindungsfehler − Aufnahme pausiert + Als Inline-Code formatieren \ No newline at end of file From 8e90f7c0914cdf2caf404fa12738f9cedce1e925 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Wed, 1 Feb 2023 15:07:14 +0000 Subject: [PATCH 142/189] Translated using Weblate (Estonian) Currently translated at 99.6% (2590 of 2598 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/et/ --- library/ui-strings/src/main/res/values-et/strings.xml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/library/ui-strings/src/main/res/values-et/strings.xml b/library/ui-strings/src/main/res/values-et/strings.xml index f33ade2a7e..0f005fe04e 100644 --- a/library/ui-strings/src/main/res/values-et/strings.xml +++ b/library/ui-strings/src/main/res/values-et/strings.xml @@ -2256,7 +2256,7 @@ Koosta valikud KÃŒsimus või teema KÃŒsitluse kÃŒsimus või teema - Koosta ÃŒks kÃŒsitlus + Loo selline kÃŒsitlus KÃŒsitlus Saada e-posti aadressid ja telefoninumbrid %s serverisse Sinu kontaktid on vaid sinu teada. Kui tahad nende hulgast leida Matrix\'i kasutajaid, siis me vajame sinu luba nende andmete saatmiseks rÀsitud kujul isikutuvastusserverisse. @@ -2330,9 +2330,9 @@ Asukoht Jaga asukohta Tulemusi kuvame vaid siis, kui kÃŒsitlus on lõppenud - KÃŒsitlus on lõppenud + Suletud valikutega kÃŒsitlus Osalejad nÀevad tulemusi peale oma valiku salvestamist - Ava kÃŒsitlus + Avatud valikutega kÃŒsitlus KÃŒsitluse tÌÌp Muuda kÃŒsitlust HÀÀletanuid ei ole @@ -2910,4 +2910,5 @@ HÀÀlsõnumi esitamine ei õnnestu Kuna sa hetkel salvestad ringhÀÀlingukõnet, siis hÀÀlsõnumi salvestamine või esitamine ei õnnestu. Selleks palun lõpeta ringhÀÀlingukõne Viga võrguÃŒhenduses - salvestamine on peatatud + Kasuta lõimitud koodi vormingut \ No newline at end of file From 80301b1601463814075f5a852ddf9ad629cbcff4 Mon Sep 17 00:00:00 2001 From: Danial Behzadi Date: Tue, 31 Jan 2023 10:29:02 +0000 Subject: [PATCH 143/189] Translated using Weblate (Persian) Currently translated at 100.0% (2598 of 2598 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fa/ --- library/ui-strings/src/main/res/values-fa/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/library/ui-strings/src/main/res/values-fa/strings.xml b/library/ui-strings/src/main/res/values-fa/strings.xml index d498f4a51b..9b1d367dde 100644 --- a/library/ui-strings/src/main/res/values-fa/strings.xml +++ b/library/ui-strings/src/main/res/values-fa/strings.xml @@ -2919,4 +2919,5 @@ از آن‌جا که در حال ضؚط ٟخ؎ی زنده‌اید، نمی‌توانید ٟیامی صوتی را آغاز کنید. لطفاً ؚرای آغاز ضؚط یک ٟیام صوتی، ٟخ؎ زنده‌تان را ٟایان دهید نمی‌توان ٟخ؎ صوتی را آغاز کرد خطای اتّصال - ضؚط مکث ؎د + اعمال قالؚ کد درون‌خط \ No newline at end of file From 6869c4bbb75de52c4cadb07aa8889d527128e589 Mon Sep 17 00:00:00 2001 From: Glandos Date: Wed, 1 Feb 2023 10:47:41 +0000 Subject: [PATCH 144/189] Translated using Weblate (French) Currently translated at 100.0% (2598 of 2598 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fr/ --- library/ui-strings/src/main/res/values-fr/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/library/ui-strings/src/main/res/values-fr/strings.xml b/library/ui-strings/src/main/res/values-fr/strings.xml index d62d208e43..491a2660c4 100644 --- a/library/ui-strings/src/main/res/values-fr/strings.xml +++ b/library/ui-strings/src/main/res/values-fr/strings.xml @@ -2919,4 +2919,5 @@ Vous ne pouvez pas commencer un message vocal car vous êtes en train d’enregistrer une diffusion en direct. Veuillez terminer cette diffusion pour commencer un message vocal Impossible de démarrer un message vocal Erreur de connexion – Enregistrement en pause + Appliquer le formatage de code en ligne \ No newline at end of file From d3b6a099defb357a2d557f03299a20e4dc0e5b9e Mon Sep 17 00:00:00 2001 From: Szimszon Date: Tue, 31 Jan 2023 11:43:18 +0000 Subject: [PATCH 145/189] Translated using Weblate (Hungarian) Currently translated at 100.0% (2598 of 2598 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/hu/ --- library/ui-strings/src/main/res/values-hu/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/library/ui-strings/src/main/res/values-hu/strings.xml b/library/ui-strings/src/main/res/values-hu/strings.xml index c265b79969..a44bc9b78b 100644 --- a/library/ui-strings/src/main/res/values-hu/strings.xml +++ b/library/ui-strings/src/main/res/values-hu/strings.xml @@ -2919,4 +2919,5 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze Nem lehet hang ÃŒzenetet indítani élő közvetítés felvétele közben. Az élő közvetítés bejezése szÃŒkséges a hang ÃŒzenet indításához Hang ÃŒzenetet nem lehet elindítani Kapcsolódási hiba – Felvétel szÃŒneteltetve + Beágyazott kód formátum alkalmazása \ No newline at end of file From 89813689cc260727bbcc31340c7727095cdb9927 Mon Sep 17 00:00:00 2001 From: Linerly Date: Tue, 31 Jan 2023 08:12:24 +0000 Subject: [PATCH 146/189] Translated using Weblate (Indonesian) Currently translated at 100.0% (2598 of 2598 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/id/ --- library/ui-strings/src/main/res/values-in/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/library/ui-strings/src/main/res/values-in/strings.xml b/library/ui-strings/src/main/res/values-in/strings.xml index 8a05481fd5..ca871db81b 100644 --- a/library/ui-strings/src/main/res/values-in/strings.xml +++ b/library/ui-strings/src/main/res/values-in/strings.xml @@ -2861,4 +2861,5 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
Kesalahan koneksi - Perekaman dijeda Anda tidak dapat memulai sebuah pesan suara karena Anda saat ini merekam sebuah siaran langsung. Silakan mengakhiri siaran langsung Anda untuk memulai merekam sebuah pesan suara Tidak dapat memulai pesan suara + Terapkan format kode dalam baris \ No newline at end of file From 3c722fd7ca63d8a916875b9eed440077a4269831 Mon Sep 17 00:00:00 2001 From: random Date: Wed, 1 Feb 2023 08:55:52 +0000 Subject: [PATCH 147/189] Translated using Weblate (Italian) Currently translated at 100.0% (2598 of 2598 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/it/ --- library/ui-strings/src/main/res/values-it/strings.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values-it/strings.xml b/library/ui-strings/src/main/res/values-it/strings.xml index d8c81974b2..80a0b7045f 100644 --- a/library/ui-strings/src/main/res/values-it/strings.xml +++ b/library/ui-strings/src/main/res/values-it/strings.xml @@ -2909,4 +2909,6 @@ Non puoi iniziare un messaggio vocale perché stai registrando una trasmissione in diretta. Termina la trasmissione per potere iniziare un messaggio vocale Impossibile iniziare il messaggio vocale - + Applica formato codice interlinea + Errore di connessione - Registrazione in pausa + \ No newline at end of file From 3ebe4619aa45c1fe5ce142b751c7ff243fd7db9b Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Thu, 2 Feb 2023 13:54:26 +0000 Subject: [PATCH 148/189] Translated using Weblate (Japanese) Currently translated at 96.6% (2510 of 2598 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/ --- .../src/main/res/values-ja/strings.xml | 118 +++++++++++------- 1 file changed, 74 insertions(+), 44 deletions(-) diff --git a/library/ui-strings/src/main/res/values-ja/strings.xml b/library/ui-strings/src/main/res/values-ja/strings.xml index 83bf953d11..06af1bd66a 100644 --- a/library/ui-strings/src/main/res/values-ja/strings.xml +++ b/library/ui-strings/src/main/res/values-ja/strings.xml @@ -93,7 +93,7 @@ ログを送信 開発者が問題を蚺断するために、このクラむアントのログがバグレポヌトず䞀緒に送信されたす。バグレポヌトは、ログずスクリヌンショットを含めお、公開されるこずはありたせん。䞊蚘の説明文だけを送信したい堎合は、以䞋のチェックを解陀しおください。 あなたは䞍満で端末を振っおいるようです。バグレポヌトの画面を開きたすか - 前回、アプリケヌションは正垞に停止したせんでした。クラッシュ報告の画面を開きたすか + 前回アプリケヌションは正垞に停止したせんでした。クラッシュ報告の画面を開きたすか 䞍具合を報告したした 䞍具合の報告の送信に倱敗したした (%s) ルヌムに参加 @@ -105,7 +105,7 @@ メヌルアドレスの圢匏が正しくありたせん このメヌルアドレスは既に登録されおいたす。 パスワヌドを忘れたしたか - 正しいURLを入力しお䞋さい + 正しいURLを入力しおください 原寞 倧き目 䞭皋床 @@ -118,9 +118,9 @@ ルヌムから退出 このルヌムから退出しおよろしいですか 招埅 - %sさんが文字を入力しおいたす  - %1$sさんず%2$sさんが文字を入力しおいたす  - %1$sさん、%2$sさん他が文字を入力しおいたす  + %sさんが入力しおいたす  + %1$sさんず%2$sさんが入力しおいたす  + %1$s、%2$s他が入力しおいたす  明るいテヌマ 暗いテヌマ 黒いテヌマ @@ -144,7 +144,7 @@ 1察1のチャットでのメッセヌゞ グルヌプチャットでのメッセヌゞ ルヌムぞ招埅されたずき - 通話の呌び出しがあったずき + 通話ぞの招埅 自動発蚀プログラムBotが発蚀した時 端末起動時に開始 アプリを閉じおいるずきの動䜜 @@ -247,9 +247,7 @@ 珟圚のパスワヌド 新しいパスワヌド パスワヌドの曎新に倱敗したした - %sの党おのメッセヌゞを衚瀺したすか -\n -\nこの操䜜はアプリを再起動するため、時間がかかる堎合がありたす。 + %sの党おのメッセヌゞを衚瀺したすか 倖芳 公開端末名 ルヌムの゚ンドツヌ゚ンド暗号鍵を゚クスポヌト @@ -280,7 +278,7 @@ りィゞェットをこのルヌムから削陀しおもよろしいですか 䞀臎しおいない堎合は、コミュニケヌションのセキュリティヌが損なわれおいる可胜性がありたす。 このセッションでは、未認蚌のセッションに察しお暗号化されたメッセヌゞを送信しない。 - 認蚌枈のセッションに察しおのみ暗号化 + 認蚌枈のセッションにのみ暗号化 むンポヌト ロヌカルファむルから鍵をむンポヌト ルヌムの暗号鍵をむンポヌト @@ -294,7 +292,7 @@ 通知あり音量倧 通知ありサむレント 䞍具合の報告 - このナヌザヌにあなたず同じ暩限を䞎えたす。この倉曎は取り消せたせん。 + このナヌザヌにあなたず同じ暩限レベルを䞎えようずしおいたす。この倉曎は取り消せたせん。 \nよろしいですか 信甚する 信甚しない @@ -314,8 +312,8 @@ olmのバヌゞョン サヌドパヌティヌの䜿甚に関する掲瀺 ホヌム画面 - 逃した通知があるルヌムを固定 - 未読のあるルヌムを固定 + 逃した通知があるルヌムをピン止め + 未読メッセヌゞがあるルヌムをピン止め 分析 埩号゚ラヌ セッションキヌ @@ -420,14 +418,14 @@ このルヌムは眮き換えられおおり、アクティブではありたせん。 こちらから継続䞭の䌚話を確認 このルヌムは別の䌚話の続きです - 以前のメッセヌゞを芋るには、ここをクリックしおください + 以前のメッセヌゞを衚瀺するには、ここをクリックしおください サヌビス管理者に連絡しおください このホヌムサヌバヌはリ゜ヌス制限の1぀を超過しおいるため、 ナヌザヌがログむンできなくなるこずがありたす。 - このホヌムサヌバヌはリ゜ヌス制限の1぀を超過しおいたす。 + このホヌムサヌバヌはリ゜ヌスの䞊限に達したした。 このホヌムサヌバヌは月間アクティブナヌザヌの䞊限に達しおいるため、 ナヌザヌがログむンできなくなるこずがありたす。 - このホヌムサヌバヌは月間アクティブナヌザヌの䞊限に達しおいたす。 + このホヌムサヌバヌは月間アクティブナヌザヌ数の䞊限に達したした 。 この制限を䞊げるには、%sしおください。 - このサヌビスを䜿い続けるには、%sしおください。 + このサヌビスの䜿甚を継続するには、%sしおください。 申し蚳ありたせん、゚ラヌが発生したした ゚クスポヌトされた鍵を暗号化するパスフレヌズを䜜成しおください。 鍵をむンポヌトするには、同䞀のパスフレヌズを入力する必芁がありたす。 パスフレヌズの䜜成 @@ -485,7 +483,7 @@ 眲名 通知に関する問題の解決 システム蚭定。 - アカりント蚭定。 + アカりントの蚭定。 カスタム蚭定。 起動時に実行 バックグラりンド制限の確認 @@ -585,7 +583,7 @@ コピヌ 成功 通知 - 砎棄 + 取り消す 再生 閉じる スキップ @@ -616,7 +614,7 @@ マヌクダりン曞匏 メッセヌゞ送信前にマヌクダりン曞匏を適甚したす。これにより、アスタリスクを䜿甚しお斜䜓のテキストを衚瀺するなどの高床な曞匏蚭定が利甚できたす。 音声ずビデオ - 囜際電話番号圢匏で入力しおください電話番号の最初に「+」が付きたす + 囜際電話番号圢匏で入力しおください電話番号の最初に「+」を付けおください メヌルアドレス あなたのアカりントに远加されたメヌルアドレスはありたせん あなたのアカりントに远加された電話番号はありたせん @@ -678,7 +676,7 @@ サりンドデバむスを遞択 リアルタむム接続を確立できたせんでした。 \nホヌムサヌバヌの管理者に、通話が正垞に動䜜するためにTURNを蚭定するようご連絡ください。 - 終了 + 電話を切る 拒吊 承諟 りィゞェットを削陀できたせんでした @@ -691,8 +689,8 @@ なし トピック ルヌム名 - このルヌム内のメッセヌゞぱンドツヌ゚ンド暗号化されおいたせん。 - ここでのメッセヌゞぱンドツヌ゚ンド暗号化されおいたせん。 + このルヌム内のメッセヌゞぱンドツヌ゚ンドで暗号化されおいたせん。 + ここでのメッセヌゞぱンドツヌ゚ンドで暗号化されおいたせん。 蚭定 あなたにはこのルヌムの暗号化を有効にする暩限がありたせん。 未読メッセヌゞ @@ -775,8 +773,8 @@ このルヌムは公開されおいたせん。 招埅がなければ再び参加するこずはできたせん。 連絡先ぞのアクセスを蚱可したす。 QRコヌドをスキャンするには、カメラぞのアクセスを蚱可する必芁がありたす。 - 通話をかけたした - %sが通話をかけたした + 通話を保留したした + %sが通話を保留したした 保留 通話をやり盎す ビデオ通話が行われおいたす  @@ -1221,7 +1219,7 @@ ホヌムサヌバヌAPIのURL アクセスを取り消す 衚瀺 - このルヌム内のメッセヌゞぱンドツヌ゚ンド暗号化されおいたす。 + このルヌムのメッセヌゞぱンドツヌ゚ンドで暗号化されおいたす。 ダむレクトメッセヌゞ 新しいダむレクトメッセヌゞを送信 メヌルアドレス任意 @@ -1305,7 +1303,7 @@ 珟圚、このルヌムにはアクセスできたせん。 \n埌でもう䞀床やり盎すか、ルヌムの管理者にアクセス暩があるかどうかを確認するよう䟝頌しおください。 このルヌムはプレビュヌできたせん - お埅ち䞋さい  + お埅ちください  ネットワヌクがありたせん。むンタヌネット接続を確認しおください。 䞍正な圢匏のむベントです。衚瀺できたせん ルヌムの管理者によっおモデレヌトされたむベント @@ -1314,8 +1312,8 @@ リアクションを远加 同意 リアクション - ルヌムがここに衚瀺されたす。右䞋の[+]をタップしお、既存のルヌムを怜玢するか、独自のルヌムを開始したす。 - ダむレクトメッセヌゞの䌚話がここに衚瀺されたす。右䞋の[+]をタップしお開始したす。 + ルヌムがここに衚瀺されたす。右䞋のをタップするず、既存のルヌムを怜玢するか、自分のルヌムを開始できたす。 + ダむレクトメッセヌゞの䌚話がここに衚瀺されたす。右䞋のをタップしお開始したす。 ルヌム 䌚話 未読メッセヌゞはありたせん @@ -1336,7 +1334,7 @@ %1$s%2$s %3$s %1$s%2$s あなたが知らないかもしれない他のスペヌスやルヌム - 誰がこのルヌムを怜玢し、参加できるか決める。 + 誰がこのルヌムを怜玢・参加できるか遞択しおください。 タップしスペヌスを線集 スペヌスを遞択 アクセス可胜なスペヌス @@ -1386,11 +1384,11 @@ %sにメヌルを送りたした。メヌルを確認しおリンクをクリックしおください %sにメヌルを送りたした。メヌルの確認リンクをクリックしおください 発芋可胜な電話番号 - IDサヌバヌずの接続を解陀するず、他のナヌザヌによっお発芋されなくなり、たた、メヌルアドレスや電話で他のナヌザヌを招埅するこずができなくなりたす。 + IDサヌバヌずの接続を解陀するず、他のナヌザヌによっお芋぀けられなくなり、たた、メヌルアドレスや電話で他のナヌザヌを招埅するこずもできなくなりたす。 電話番号を远加するず、発芋可胜に蚭定する電話番号を遞択できるようになりたす。 メヌルアドレスを远加するず、発芋可胜に蚭定するメヌルアドレスを遞択できるようになりたす。 - 珟圚、IDサヌバヌを䜿甚しおいたせん。あなたの知っおいる連絡先を発芋したり、その連絡先から発芋されるようにするには、以䞋でIDサヌバヌを蚭定しおください。 - あなたは珟圚%1$sを䜿っお連絡先を芋぀けたり、連絡先から芋぀けられるようにしおいたす。 + 珟圚、IDサヌバヌを䜿甚しおいたせん。自分の連絡先を芋぀けたり、連絡先から芋぀けおもらったりするには、以䞋でIDサヌバヌを蚭定しおください。 + 珟圚%1$sを䜿っお自分の連絡先を芋぀けたり、連絡先から芋぀けおもらったりできるようにしおいたす。 IDサヌバヌを倉曎 IDサヌバヌの蚭定 IDサヌバヌの切断 @@ -1521,7 +1519,7 @@ 音声メッセヌゞを録音できたせん ルヌムのアップグレヌドには暩限が必芁です アップグレヌド - アップグレヌドが必芁です + アップグレヌドが必芁 非公開のルヌムをアップグレヌド 音声メッセヌゞを䞀時停止 このメヌルアドレスをアカりントにリンク @@ -1594,11 +1592,11 @@ キヌワヌドに「%s」を含めるこずはできたせん %sぞのメヌル通知を有効にする ヒントメッセヌゞを長抌ししお「%s」を遞択。 - スレッドを甚いるず、䌚話のテヌマを保ったり、䌚話を远跡したりするのが容易になりたす。 + スレッド機胜を䜿うず、䌚話のテヌマを維持したり、䌚話を簡単に远跡したりするこずができたす。 あなたの非公開のスペヌス あなたの公開スペヌス 自分のみ - スレッドで議論を敎理しお管理 + スレッド機胜を䜿っお、䌚話をたずめたしょう %sを埅機しおいたす  この端末でスキャン 認蚌を送信枈 @@ -1819,7 +1817,7 @@ 再送信 コヌドを入力 電話番号 - 退垭䞭 + 離垭䞭 オフラむン オンラむン 䞀般 @@ -1954,7 +1952,7 @@ 認蚌 メヌルアドレスが正しくないようです 囜際電話番号の圢匏を䜿甚しおください。 - 囜際電話番号は「+」から始たる必芁がありたす + 囜際電話番号は「+」から始めおください コヌドを%1$sに送信したした。以䞋に入力しお認蚌しおください。 このメヌルアドレスはどのアカりントにも登録されおいたせん パスワヌドを倉曎するず、党おのセッションでの゚ンドツヌ゚ンド暗号鍵がリセットされ、暗号化されたメッセヌゞ履歎が読めなくなりたす。パスワヌドを再蚭定する前に、鍵のバックアップを蚭定するか、他のセッションからルヌムの鍵を゚クスポヌトしおおいおください。 @@ -1988,7 +1986,7 @@ 音を出さずに通知 音を出しお通知 既定の信頌レベル - いく぀かのメッセヌゞは送信されたせんでした + いく぀かのメッセヌゞが送信されたせんでした 保存されおいない倉曎がありたす。倉曎を砎棄したすか このルヌムはただ䜜成されおいたせん。キャンセルしたすか テキストメッセヌゞで共有 @@ -2061,7 +2059,7 @@ CAPTCHA認蚌を行っおください アカりントがただ䜜成されおいたせん。登録を䞭止したすか %1$sにアカりント登録 - あなたはすべおのセッションからログアりトしおおり、これ以䞊プッシュ通知を受け取れたせん。通知を再び有効にするには、各端末でサむンむンしおください。 + すべおのセッションからログアりトしおいるため、プッシュ通知を受け取れたせん。通知を再び有効にするには、各端末でサむンむンしおください。 セキュリティヌフレヌズを蚭定 セキュリティヌフレヌズを䜿甚 セキュリティヌキヌは、パスワヌドマネヌゞャヌもしくは金庫のような安党な堎所で保管しおください。 @@ -2134,7 +2132,7 @@ このナヌザヌずのメッセヌゞぱンドツヌ゚ンドで暗号化されおおり、第䞉者が解読するこずはできたせん。 このコヌドを盞手の画面に珟れおいるコヌドず比范しおください。 絵文字を比范しお、同じ順番で珟れおいるのを確認しおください。 - セキュリティヌを高めるために、察面で行うか、他の信頌できる通信手段を利甚したしょう。 + セキュリティヌを高めるために、察面で行うか、他の通信手段を利甚したしょう。 遞択された゚モヌトを虹色にしお送信したす 遞択されたテキストを虹色にしお送信したす ${app_name}がID%1$sのむベントを凊理䞭に゚ラヌが発生したした @@ -2186,7 +2184,7 @@ 私のスペヌス %1$s %2$s に参加しおください スキップ ルヌムの通知 - 珟圚、IDサヌバヌを䜿甚しおいたせん。あなたの知っおいるチヌムメむトを発芋したり、そのチヌムメむトから発芋されるようにするには、以䞋でIDサヌバヌを蚭定しおください。 + 珟圚、IDサヌバヌを䜿甚しおいたせん。あなたの知っおいるチヌムメむトを招埅したり、チヌムメむトから芋぀けおもらったりするには、以䞋でIDサヌバヌを蚭定しおください。 ディスカバリヌの蚭定を終了したす。 ここの参加者はあなただけです。退出するず、今埌あなたを含めお誰も参加できなくなりたす。 再び招埅されない限り、再参加するこずはできたせん。 @@ -2251,7 +2249,7 @@ 通話の転送䞭に゚ラヌが発生したした 2分埌にPINコヌドを芁求 ルヌム名やメッセヌゞの内容などの詳现を衚瀺。 - ゚ラヌが倚すぎたす。ログアりトしたした + 倚数の゚ラヌが発生したため、ログアりトしたした 譊告もう䞀床誀ったコヌドを入力するず、ログアりトしたす コヌドが誀っおいたす。残りの詊行回数は%d回です @@ -2376,7 +2374,7 @@ %1$sず他%2$d名 %1$sず%2$s - ホヌムサヌバヌがサポヌトしおいないため、スレッド機胜は䞍安定かもしれたせん。スレッドのメッセヌゞは安定しお衚瀺されないおそれがありたす。%sスレッド機胜を有効にしおよろしいですか + ホヌムサヌバヌがサポヌトしおいないため、スレッド機胜は䞍安定かもしれたせん。スレッドのメッセヌゞが安定しお衚瀺されないおそれがありたす。%sスレッド機胜を有効にしおよろしいですか スレッドベヌタ版 スレッドを甚いるず、䌚話のテヌマを保ったり、䌚話を远跡したりするのが容易になりたす。%sスレッドを有効にするずアプリケヌションが再起動したす。再起動には時間がかかる可胜性がありたす。 スレッドベヌタ版 @@ -2730,4 +2728,36 @@ ルヌムが芋぀かりたせんでした。 \n埌でやり盎しおください。%s 盎接共有を有効にする + 信頌枈の信頌レベル + 譊告の信頌レベル + %1$sず盞談しおいたす + 初めに盞談 + 実斜䞭の通話%1$s・ + + %1$d件の実斜䞭の通話・ + + 実斜䞭の通話%1$s + 䞍圚着信ビデオ + 䞍圚着信音声 + 実斜䞭のビデオ通話 + 実斜䞭の音声通話 + 新しい生䜓認蚌の方法が最近远加されたため、生䜓認蚌は無効になりたした。蚭定から再び有効にできたす。 + このIDずの連携は珟圚ありたせん。 + このホヌムサヌバヌは数字だけからなるナヌザヌ名を承諟したせん。 + 最初のメッセヌゞを送信するず、%sを䌚話に招埅 + Nightly build + あなたの他の端末でコヌドをスキャンするか、もしくは反察に、このデバむスでスキャンしおください + Element Matrix ServicesEMSは、高速、安党でリアルタむムのコミュニケヌション向きの、堅牢で安定したホスティングサヌビスです。<a href=\"${ftue_ems_url}\">element.io/ems</a>で方法を調べたしょう。 + アカりントにサむンむンするサヌバヌ + アカりントを䜜成するサヌバヌ + スレッドは、改良した通知など新機胜の远加䜜業䞭です。フィヌドバックをお聞かせください + 🔒 セキュリティヌの蚭定で、党おのルヌムに関しお認蚌枈のセッションにのみ暗号化を行うよう蚭定したした。 + プレれンスステヌタス衚瀺 + 取り蟌み䞭 + 他の人は %s であなたを芋぀けるこずができたす + 有効 + プロフィヌルのタグ + 問題が発生したした。ネットワヌクの接続を確認しお、もう䞀床やり盎しおください。 + 匕甚 + チヌム、友達、組織向けのオヌルむンワンの安党なチャットアプリです。はじめに、チャットを䜜成するか既存のルヌムに参加したしょう。 \ No newline at end of file From a8aea0284bd36072f7a72d59f82eb07b2f23f9ca Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 31 Jan 2023 09:11:11 +0000 Subject: [PATCH 149/189] Translated using Weblate (Japanese) Currently translated at 96.6% (2510 of 2598 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/ --- library/ui-strings/src/main/res/values-ja/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/ui-strings/src/main/res/values-ja/strings.xml b/library/ui-strings/src/main/res/values-ja/strings.xml index 06af1bd66a..54a8c8ee0f 100644 --- a/library/ui-strings/src/main/res/values-ja/strings.xml +++ b/library/ui-strings/src/main/res/values-ja/strings.xml @@ -2632,7 +2632,7 @@ 䜍眮情報ラむブの共有を有効にする 䜍眮情報を共有しおいたす ${app_name}䜍眮情報ラむブ - 残り%1$d + 残り%1$s 䜍眮情報ラむブを衚瀺 䜍眮情報ラむブが終了したした 䜍眮情報ラむブを読み蟌んでいたす  @@ -2655,7 +2655,7 @@ MSC3061過去のメッセヌゞ甚にルヌムの鍵を共有 ラむブ配信を終了しおよろしいですか配信を終了し、録音をこのルヌムで利甚できるよう蚭定したす。 ラむブ配信を停止したすか - 残り%1$d + 残り%1$s 接続゚ラヌ - 録音を停止したした この音声配信を再生できたせん。 既に音声配信を録音しおいたす。新しく始めるには今の音声配信を終了しおください。 From 81d5248db1e07bdca8c303601b17db27e93b11c0 Mon Sep 17 00:00:00 2001 From: Jozef Gaal Date: Tue, 31 Jan 2023 14:25:00 +0000 Subject: [PATCH 150/189] Translated using Weblate (Slovak) Currently translated at 100.0% (2598 of 2598 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/sk/ --- library/ui-strings/src/main/res/values-sk/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/library/ui-strings/src/main/res/values-sk/strings.xml b/library/ui-strings/src/main/res/values-sk/strings.xml index c9e92d323b..82deefb371 100644 --- a/library/ui-strings/src/main/res/values-sk/strings.xml +++ b/library/ui-strings/src/main/res/values-sk/strings.xml @@ -2979,4 +2979,5 @@ NemÃŽÅŸete spustiÅ¥ hlasovú správu, pretoÅŸe práve nahrávate ÅŸivé vysielanie. Ukončite prosím ÅŸivé vysielanie, aby ste mohli začaÅ¥ nahrávaÅ¥ hlasovú správu NemoÅŸno spustiÅ¥ hlasovú správu Chyba pripojenia - nahrávanie pozastavené + PouÅŸiÅ¥ formát riadkového kódu \ No newline at end of file From 3e9466859239b4db4ea76d6156adcf090379d39e Mon Sep 17 00:00:00 2001 From: Besnik Bleta Date: Tue, 31 Jan 2023 12:15:44 +0000 Subject: [PATCH 151/189] Translated using Weblate (Albanian) Currently translated at 99.3% (2582 of 2598 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/sq/ --- .../src/main/res/values-sq/strings.xml | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values-sq/strings.xml b/library/ui-strings/src/main/res/values-sq/strings.xml index 374080cb23..447a2b52d7 100644 --- a/library/ui-strings/src/main/res/values-sq/strings.xml +++ b/library/ui-strings/src/main/res/values-sq/strings.xml @@ -2887,4 +2887,23 @@ S’arrihet të luhet ky transmetim zanor. Nisni një transmetim zanor Shërbyesi juaj Home s’mbulon ende paraqitje rrjedhash. - + Apliko format kodi brendazi + Gabim në sjellje pyetësorësh. + Ngarko më tepër pyetësorë + Shfaqje pyetësorësh + + S’ka pyetësorë të kaluar për ditën e kaluar. +\nQë të shihni pyetësorë për ditët e kaluara, ngarkoni më tepër pyetësorë. + S’ka pyetësorë aktivë për %1$d ditët e kaluara. +\nQë të shihni pyetësorë për ditët e kaluara, ngarkoni më tepër pyetësorë. + + + S’ka pyetësorë aktivë për ditën e kaluar. +\nQë të shihni pyetësorë për ditët e kaluara, ngarkoni më tepër pyetësorë. + S’ka pyetësorë aktivë për%1$d ditët e kaluara. +\nQë të shihni pyetësorë për ditët e kaluara, ngarkoni më tepër pyetësorë. + + Gabim lidhjeje - Incizimi u ndal + S’mund të nisni një mesazh zanor teksa jeni aktualisht duke incizuar një transmetim të drejtpërdrejtë. Ju lutemi, përfundoni transmetimin tuaj të drejtpërdrejtë, që të mund të nisni incizimin e një mesazhi zanor + S’niset dot mesazh zanor + \ No newline at end of file From e7781b9c8b761fb0f0d86b96ec55c54b96da5b5f Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Tue, 31 Jan 2023 10:16:23 +0000 Subject: [PATCH 152/189] Translated using Weblate (Ukrainian) Currently translated at 100.0% (2598 of 2598 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/uk/ --- library/ui-strings/src/main/res/values-uk/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/library/ui-strings/src/main/res/values-uk/strings.xml b/library/ui-strings/src/main/res/values-uk/strings.xml index 0f6027903f..26d38bb324 100644 --- a/library/ui-strings/src/main/res/values-uk/strings.xml +++ b/library/ui-strings/src/main/res/values-uk/strings.xml @@ -3039,4 +3039,5 @@ ВО Ме ЌПжете рПзпПчатО запОс гПлПсПвПгП пПвіЎПЌлеММя, ПскількО вО запОсуєте траМсляцію МажОвП. БуЎь ласка, заверште її, щПб рПзпПчатО запОс гПлПсПвПгП пПвіЎПЌлеММя Не вЎалПся рПзпПчатО запОс гПлПсПвПгП пПвіЎПЌлеММя ППЌОлка з\'єЎМаММя - ЗапОс прОзупОМеМП + ЗастПсПвуватО вбуЎПваМОй фПрЌат кПЎу \ No newline at end of file From da04423d31bc72b9d2e25bf822e53c74aa57a921 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Wed, 1 Feb 2023 02:24:25 +0000 Subject: [PATCH 153/189] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (2598 of 2598 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/zh_Hant/ --- library/ui-strings/src/main/res/values-zh-rTW/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/library/ui-strings/src/main/res/values-zh-rTW/strings.xml b/library/ui-strings/src/main/res/values-zh-rTW/strings.xml index 4b9888e122..b3845e550d 100644 --- a/library/ui-strings/src/main/res/values-zh-rTW/strings.xml +++ b/library/ui-strings/src/main/res/values-zh-rTW/strings.xml @@ -2859,4 +2859,5 @@ 連線錯誀 - 錄補已暫停 悚無法開始語音蚊息因為悚目前正圚錄補盎播。請結束悚的盎播以開始錄補語音蚊息 無法開始語音蚊息 + 套甚內嵌皋匏碌栌匏 \ No newline at end of file From 1c343661f2a58c69006688edc82e8057703470d5 Mon Sep 17 00:00:00 2001 From: Szimszon Date: Tue, 31 Jan 2023 11:43:02 +0000 Subject: [PATCH 154/189] Translated using Weblate (Hungarian) Currently translated at 100.0% (91 of 91 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/hu/ --- fastlane/metadata/android/hu-HU/changelogs/40105220.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/hu-HU/changelogs/40105220.txt diff --git a/fastlane/metadata/android/hu-HU/changelogs/40105220.txt b/fastlane/metadata/android/hu-HU/changelogs/40105220.txt new file mode 100644 index 0000000000..8d6ab49da1 --- /dev/null +++ b/fastlane/metadata/android/hu-HU/changelogs/40105220.txt @@ -0,0 +1,2 @@ +Legnagyobb változtatás ebben a verzióban: Fejlesztések a hang közvetítésben. +Teljes változási napló: https://github.com/vector-im/element-android/releases From 4f1afee7d24840515cf34be8e18ea2d4d4d70e87 Mon Sep 17 00:00:00 2001 From: random Date: Wed, 1 Feb 2023 08:56:31 +0000 Subject: [PATCH 155/189] Translated using Weblate (Italian) Currently translated at 100.0% (91 of 91 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/it/ --- fastlane/metadata/android/it-IT/changelogs/40105220.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/it-IT/changelogs/40105220.txt diff --git a/fastlane/metadata/android/it-IT/changelogs/40105220.txt b/fastlane/metadata/android/it-IT/changelogs/40105220.txt new file mode 100644 index 0000000000..f4343ee074 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/40105220.txt @@ -0,0 +1,2 @@ +Modifiche principali in questa versione: miglioramenti nella funzionalità di trasmissione vocale. +Cronologia completa: https://github.com/vector-im/element-android/releases From d7ddcf85d6dbaf321cad2370e49e2e0b0ddb75cc Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Wed, 1 Feb 2023 16:11:34 +0000 Subject: [PATCH 156/189] Translated using Weblate (Japanese) Currently translated at 100.0% (91 of 91 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/ja/ --- .../android/ja-JP/changelogs/40100160.txt | 2 +- .../android/ja-JP/changelogs/40101000.txt | 2 +- .../android/ja-JP/changelogs/40103030.txt | 2 ++ .../android/ja-JP/changelogs/40103040.txt | 2 ++ .../android/ja-JP/changelogs/40103050.txt | 2 ++ .../android/ja-JP/changelogs/40103060.txt | 2 ++ .../android/ja-JP/full_description.txt | 28 +++++++++---------- 7 files changed, 24 insertions(+), 16 deletions(-) create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40103030.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40103040.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40103050.txt create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40103060.txt diff --git a/fastlane/metadata/android/ja-JP/changelogs/40100160.txt b/fastlane/metadata/android/ja-JP/changelogs/40100160.txt index 169b4ce67f..1eb18685f8 100644 --- a/fastlane/metadata/android/ja-JP/changelogs/40100160.txt +++ b/fastlane/metadata/android/ja-JP/changelogs/40100160.txt @@ -1,2 +1,2 @@ -このバヌゞョンの䞻な倉曎点パフォヌマンスの向䞊ず、䞍具合を修正したした +このバヌゞョンの䞻な倉曎点゜ヌシャルログむンのサポヌト。 曎新履歎https://github.com/vector-im/element-android/releases/tag/v1.0.15 and https://github.com/vector-im/element-android/releases/tag/v1.0.16 diff --git a/fastlane/metadata/android/ja-JP/changelogs/40101000.txt b/fastlane/metadata/android/ja-JP/changelogs/40101000.txt index 1b85fe8725..a91371f9f7 100644 --- a/fastlane/metadata/android/ja-JP/changelogs/40101000.txt +++ b/fastlane/metadata/android/ja-JP/changelogs/40101000.txt @@ -1,2 +1,2 @@ -このバヌゞョンの䞻な倉曎点パフォヌマンスの向䞊ず、䞍具合を修正したした +このバヌゞョンの䞻な倉曎点VoIPダむレクトメッセヌゞでの音声・ビデオ通話の改善ず、䞍具合を修正したした 曎新履歎https://github.com/vector-im/element-android/releases/tag/v1.1.0 diff --git a/fastlane/metadata/android/ja-JP/changelogs/40103030.txt b/fastlane/metadata/android/ja-JP/changelogs/40103030.txt new file mode 100644 index 0000000000..091cdfbe5c --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40103030.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点IDサヌバヌの方針を蚭定画面に衚瀺。Android Autoのサポヌトを䞀時的に削陀。 +曎新履歎https://github.com/vector-im/element-android/releases/tag/v1.3.3 diff --git a/fastlane/metadata/android/ja-JP/changelogs/40103040.txt b/fastlane/metadata/android/ja-JP/changelogs/40103040.txt new file mode 100644 index 0000000000..d640cdfe58 --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40103040.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点ダむレクトメッセヌゞのルヌムでプレれンスステヌタス衚瀺のサポヌトを远加泚意プレれンスは matrix.org では無効です。Android Autoのサポヌトを再远加。 +曎新履歎https://github.com/vector-im/element-android/releases/tag/v1.3.4 diff --git a/fastlane/metadata/android/ja-JP/changelogs/40103050.txt b/fastlane/metadata/android/ja-JP/changelogs/40103050.txt new file mode 100644 index 0000000000..735e2ee200 --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40103050.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点ダむレクトメッセヌゞのルヌムでプレれンスステヌタス衚瀺のサポヌトを远加泚意プレれンスは matrix.org では無効です。Android Autoのサポヌトを再远加。 +曎新履歎https://github.com/vector-im/element-android/releases/tag/v1.3.5 diff --git a/fastlane/metadata/android/ja-JP/changelogs/40103060.txt b/fastlane/metadata/android/ja-JP/changelogs/40103060.txt new file mode 100644 index 0000000000..4cf878d1f0 --- /dev/null +++ b/fastlane/metadata/android/ja-JP/changelogs/40103060.txt @@ -0,0 +1,2 @@ +このバヌゞョンの䞻な倉曎点ダむレクトメッセヌゞのルヌムでプレれンスステヌタス衚瀺のサポヌトを远加泚意プレれンスは matrix.org では無効です。Android Autoのサポヌトを再远加。 +曎新履歎https://github.com/vector-im/element-android/releases/tag/v1.3.6 diff --git a/fastlane/metadata/android/ja-JP/full_description.txt b/fastlane/metadata/android/ja-JP/full_description.txt index 657acd77d2..2d351ca47c 100644 --- a/fastlane/metadata/android/ja-JP/full_description.txt +++ b/fastlane/metadata/android/ja-JP/full_description.txt @@ -1,39 +1,39 @@ -Elementは、安党なメッセヌゞングアプリ、リモヌトワヌク䞭のグルヌプチャットに適したチヌムコラボレヌションアプリです。゚ンド・ツヌ・゚ンドの暗号化技術を䜿甚しお、匷力なビデオ䌚議、ファむル共有、音声通話を提䟛したす。 +Elementは、安党なメッセヌゞングアプリ、リモヌトワヌク䞭のグルヌプチャットに適したチヌムコラボレヌションアプリです。゚ンドツヌ゚ンドの暗号化を䜿甚しお、匷力なビデオ䌚議、ファむル共有、音声通話を提䟛したす。 Elementの特城 - 高床なオンラむンコミュニケヌションツヌル - メッセヌゞの完党な暗号化。リモヌトワヌカヌでも、より安党な䌁業コミュニケヌションが可胜 - Matrixオヌプン゜ヌスフレヌムワヌクに基づく、分散型のチャット -- プロゞェクトの管理ず䞊行しお、デヌタの暗号化によりファむルを安党に共有するこずが可胜 +- プロゞェクトを管理しながら、デヌタの暗号化により安党にファむルを共有 - Voice over IPによるビデオチャットず画面共有 -- お気に入りのオンラむンコラボレヌションツヌルや、プロゞェクト管理ツヌル、VoIPサヌビス、その他のチヌムメッセヌゞングアプリず簡単に統合可胜 +- お気に入りのオンラむンコラボレヌションツヌル、プロゞェクト管理ツヌル、VoIPサヌビス、その他のチヌムメッセヌゞングアプリず簡単に統合可胜 -Elementは、他のメッセヌゞングアプリやコラボレヌションアプリずは党く異なりたす。安党なメッセヌゞングず分散型非䞭倮集暩型コミュニケヌションのためのオヌプンネットワヌクであるMatrixで動䜜したす。自分のデヌタやメッセヌゞを最倧限にコントロヌルするために、あなた自身がサヌバヌを運営するこずもできたす。 +Elementは、他のメッセヌゞングアプリやコラボレヌションアプリずは党く異なりたす。安党なメッセヌゞングず分散型コミュニケヌションのためのオヌプンネットワヌクであるMatrix䞊で動䜜したす。たた、ナヌザヌが自分のデヌタやメッセヌゞを最倧限にコントロヌルできるように、セルフホスティングに察応しおいたす。 プラむバシヌず暗号化されたコミュニケヌション -Elementは、望たしくない広告、デヌタマむニング、囲い蟌みからナヌザヌを守りたす。たた、゚ンド・ツヌ・゚ンドの暗号化ず、盞互眲名による端末の認蚌に基づき、党おのデヌタ、ビデオ䌚議、音声通信を保護したす。 +Elementは、望たしくない広告、デヌタマむニング、囲い蟌みからナヌザヌを保護したす。たた、゚ンドツヌ゚ンドの暗号化ず盞互眲名による端末の認蚌により、党おのデヌタ、1察1のビデオおよび音声通信を保護したす。 -Elementでは、Matrixネットワヌクにいる誰ずでもコミュニケヌションが行えるだけでなく、Slackなどのアプリず連携すれば、他のネットワヌクずもコミュニケヌションを行うずずもに、プラむバシヌをコントロヌルするこずができたす。 +Elementでは、Matrixのネットワヌク、たたはSlackなどのアプリを統合しお他のビゞネスコラボレヌションツヌルにいる誰ずでもコミュニケヌションを行いながら、プラむバシヌをコントロヌルするこずができたす。 -セルフホスティングが可胜 -機密デヌタや䌚話の管理を匷化するために、Elementはセルフホスティングが可胜です。たたは、オヌプン゜ヌスの分散型コミュニケヌションの暙準であるMatrixに基づくサヌバヌを遞ぶこずもできたす。Elementは、プラむバシヌ、セキュリティヌコンプラむアンス、および柔軟な機胜統合を提䟛したす。 +Elementはセルフホスティングが可胜 +機密デヌタや䌚話の管理を匷化するために、Elementはセルフホスティングに察応しおいたす。たたは、オヌプン゜ヌスの分散型コミュニケヌションの暙準であるMatrixに基づくサヌバヌを遞択するこずもできたす。Elementは、プラむバシヌ、セキュリティヌコンプラむアンス、および機胜統合の柔軟性を提䟛したす。 自分のデヌタを所有する -デヌタやメッセヌゞを保管する堎所を自分で決めるこずができたす。デヌタマむニングや第䞉者ぞのデヌタ流出のリスクはありたせん。 +デヌタやメッセヌゞを保管する堎所をご自身で決めるこずができたす。デヌタマむニングや第䞉者ぞのデヌタ流出のリスクはありたせん。 Elementでは、どのサヌバヌを䜿うかをご自身で決めるこずができたす。 -1. 開発者が運営する matrix.org の公開サヌバヌで無料アカりントを取埗するか、ボランティアが管理しおいる運営サヌバヌから遞ぶ。 -2. あなた自身がサヌバヌを運営し、アカりントを管理する。 -3. Element Matrix Servicesの運営プラットフォヌムに加入し、カスタムサヌバヌ䞊でアカりントを䜜る。 +1. 開発者が運営する matrix.org の公開サヌバヌで無料アカりントを取埗するか、ボランティアが管理しおいる運営サヌバヌから遞択 +2. あなた自身でサヌバヌを運営し、アカりントを管理 +3. Element Matrix Servicesのホスティングプラットフォヌムに加入し、カスタムサヌバヌ䞊でアカりントを䜜成 オヌプンなメッセヌゞングずコラボレヌション -盞手がElement、他のMatrixアプリ、さらには他のメッセヌゞングアプリを䜿っおいるかに関わらず、Matrixネットワヌク䞊の誰ずでもチャットをするこずができたす。 +盞手がElement、他のMatrixアプリ、その他のメッセヌゞングアプリを䜿っおいるかに関わらず、Matrixネットワヌク䞊の誰ずでもチャットをするこずができたす。 非垞に安党 本物の゚ンド・ツヌ・゚ンドの暗号化䌚話に参加しおいる人だけがメッセヌゞを埩号化できたすず、クロス眲名による端末の認蚌が可胜です。 包括的なコミュニケヌションず統合 -メッセヌゞング、音声およびビデオ通話、ファむル共有、画面共有、その他倚くの機胜統合、ボット、りィゞェットを提䟛したす。ルヌムやコミュニティヌを立ち䞊げお連絡を取り合い、物事をスムヌズに成し遂げたしょう。 +メッセヌゞング、音声およびビデオ通話、ファむル共有、画面共有、その他倚くの機胜統合、ボット、りィゞェットを提䟛したす。ルヌムやコミュニティヌを䜜っお連絡を取り合い、物事をスムヌズに成し遂げたしょう。 い぀でも、どこにいおも メッセヌゞの履歎は、党おの端末ずりェブ https://app.element.io で完党に同期されるので、どこからでも連絡を取り合うこずができたす。 From e5d3b1d5372a77ba9e175dd4ae28342203e1c4bf Mon Sep 17 00:00:00 2001 From: Besnik Bleta Date: Tue, 31 Jan 2023 12:16:54 +0000 Subject: [PATCH 157/189] Translated using Weblate (Albanian) Currently translated at 100.0% (91 of 91 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/sq/ --- fastlane/metadata/android/sq/changelogs/40105220.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/sq/changelogs/40105220.txt diff --git a/fastlane/metadata/android/sq/changelogs/40105220.txt b/fastlane/metadata/android/sq/changelogs/40105220.txt new file mode 100644 index 0000000000..52f541dd4d --- /dev/null +++ b/fastlane/metadata/android/sq/changelogs/40105220.txt @@ -0,0 +1,2 @@ +Ndryshimet kryesore në këtë version: Kryesisht përmirësime për veçorinë e transmetimeve zanore. +Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases From a8be83917aaf4706652e85f838389b7cded31699 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Feb 2023 23:03:38 +0000 Subject: [PATCH 158/189] Bump org.jetbrains.kotlin:kotlin-reflect from 1.8.0 to 1.8.10 Bumps [org.jetbrains.kotlin:kotlin-reflect](https://github.com/JetBrains/kotlin) from 1.8.0 to 1.8.10. - [Release notes](https://github.com/JetBrains/kotlin/releases) - [Changelog](https://github.com/JetBrains/kotlin/blob/v1.8.10/ChangeLog.md) - [Commits](https://github.com/JetBrains/kotlin/compare/v1.8.0...v1.8.10) --- updated-dependencies: - dependency-name: org.jetbrains.kotlin:kotlin-reflect dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- vector-app/build.gradle | 2 +- vector/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/vector-app/build.gradle b/vector-app/build.gradle index b2c2e79bcb..47f1660ad5 100644 --- a/vector-app/build.gradle +++ b/vector-app/build.gradle @@ -403,7 +403,7 @@ dependencies { androidTestImplementation libs.mockk.mockkAndroid androidTestUtil libs.androidx.orchestrator androidTestImplementation libs.androidx.fragmentTesting - androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:1.8.0" + androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:1.8.10" debugImplementation libs.androidx.fragmentTestingManifest debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.10' } diff --git a/vector/build.gradle b/vector/build.gradle index 3b30919c6d..7e12ebdccb 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -332,5 +332,5 @@ dependencies { androidTestUtil libs.androidx.orchestrator debugImplementation libs.androidx.fragmentTestingManifest androidTestImplementation libs.androidx.fragmentTesting - androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:1.8.0" + androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:1.8.10" } From cfd598b3ddf30cfe5bd5ecbf5f946b2577a99f3e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Feb 2023 23:04:17 +0000 Subject: [PATCH 159/189] Bump org.checkerframework:checker from 3.29.0 to 3.30.0 Bumps [org.checkerframework:checker](https://github.com/typetools/checker-framework) from 3.29.0 to 3.30.0. - [Release notes](https://github.com/typetools/checker-framework/releases) - [Changelog](https://github.com/typetools/checker-framework/blob/master/docs/CHANGELOG.md) - [Commits](https://github.com/typetools/checker-framework/compare/checker-framework-3.29.0...checker-framework-3.30.0) --- updated-dependencies: - dependency-name: org.checkerframework:checker dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- vector/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/build.gradle b/vector/build.gradle index 3b30919c6d..9ee8836769 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -308,7 +308,7 @@ dependencies { // Fix issue with Jitsi. Inspired from https://github.com/android/android-test/issues/861#issuecomment-872067868 // Error was lots of `Duplicate class org.checkerframework.common.reflection.qual.MethodVal found in modules jetified-checker-3.1 (org.checkerframework:checker:3.1.1) and jetified-checker-qual-3.12.0 (org.checkerframework:checker-qual:3.12.0) //noinspection GradleDependency Cannot use latest 3.15.0 since it required min API 26. - implementation "org.checkerframework:checker:3.29.0" + implementation "org.checkerframework:checker:3.30.0" androidTestImplementation libs.androidx.testCore androidTestImplementation libs.androidx.testRunner From 3d12bb1df96723ea39642d31de6d91207aaf98ca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Feb 2023 23:07:11 +0000 Subject: [PATCH 160/189] Bump org.jetbrains.kotlin:kotlin-gradle-plugin from 1.8.0 to 1.8.10 Bumps [org.jetbrains.kotlin:kotlin-gradle-plugin](https://github.com/JetBrains/kotlin) from 1.8.0 to 1.8.10. - [Release notes](https://github.com/JetBrains/kotlin/releases) - [Changelog](https://github.com/JetBrains/kotlin/blob/v1.8.10/ChangeLog.md) - [Commits](https://github.com/JetBrains/kotlin/compare/v1.8.0...v1.8.10) --- updated-dependencies: - dependency-name: org.jetbrains.kotlin:kotlin-gradle-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index 413199363a..749b842e68 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -8,7 +8,7 @@ ext.versions = [ def gradle = "7.4.1" // Ref: https://kotlinlang.org/releases.html -def kotlin = "1.8.0" +def kotlin = "1.8.10" def kotlinCoroutines = "1.6.4" def dagger = "2.44.2" def firebaseBom = "31.2.0" From 14d742d5040a21d6a927120fadabfe3ab7abf66f Mon Sep 17 00:00:00 2001 From: yostyle Date: Fri, 3 Feb 2023 11:59:37 +0100 Subject: [PATCH 161/189] Not start broadcasting if there is already a live broadcast in the room --- .../usecase/GetVoiceBroadcastStateEventUseCase.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateEventUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateEventUseCase.kt index e821e09119..d1b1c21b57 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateEventUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateEventUseCase.kt @@ -49,14 +49,15 @@ class GetVoiceBroadcastStateEventUseCase @Inject constructor( * 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()) { + val startedEvent = room.getTimelineEvent(voiceBroadcast.voiceBroadcastId)?.root + return if (startedEvent?.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 } + ?: startedEvent?.asVoiceBroadcastEvent() } } } From c139eb55c3f02cc48d4670dc1ac48bb5eaacf7aa Mon Sep 17 00:00:00 2001 From: yostyle Date: Fri, 3 Feb 2023 12:36:05 +0100 Subject: [PATCH 162/189] Add changelog --- changelog.d/8062.bugfix | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 changelog.d/8062.bugfix diff --git a/changelog.d/8062.bugfix b/changelog.d/8062.bugfix new file mode 100644 index 0000000000..af1a370350 --- /dev/null +++ b/changelog.d/8062.bugfix @@ -0,0 +1,2 @@ + [Voice Broadcast] We should not be able to start broadcasting if there is already a live broadcast in the Room + \ No newline at end of file From 8a1f1a37569df5972a6a42ee46836162f009ae5d Mon Sep 17 00:00:00 2001 From: oksya8and8 Date: Sat, 4 Feb 2023 18:29:45 +0000 Subject: [PATCH 163/189] Translated using Weblate (Japanese) Currently translated at 99.7% (2592 of 2598 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/ --- library/ui-strings/src/main/res/values-ja/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values-ja/strings.xml b/library/ui-strings/src/main/res/values-ja/strings.xml index 54a8c8ee0f..24f71a0ca7 100644 --- a/library/ui-strings/src/main/res/values-ja/strings.xml +++ b/library/ui-strings/src/main/res/values-ja/strings.xml @@ -848,7 +848,7 @@ %1$sがこのルヌムのアドレスに%2$sを远加したした。 - %sがこのルヌムのサヌバヌのアクセス制埡リストを倉曎したした。 + %sがこのサヌバヌのACLを倉曎したした。 IPリテラルに䞀臎するサヌバヌは犁止されおいたす。 ・IPリテラルに䞀臎するサヌバヌを蚱可したす。 ・%sに䞀臎するサヌバヌは蚱可されおいたす。 From e1a285180378215d8543b3d65fcbd84478ba9214 Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Sat, 4 Feb 2023 18:29:05 +0000 Subject: [PATCH 164/189] Translated using Weblate (Japanese) Currently translated at 99.7% (2592 of 2598 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/ --- .../src/main/res/values-ja/strings.xml | 208 +++++++++++++----- 1 file changed, 151 insertions(+), 57 deletions(-) diff --git a/library/ui-strings/src/main/res/values-ja/strings.xml b/library/ui-strings/src/main/res/values-ja/strings.xml index 24f71a0ca7..276fb2e234 100644 --- a/library/ui-strings/src/main/res/values-ja/strings.xml +++ b/library/ui-strings/src/main/res/values-ja/strings.xml @@ -16,14 +16,14 @@ %1$sが衚瀺名%2$sを削陀したした %1$sがテヌマを%2$sに倉曎したした %1$sがルヌム名を%2$sに倉曎したした - %sがビデオ通話を開始したした。 - %sが音声通話を開始したした。 + %sがビデオ通話を発信したした。 + %sが音声通話を発信したした。 %sが電話に出たした。 %sが通話を終了したした。 ルヌムぞの招埅 %1$sず%2$s 空のルヌム - %1$sが今埌のルヌム履歎を「%2$s」閲芧可胜に蚭定したした。 + %1$sが今埌のルヌムの履歎を「%2$s」閲芧可胜に蚭定したした。 ルヌムのメンバヌ党員招埅された時点から ルヌムのメンバヌ党員参加した時点から ルヌムのメンバヌ党員 @@ -34,7 +34,7 @@ %1$sが%2$sにルヌムぞの招埅を送りたした %1$sが%2$sの招埅を受け入れたした ** 埩号化できたせん%s ** - 送信者の端末からこのメッセヌゞの鍵が送信されおいたせん。 + 送信者の端末からこのメッセヌゞ甚の鍵が送信されおいたせん。 メッセヌゞを送信できたせん Matrix゚ラヌ メヌルアドレス @@ -72,7 +72,7 @@ 共有 削陀 招埅 - 党おの発蚀を既読にする + 党お既読にする すぐに返信 開く 閉じる @@ -230,8 +230,8 @@ ダむレクトメッセヌゞ ブロック ブロックを解陀 - この参加者の発蚀を党お非衚瀺 - このメンバヌの発蚀を党お衚瀺 + 無芖 + 無芖を解陀 メンション ログアりト 無芖 @@ -302,7 +302,7 @@ サヌバヌの管理者が、これは想定されおいるこずであるず蚀っおいるのであれば、以䞋のフィンガヌプリントが、管理者によるフィンガヌプリントず䞀臎しおいるこずを確認しおください。 蚌明曞はあなたの電話により信頌されおいたものから倉曎されおいたす。これはきわめお異垞な事態です。この新しい蚌明曞を承認しないこずを匷く掚奚したす。 蚌明曞は以前信頌されおいたものから信頌されおいないものぞず倉曎されおいたす。サヌバヌがその蚌明曞を曎新した可胜性がありたす。サヌバヌの管理者に連絡しお、適切なフィンガヌプリントを確認しおください。 - サヌバヌの管理者が䞊のフィンガヌプリントず䞀臎するものを発行した堎合に限り、蚌明曞を承認しおください。 + サヌバヌの管理者が䞊蚘のものず䞀臎するフィンガヌプリントを発行した堎合にのみ、蚌明曞を承認しおください。 怜玢 このアプリの情報をシステム蚭定で衚瀺。 アプリの情報 @@ -352,13 +352,13 @@ メンバヌ - %d名のメンバヌ + %d人のメンバヌ %d件の新しいメッセヌゞ アバタヌ - スタンプを送る + ステッカヌを送る ダりンロヌド システムアラヌト 可胜であれば、英語で説明文を蚘述しおください。 @@ -403,7 +403,7 @@ 衚瀺するニックネヌムを倉曎 Markdown曞匏の入/切 Matrixアプリの管理を修正するには - %1$sのホヌムサヌバヌの䜿甚を継続するには、利甚芏玄を確認し、同意する必芁がありたす。 + %1$sのホヌムサヌバヌを匕き続き䜿甚するには、利甚芏玄を確認しお同意する必芁がありたす。 ゚ラヌ 今すぐ確認 アカりントを停止 @@ -641,7 +641,7 @@ 拡匵蚭定 珟圚の蚀語 他の利甚可胜な蚀語 - メッセヌゞ゚ディタ + メッセヌゞ゚ディタヌ 環境蚭定 この端末で蚭定 セキュアバックアップを再蚭定 @@ -761,7 +761,7 @@ このナヌザヌの招埅をキャンセルしおよろしいですか 招埅をキャンセル このナヌザヌを解陀するず、そのナヌザヌからの党おのメッセヌゞが再び衚瀺されたす。 - ナヌザヌを無芖しない + ナヌザヌの無芖を解陀 このナヌザヌを無芖するず、あなたが共有しおいるルヌムからそのナヌザヌのメッセヌゞが削陀されたす。 \n \nこの操䜜は、蚭定からい぀でも元に戻すこずができたす。 @@ -787,11 +787,11 @@ 非公開 切り替える 远加 - %1$sが゚ンドツヌ゚ンド暗号化認識されおいないアルゎリズム %2$sをオンにしたした。 - ゚ンドツヌ゚ンド暗号化認識されおいないアルゎリズム %1$sをオンにしたした。 + %1$sが゚ンドツヌ゚ンド暗号化認識されおいないアルゎリズム %2$sを有効にしたした。 + ゚ンドツヌ゚ンド暗号化認識されおいないアルゎリズム %1$sを有効にしたした。 䌚話を始める - %1$sが゚ンドツヌ゚ンド暗号化をオンにしたした。 - ゚ンドツヌ゚ンド暗号化をオンにしたした。 + %1$sが゚ンドツヌ゚ンド暗号化を有効にしたした。 + ゚ンドツヌ゚ンド暗号化を有効にしたした。 ゲストがルヌムに参加するのを拒吊したした。 %1$sはゲストがルヌムに参加するのを拒吊したした。 ゲストがルヌムに参加するのを拒吊したした。 @@ -849,19 +849,19 @@ %1$sがこのルヌムのアドレスに%2$sを远加したした。 %sがこのサヌバヌのACLを倉曎したした。 - IPリテラルに䞀臎するサヌバヌは犁止されおいたす。 + ・IPリテラルに䞀臎するサヌバヌはブロックされおいたす。 ・IPリテラルに䞀臎するサヌバヌを蚱可したす。 ・%sに䞀臎するサヌバヌは蚱可されおいたす。 - ・%sに䞀臎するサヌバヌは犁止されおいたす。 + ・%sに䞀臎するサヌバヌはブロックされおいたす。 %sがこのルヌムのサヌバヌアクセス制埡リストを蚭定したした。 %sがここをアップグレヌドしたした。 %sがこのルヌムをアップグレヌドしたした。 今埌のメッセヌゞを「%1$s」閲芧可胜に蚭定したした。 - 今埌のルヌム履歎を「%1$s」閲芧可胜に蚭定したした。 + 今埌のルヌムの履歎を「%1$s」閲芧可胜に蚭定したした。 %1$sが今埌のメッセヌゞを「%2$s」閲芧可胜に蚭定したした。 %sが通話を蚭定するためにデヌタを送信したした。 - 通話を開始したした。 - ビデオ通話を開始したした。 + 音声通話を発信したした。 + ビデオ通話を発信したした。 %1$sをブロックしたした。理由%2$s %1$sが%2$sをブロックしたした。理由%3$s %1$sのブロックを解陀したした。理由%2$s @@ -913,7 +913,7 @@ %1$sの暩限レベルを%2$sから%3$sぞ倉曎したした。 カスタム カスタム (%1$d) - デフォルト + 既定 モデレヌタヌ 管理者 %1$sりィゞェットを倉曎したした @@ -1115,7 +1115,7 @@ ホヌムサヌバヌにバックアップが存圚しおいたす リカバリヌキヌが保存されたした。 リカバリヌキヌを保存 - コピヌをしたした + コピヌしたした リカバリヌキヌはパスワヌドマネヌゞャヌもしくは金庫のような、非垞に安党な堎所で保管しおください リカバリヌキヌはセヌフティヌネットずなりたす。パスフレヌズを忘れた堎合でも、リカバリヌキヌを䜿えば、暗号化されたメッセヌゞにアクセスするこずができたす。 \nリカバリヌキヌは、パスワヌドマネヌゞャヌもしくは金庫のような、非垞に安党な堎所で保管しおください。 @@ -1175,7 +1175,7 @@ 既に䞀芧に茉っおいるサヌバヌです サヌバヌたたはそのルヌム䞀芧が芋぀かりたせん - 探玢したい新しいサヌバヌの名前を入力しおください。 + 探したい新しいサヌバヌの名前を入力しおください。 新しいサヌバヌを远加 あなたのサヌバヌ 暗号化されたメッセヌゞの埩元 @@ -1187,7 +1187,7 @@ スペヌスのメンバヌのみ 誰でもルヌムを発芋し参加できたす 公開 - 招埅された人だけが発芋し参加できたす + 招埅した人のみが怜玢・参加できたす 非公開 䞍明のアクセス蚭定%s 誰でもルヌムにノックができ、メンバヌがその参加を承認たたは拒吊できたす @@ -1490,7 +1490,7 @@ 暗号化されおいたせん 終了 再読み蟌み - セッション䞀芧 + セッション è­Šå‘Š 無芖を解陀 動画。 @@ -1543,7 +1543,7 @@ スペヌスに関する倉曎を行うために必芁な圹割を曎新する暩限がありたせん スペヌスに関する倉曎を行うために必芁な圹割を遞択 スペヌスに関する倉曎を行うために必芁な圹割を衚瀺し曎新したす。 - フィルタヌ + 絞り蟌む スレッド スレッド スペヌスをアップグレヌド @@ -1595,17 +1595,17 @@ スレッド機胜を䜿うず、䌚話のテヌマを維持したり、䌚話を簡単に远跡したりするこずができたす。 あなたの非公開のスペヌス あなたの公開スペヌス - 自分のみ + 自分専甚 スレッド機胜を䜿っお、䌚話をたずめたしょう %sを埅機しおいたす  この端末でスキャン - 認蚌を送信枈 + 認蚌を送信したした このセッションを認蚌 音声 ルヌムのアドレスを入力しおください このアドレスは既に䜿甚されおいたす スペヌスのアドレス - 䞀臎したせん + 䞀臎しおいたせん 䞀臎しおいたす サむンむン サむンアりトしたした @@ -1748,7 +1748,7 @@ %1$d個の投祚 アンケヌトを締め切り、最終結果を衚瀺したす。 - 招埅者のみ参加可胜。個人やチヌムに最適 + 招埅者のみ参加可胜。個人やチヌム向け スペヌスを䜜成 連絡先をスペヌスに招埅 IDサヌバヌには利甚芏玄がありたせん @@ -1777,7 +1777,7 @@ このアンケヌトを削陀しおよろしいですか䞀床削陀するず埩元するこずはできたせん。 共有デヌタの取り扱いに倱敗したした 回転ずクロップ - ルヌムを探玢 + ルヌムを探す 既存のルヌムずスペヌスを远加 スレッドのメッセヌゞを有効にする おすすめに远加 @@ -1826,7 +1826,7 @@ 認蚌枈 未送信のメッセヌゞを削陀 カスタムむベントを送信 - ルヌムの状態を探玢 + ルヌムの状態を調査 開封確認メッセヌゞを衚瀺 通知しない ファむルから鍵をむンポヌト @@ -1840,7 +1840,7 @@ 初めに蚭定画面でIDサヌバヌの利甚芏玄を承認しおください。 初めにIDサヌバヌを蚭定しおください。 - %1$d個の投祚がありたす。結果を芋るには投祚しおください + 合蚈%1$d祚。投祚するず結果を確認できたす 未認蚌の端末で暗号化 メッセヌゞを玙吹雪ず共に送信 @@ -1860,15 +1860,15 @@ 䞀床有効にしたルヌムの暗号化は無効にするこずはできたせん。暗号化されたルヌムで送信されたメッセヌゞは、サヌバヌからは芋るこずができず、そのルヌムのメンバヌだけが芋るこずができたす。暗号化を有効にするず、倚くのボットやブリッゞが正垞に動䜜しなくなる堎合がありたす。 %sしお、このルヌムを皆に玹介したしょう。 このコヌドを共有し、スキャンしお远加しおもらい、䌚話を始めたしょう。 - 正圓な参加者が%sにアクセスできるこずを確認しおください。 - 参加者を远加 + 正しい参加者が%sにアクセスできるようにしたしょう。 + 連絡先を远加 %d人の知り合いがすでに参加しおいたす %sに招埅 ナヌザヌ名かメヌルアドレスで招埅 %sから退出しおよろしいですか - スペヌスは、ルヌムや連絡先をグルヌプ化する新しい方法です。 + スペヌスは、ルヌムや連絡先をたずめる新しい方法です。 招埅されおいたす 新しいスペヌスを、あなたが管理するスペヌスに远加。 泚意アプリケヌションが再起動したす @@ -1882,7 +1882,7 @@ %1$d個の投祚に基づく - %1$d個の投祚に基づく最終結果 + 合蚈%1$d祚の投祚に基づく最終結果 新しいセッションが認蚌されたした。セッションは暗号化されたメッセヌゞにアクセスでき、他のナヌザヌには信頌枈ずしお衚瀺されたす。 このルヌムを同じホヌムサヌバヌ䞊で組織内のチヌムずのコラボレヌションにのみ䜿甚するなら、このオプションを有効にするずいいかもしれたせん。これは埌から倉曎できたせん。 @@ -1992,7 +1992,7 @@ テキストメッセヌゞで共有 保護を蚭定 このルヌムのみ - 誰でも参加可胜。コミュニティヌに最適 + 誰でも参加可胜。コミュニティヌ向け 既存のスペヌスに参加するには、招埅が必芁です。 これは埌から倉曎できたす 倉曎を砎棄 @@ -2131,7 +2131,7 @@ 暗号化を有効な状態に取り戻すために、管理者に連絡しおください。 このナヌザヌずのメッセヌゞぱンドツヌ゚ンドで暗号化されおおり、第䞉者が解読するこずはできたせん。 このコヌドを盞手の画面に珟れおいるコヌドず比范しおください。 - 絵文字を比范しお、同じ順番で珟れおいるのを確認しおください。 + 絵文字を比范しお、同じ順番で珟れおいるこずを確認しおください。 セキュリティヌを高めるために、察面で行うか、他の通信手段を利甚したしょう。 遞択された゚モヌトを虹色にしお送信したす 遞択されたテキストを虹色にしお送信したす @@ -2170,17 +2170,17 @@ %1$sの暩限レベルを倉曎したした。 誰ず䜿いたすか 䜜成するスペヌスの皮類を遞択しおください - 自分ず仲間の非公開のスペヌス - ルヌムを敎理するためのプラむベヌトスペヌス + 自分ずチヌムメむトの非公開のスペヌス + ルヌムを敎理するための非公開のスペヌス ここが䌚話のスタヌト地点です。 ここが%sのスタヌト地点です。 - あず少しです確認を埅機しおいたす  + もう少しです確認を埅機しおいたす  あず少しですもう䞀方のデバむスは同じマヌクを衚瀺しおいたすか %sを埅機しおいたす  このナヌザヌがこのセッションを認蚌するたで、送受信されるメッセヌゞには譊告マヌクが付きたす。手動で認蚌するこずも可胜です。 セッションの取埗に倱敗したした - 誰がチヌムの仲間ですか - %sを探玢できるようになりたす + チヌムの仲間を招埅したしょう + %sを探せるようになりたす 私のスペヌス %1$s %2$s に参加しおください スキップ ルヌムの通知 @@ -2227,7 +2227,7 @@ 最新の${app_name}を他の端末で、${app_name} りェブ版、${app_name} デスクトップ版、${app_name} iOS、${app_name} Android、あるいはクロス眲名に察応した他のMatrixのクラむアントでご䜿甚ください スラむドしお通話を終了 電話番号を怜玢する際に゚ラヌが発生したした - 着信を拒吊したした + 通話を拒吊したした それぞれにルヌムを䜜りたしょう。埌から远加するこずもできたす既にあるルヌムも远加できたす。 このスペヌスを特定できるような特城を蚘入しおください。これはい぀でも倉曎できたす。 目立぀ように特城を蚘入しおください。これはい぀でも倉曎できたす。 @@ -2240,7 +2240,7 @@ ステヌトむベント カスタムのステヌトむベントを送信 ステヌトむベントを送信したした - 続行するには名前を付けおください。 + 続行するには名前を蚭定しおください。 どんな䜜業に取り組みたすか あず%1$d件 @@ -2289,7 +2289,7 @@ IDサヌバヌ %s から切断したすか ダむレクトメッセヌゞを䜜成できたせんでした。招埅したいナヌザヌを確認し、もう䞀床やり盎しおください。 セキュリティヌフレヌズ - 自分ず仲間 + 自分ずチヌムメむト メッセヌゞの皮類がありたせん 絵文字の䞀芧を閉じる 絵文字の䞀芧を開く @@ -2306,7 +2306,7 @@ 操䜜を実行できたせん。ホヌムサヌバヌは最新のバヌゞョンではありたせん。 ビデオ通話が拒吊されたした 音声通話が拒吊されたした - %1$sは着信を拒吊したした + %1$sは通話を拒吊したした このデバむスを認蚌可胜な他の端末が党くない堎合にのみ、続行しおください。 このセッションを信頌枈ずしお認蚌するず、暗号化されたメッセヌゞにアクセスするこずができたす。このアカりントにサむンむンしなかった堎合は、あなたのアカりントのセキュリティヌが砎られおいる可胜性がありたす アカりントのセキュリティヌが砎られおいる可胜性がありたす @@ -2401,12 +2401,12 @@ 進みたしょう ナヌザヌ名 / メヌルアドレス / 電話番号 あなたは人間ですか - %sに送信された手順に埓っおください + %sに送信された手順に埓っおください。 パスワヌドを再蚭定 パスワヌドを忘れた堎合 電子メヌルを再送信 電子メヌルが届いおいたせんか - %sに送信された手順に埓っおください + %sに送信された手順に埓っおください。 メヌルアドレスを認蚌 コヌドを再送信 コヌドが%sに送信されたした @@ -2447,7 +2447,7 @@ 初期同期のリク゚スト %sの子スペヌスを折りたたむ %sの子スペヌスを展開 - ルヌムを探玢 + ルヌムを探す スペヌスを倉曎 ルヌムを䜜成 チャットを開始 @@ -2504,7 +2504,7 @@ 地図を読み蟌めたせん \nこのホヌムサヌバヌは地図が読み蟌むよう蚭定されおいないおそれがありたす。 - スペヌスは、ルヌムや連絡先をグルヌプ化する新しい方法です。右䞋のボタンを䜿うず、既存のルヌムを远加したり新たに䜜成したりできたす。 + スペヌスは、ルヌムや連絡先をたずめる新しい方法です。右䞋のボタンを䜿うず、既存のルヌムを远加したり新たに䜜成したりできたす。 セキュリティヌに関する勧告 その他のセッション セキュリティヌを最倧限に高めるには、䞍明なセッションや利甚しおいないセッションからサむンアりトしおください。 @@ -2530,7 +2530,7 @@ セッション名を蚭定するず、端末をより簡単に認識できるようになりたす。 このセッションでプッシュ通知を受信。 絞り蟌みを解陀 - 絞り蟌み + 絞り蟌む アプリケヌション、端末、アクティビティヌに関する情報。 盎近のアクティビティヌ セッション名 @@ -2706,8 +2706,8 @@ 問い合わせる 自分でサヌバヌを運営したいですか サヌバヌのURL - あなたのサヌバヌのアドレスを入力しおください - あなたのサヌバヌのアドレスを指定しおください。サヌバヌにはあなたの党おのデヌタが保管されたす + ホヌムサヌバヌのアドレスを入力しおください + あなたのホヌムサヌバヌのアドレスを入力しおください。ここにあなたの党おのデヌタがホストされたす サヌバヌを遞択 線集 8文字以䞊にしおください @@ -2760,4 +2760,98 @@ 問題が発生したした。ネットワヌクの接続を確認しお、もう䞀床やり盎しおください。 匕甚 チヌム、友達、組織向けのオヌルむンワンの安党なチャットアプリです。はじめに、チャットを䜜成するか既存のルヌムに参加したしょう。 + 䞀臎しおいたせんか + 接続に倱敗したした + サむンむン枈の端末を確認しおください。以䞋のコヌドが衚瀺されおいるはずです。以䞋のコヌドがサむンむン枈の端末ず䞀臎しおいるこずを確認しおください + サむンむン枈の端末で以䞋のQRコヌドをスキャンしおください + この端末を䜿い、QRコヌドをスキャンしお新しい端末でサむンむンできたす。2぀の方法がありたす + このスペヌス内のもの + 正しい参加者が%sにアクセスできるようにしたしょう。埌から远加で招埅できたす。 + 終了したアンケヌト + アンケヌトを終了したした。 + アンケヌトを䜜成したした。 + ステッカヌを送信したした。 + 動画を送信したした。 + 画像を送信したした。 + 音声メッセヌゞを送信したした。 + 音声ファむルを送信したした。 + ファむルを送信したした。 + むンラむンコヌドの装食を適甚 + 箇条曞きリストの衚瀺を切り替える + 番号付きリストの衚瀺を切り替える + 䞋線で装食 + 打ち消し線で装食 + このコヌドの出所を知っおいるこずを確認しおください。端末をリンクするず、あなたのアカりントに無制限にアクセスできるようになりたす。 + もう䞀床詊しおください + サむンむンしおいたす + この端末でQRコヌドを衚瀺 + 「QRコヌドをスキャン」を遞択しおください + 「QRコヌドでサむンむン」を遞択しおください + 「QRコヌドを衚瀺」を遞択しおください + 蚭定から「セキュリティヌずプラむバシヌ」を開いおください + 他の端末でアプリを開いおください + もう䞀方の端末のサむンむンはキャンセルされたした。 + もう䞀方のデバむスは既にサむンむンしおいたす。 + リク゚ストはもう䞀方の端末で拒吊されたした。 + 時間内にリンクが完了したせんでした。 + この端末ずのリンクはサポヌトしおいたせん。 + サむンアりトした端末で以䞋のQRコヌドをスキャンしおください。 + この端末のカメラを䜿甚しお、他の端末に衚瀺されおいるQRコヌドをスキャンしおください + %s +\nは空です。 + クラむアントの名称、バヌゞョン、URLを蚘録し、セッションマネヌゞャヌでより容易にセッションを認識できるよう蚭定。 + セッション名を倉曎 + 絞り蟌む + 盎近のオンラむン日時 %1$s + + 䜿甚しおいない叀いセッション%1$d日以䞊䜿甚されおいたせんからサむンアりトするこずを怜蚎しおください。 + + 未認蚌のセッションを認蚌するか、サむンアりトしおください。 + 未認蚌・珟圚のセッション + 未認蚌・盎近のオンラむン日時 %1$s + 認蚌枈・盎近のオンラむン日時 %1$s + 珟圚のセッションを認蚌するず、このセッションの認蚌の状態を確認できたす。 + セキュリティヌを最倧限に高めるには、セッションを認蚌し、䞍明なセッションや利甚しおいないセッションからサむンアりトしおください。 + Element Callりィゞェットを自動で承認し、カメラたたはマむクのアクセス暩を付䞎 + Element Callの暩限のショヌトカットを有効にする + 珟圚のゲヌトりェむ%s + ゲヌトりェむ + + %d個の方法が芋぀かりたした。 + + フォトラむブラリヌ + %1$s前に曎新枈 + %1$sたで共有ラむブ + 他のアンケヌトを読み蟌む + + 過去%1$d日に実斜されたアンケヌトはありたせん。 +\nさらにアンケヌトを読み蟌み、前の月のアンケヌトを衚瀺。 + + + 過去%1$d日に実斜䞭のアンケヌトはありたせん。 +\nさらにアンケヌトを読み蟌み、前の月のアンケヌトを衚瀺。 + + 30秒早送り + 30秒巻き戻す + 音声配信を再生たたは再開 + 音声配信の録音を停止 + 音声配信の録音を䞀時停止 + %1$s、%2$s、%3$s + 録音をタップしお停止たたは再生 + 非公開で招埅が必芁なものは衚瀺されおいたせん。 + 䞋曞きを取り消したした + あなたが参加するダむレクトメッセヌゞずルヌムの他のナヌザヌは、あなたのセッションの䞀芧を閲芧できたす。 +\n +\nセッションの䞀芧から、盞手はあなたずやり取りしおいるこずを確かめるこずができたす。なお、あなたがここに入力するセッション名は盞手に察しお衚瀺されたす。 + このセッションは暗号化をサポヌトしおいないため、認蚌できたせん。 +\n +\nこのセッションでは、暗号化が有効になっおいるルヌムに参加するこずができたせん。 +\n +\nセキュリティヌずプラむバシヌ保護の芳点から、暗号化をサポヌトしおいるMatrixのクラむアントの䜿甚を掚奚したす。 + 未認蚌のセッションは、認蚌情報でログむンされおいたすが、クロス認蚌は行われおいないセッションです。 +\n +\nこれらのセッションは、アカりントの䞍正䜿甚を瀺しおいる可胜性があるため、泚意しお確認しおください。 + 認蚌枈のセッションは、パスフレヌズの入力、たたは他の認蚌枈のセッションで本人確認を行ったセッションです。 +\n +\n認蚌枈のセッションには、暗号化されたメッセヌゞを埩号化する際に䜿甚する党おの鍵が備わっおいたす。たた、他のナヌザヌに察しおは、あなたがこのセッションを信頌しおいるこずが衚瀺されたす。 \ No newline at end of file From 222262e75a27b2b346c5f38c815d649a7e66b117 Mon Sep 17 00:00:00 2001 From: Tuomas Hietala Date: Thu, 2 Feb 2023 23:25:28 +0000 Subject: [PATCH 165/189] Translated using Weblate (Finnish) Currently translated at 80.4% (2090 of 2598 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fi/ --- .../src/main/res/values-fi/strings.xml | 64 +++++++++++++++++-- 1 file changed, 57 insertions(+), 7 deletions(-) diff --git a/library/ui-strings/src/main/res/values-fi/strings.xml b/library/ui-strings/src/main/res/values-fi/strings.xml index c1cc5da2c8..66f333845f 100644 --- a/library/ui-strings/src/main/res/values-fi/strings.xml +++ b/library/ui-strings/src/main/res/values-fi/strings.xml @@ -252,7 +252,7 @@ Saapuva videopuhelu Saapuva puhelu Puhelu kÀynnissÀ  - Toinen puoli ei vastannut. + Toinen osapuoli ei vastannut. Huomio ${app_name} tarvitsee kÀyttöluvan mikrofoniin suorittakseen puheluita. ${app_name} tarvitsee kÀyttöluvan kameraan ja mikrofoniin suorittakseen videopuheluita. @@ -887,7 +887,7 @@ Jaat sÀhköpostiosoitteita tai puhelinnumeroita identiteettipalvelimella %1$s. Sinun tÀytyy yhdistÀÀ uudelleen palvelimeen %2$s, jotta voit lopettaa niiden jakamisen. HyvÀksy identiteettipalvelimen (%s) kÀyttöehdot salliaksesi, ettÀ sinut voi löytÀÀ sÀhköpostiosoitteen tai puhelinnumeron perusteella. Yhteyden katkaiseminen identiteettipalvelimeesi tarkoittaa, ettÀ muut kÀyttÀjÀt eivÀt voi etsiÀ sinua etkÀ voi kutsua muita sÀhköpostin tai puhelinnumeron perusteella. - LÀhetimme sinulle vahvistussÀhköpostin osoitteeseen %s, tarkista sÀhköpostisi ja klikkaa vahvistuslinkkiÀ + LÀhetimme sinulle sÀhköpostia osoitteeseen %s. Tarkista sÀhköpostisi ja klikkaa vahvistuslinkkiÀ. Ota yksityiskohtaiset lokit kÀyttöön. YritÀ uudelleen, kun olet hyvÀksynyt kotipalvelimesi kÀyttöehdot. Palvelimen vastaus nÀyttÀisi olevan liian hidas. TÀmÀ voi johtua kehnosta yhteydestÀ tai palvelimella olevasta ongelmasta. YritÀ hetken kuluttua uudelleen. @@ -1026,7 +1026,7 @@ Kirjaudu sisÀÀn palvelimeen %1$s Rekisteröidy Kirjaudu sisÀÀn - Jatka kertakirjautumiseen + Jatka kertakirjautumisella Element Matrix Services in osoite Osoite Korkealuokkaista isÀnnöintiÀ organisaatioille @@ -1097,8 +1097,8 @@ SyöttÀmÀsi koodi ei ole kelvollinen. Tarkista se. Vanhentunut kotipalvelin - Liian monta pyyntöÀ lÀhetettiin. Voit yrittÀÀ uudelleen 1 sekunnissa
 - Liian monta pyyntöÀ lÀhetettiin. Voit yrittÀÀ uudelleen %1$d sekunnissa
 + Liian monta pyyntöÀ lÀhetettiin. Voit yrittÀÀ uudelleen sekunnin kuluttua
 + Liian monta pyyntöÀ lÀhetettiin. Voit yrittÀÀ uudelleen %1$d sekunnin kuluttua
 NÀhneet Olet kirjautunut ulos @@ -2068,7 +2068,7 @@ Onnittelut! Personoi profiili ohittaa tÀmÀn kysymyksen - Ei varmuutta vielÀ\? Voit %s + Etkö ole vielÀ varma\? Voit %s Identiteettipalvelin ei tarjoa kÀytÀntöÀ Piilota identiteettipalvelimen kÀytÀntö NÀytÀ identiteettipalvelimen kÀytÀntö @@ -2307,4 +2307,54 @@ %1$d valittu %1$d valittu - + Puskuroidaan
 + ÄÀniviestiÀ ei voi aloittaa + TÀssÀ huoneessa on kÀytössÀ huoneversio %s, jonka tÀmÀ kotipalvelin on merkinnyt epÀvakaaksi. + ÄlÀ poistu mistÀÀn + Poistu kaikista + Poista profiilikuva + Vaihda profiilikuva + Puhelinnumeron haussa tapahtui virhe + + Kutsut lÀhetetty kÀyttÀjÀlle %1$s ja yhdelle muulle + Kutsut lÀhetetty kÀyttÀjÀlle %1$s ja %2$d muulle + + Kutsu lÀhetetty kÀyttÀjille %1$s ja %2$s + Kutsu lÀhetetty kÀyttÀjÀlle %1$s + Kutsu %s keskusteluun lÀhettÀmÀllÀ ensimmÀinen viesti + TÀstÀ alkaa yksityisviestihistoriasi sinun ja kÀyttÀjÀn %s vÀlillÀ. + %s alkaa tÀstÀ. + Salaus on sÀÀdetty vÀÀrin + Salaus on sÀÀdetty vÀÀrin. + TÀllÀ kotipalvelimella on vanha versio. PyydÀ kotipalvelimesi yllÀpitÀjÀÀ pÀivittÀmÀÀn se. Voit jatkaa, mutta jotkin ominaisuudet eivÀt vÀlttÀmÀttÀ toimi oikein. + Ota yhteyttÀ + Jatka %s-kirjautumisella + tai + Keskustelujesi koti + Keskustelujesi koti + Laitetaan yhteydet kuntoon + Kenen kanssa juttelet eniten\? + ${app_name} toimii mainiosti työpaikallakin. Siihen luottavat maailman turvallisimmat organisaatiot. + Poistutaanko nykyisestÀ ryhmÀpuhelusta ja vaihdetaan toiseen\? + TÀmÀ palvelin on jo luettelossa + TÀtÀ palvelinta tai sen huoneluetteloa ei löydy + Kuka vain voi koputtaa huoneeseen ja jÀsenet voivat sen jÀlkeen hyvÀksyÀ tai hylÀtÀ + Poista osoitteen \"%1$s\" julkaiseminen\? + Huomaa, ettÀ maininnat ja avainsanailmoitukset eivÀt ole kÀytössÀ salausta kÀyttÀvissÀ huoneissa mobiililaitteilla. + Ota suora jako kÀyttöön + Toista aikajanalla olevat animoidut kuvat heti, kun ne nÀkyvÀt + Toista animoidut kuvat automaattisesti + Et saa ilmoituksia maininnoista ja avainsanoista salausta kÀyttÀvissÀ huoneissa mobiililaitteilla. + HuonepÀivitykset + Botin lÀhettÀmÀt viestit + ${app_name} tarvitsee luvan ilmoitusten nÀyttÀmiseen. +\nAnna lupa. + PÀivitÀ huone + Ota lykÀtyt yksityisviestit kÀyttöön + Poista valinta kaikista + Valitse kaikki + Anna mikrofonin kÀyttöoikeus ÀÀniviestien lÀhettÀmiseksi. + Anna kameran kÀyttöoikeus jÀrjestelmÀn asetuksista tÀmÀn toiminnon suorittamiseksi. + TÀmÀn toiminnon suorittaminen vaatii enemmÀn oikeuksia. Anna oikeudet jÀrjestelmÀn asetuksista. + Kuunnellaan ilmoituksia + \ No newline at end of file From 91db521ca934f9ab39b60233ef4ecfa192cff1e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sveinn=20=C3=AD=20Felli?= Date: Thu, 2 Feb 2023 17:35:36 +0000 Subject: [PATCH 166/189] Translated using Weblate (Icelandic) Currently translated at 86.0% (2236 of 2598 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/is/ --- .../src/main/res/values-is/strings.xml | 42 ++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values-is/strings.xml b/library/ui-strings/src/main/res/values-is/strings.xml index ba505bc0a3..8af9da3c9d 100644 --- a/library/ui-strings/src/main/res/values-is/strings.xml +++ b/library/ui-strings/src/main/res/values-is/strings.xml @@ -2382,4 +2382,44 @@ Náði ßví Þú endaðir talútsendingu. %1$s endaði talútsendingu. - + Víxla heilskjásham af/á + Víxla punktalista af/á + Víxla tölusettum lista af/á + Setja tengil + Virkja undirstrikun + Virkja yfirstrikun + Virkja skáletrað snið + Virkja feitletrað snið + Óstaðfest · Núverandi setan ßín + Óstaðfest - Síðasta virkni %1$s + Staðfest - Síðasta virkni %1$s + Núverandi gátt: %s + Finn ekki endapunktinn. + Núverandi endapunktur: %s + Endapunktur + TiltÊkar aðferðir + Ertu viss um að ßú viljir stöðva ßessa beinu útsendingu\? Þetta mun stöðva útsendinguna og full skráning hennar verður tiltÊk á spjallrásinni. + Stöðva beina útsendingu\? + Villa í tengingu - Upptaka í bið + Tekst ekki að spila ßessa talútsendingu. + Get ekki byrjað nÃœja talútsendingu + setja talútsendingu í bið + Spila eða halda áfram með talútsendingu + Stöðva upptöku á talútsendingu + Setja upptöku á talútsendingu í bið + Halda áfram með upptöku á talútsendingu + Nafnlaust lyklaborð + Tilgreindu ástÊðu + Takmörk netßjóns á innsendingum skráa + Takmörk fyrir greiningu + Það eru engar skrár í ßessari spjallrás + Útbúa nÃœtt samtal eða spjallrás + Staðfestingarkóðinn er ekki réttur. + Uppgötvanleg símanúmer + Umsögn um beta-útgáfu spjallßráða + Innifelur breytingar á auðkennismynd og birtingarnafni. + Birta atburði notandaaðgangs + Virkja beina deilingu + Beta-útgáfa spjallßráða + Beta-útgáfa spjallßráða + \ No newline at end of file From d798a83b4eb434ba4921b72fcaa9ce845e2abef7 Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Sat, 4 Feb 2023 18:30:04 +0000 Subject: [PATCH 167/189] Translated using Weblate (Japanese) Currently translated at 99.7% (2592 of 2598 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/ --- library/ui-strings/src/main/res/values-ja/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values-ja/strings.xml b/library/ui-strings/src/main/res/values-ja/strings.xml index 276fb2e234..357edd5708 100644 --- a/library/ui-strings/src/main/res/values-ja/strings.xml +++ b/library/ui-strings/src/main/res/values-ja/strings.xml @@ -848,7 +848,7 @@ %1$sがこのルヌムのアドレスに%2$sを远加したした。 - %sがこのサヌバヌのACLを倉曎したした。 + %sがこのルヌムのサヌバヌのアクセス制埡リストを倉曎したした。 ・IPリテラルに䞀臎するサヌバヌはブロックされおいたす。 ・IPリテラルに䞀臎するサヌバヌを蚱可したす。 ・%sに䞀臎するサヌバヌは蚱可されおいたす。 From 24b6c407ee338315e410a3b4e8bbfe3e5227d2b5 Mon Sep 17 00:00:00 2001 From: phardyle Date: Sat, 4 Feb 2023 00:02:14 +0000 Subject: [PATCH 168/189] Translated using Weblate (Chinese (Simplified)) Currently translated at 98.7% (2566 of 2598 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/zh_Hans/ --- library/ui-strings/src/main/res/values-zh-rCN/strings.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/library/ui-strings/src/main/res/values-zh-rCN/strings.xml b/library/ui-strings/src/main/res/values-zh-rCN/strings.xml index 1e75540acf..f3b5854afb 100644 --- a/library/ui-strings/src/main/res/values-zh-rCN/strings.xml +++ b/library/ui-strings/src/main/res/values-zh-rCN/strings.xml @@ -1803,7 +1803,7 @@ %d 䞪条目 这䞍是有效的 Matrix QR码 - 扫描二绎码 + 扫描QR码 添加人员 邀请朋友 服务噚版本 @@ -2819,4 +2819,5 @@ 无法播攟歀语音广播。 䜠无法启劚语音消息因䞺䜠正圚圕制实时广播。请终止实时广播以匀始圕制语音消息 无法启劚语音消息 - + 结束了投祚。 + \ No newline at end of file From d190ebfbcd9464a19ab6d60a0fcab2e4fe5f3345 Mon Sep 17 00:00:00 2001 From: Joe Sagawa Date: Sat, 4 Feb 2023 18:32:27 +0000 Subject: [PATCH 169/189] Translated using Weblate (Japanese) Currently translated at 99.7% (2592 of 2598 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/ --- library/ui-strings/src/main/res/values-ja/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values-ja/strings.xml b/library/ui-strings/src/main/res/values-ja/strings.xml index 357edd5708..3195249829 100644 --- a/library/ui-strings/src/main/res/values-ja/strings.xml +++ b/library/ui-strings/src/main/res/values-ja/strings.xml @@ -1366,7 +1366,7 @@ %d件の䞍圚着信音声 - デフォルトで䜿いもう尋ねない + 芏定ず蚭定しお次回から確認しない 鍵の共有リク゚ストの履歎を送信 結果がありたせん 自分に電話をかけるこずはできたせん。参加者が招埅を受け入れるたでお埅ちください From a7c1c4381b04057162b333f9bafd6b4cce8c20d3 Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Sat, 4 Feb 2023 18:32:09 +0000 Subject: [PATCH 170/189] Translated using Weblate (Japanese) Currently translated at 99.7% (2592 of 2598 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/ --- library/ui-strings/src/main/res/values-ja/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/ui-strings/src/main/res/values-ja/strings.xml b/library/ui-strings/src/main/res/values-ja/strings.xml index 3195249829..490c95b2bc 100644 --- a/library/ui-strings/src/main/res/values-ja/strings.xml +++ b/library/ui-strings/src/main/res/values-ja/strings.xml @@ -1060,7 +1060,7 @@ ルヌムを远加 %sはあなたを招埅しおいたす このルヌムでグルヌプ通話を開始する暩限がありたせん - オヌディオミヌティングを開始 + 音声通話を開始 安党バックアップを蚭定 鍵のバックアップで管理 鍵のバックアップを䜿甚 @@ -1370,7 +1370,7 @@ 鍵の共有リク゚ストの履歎を送信 結果がありたせん 自分に電話をかけるこずはできたせん。参加者が招埅を受け入れるたでお埅ちください - ミヌティングはJitsiのセキュリティヌずパヌミッションポリシヌを䜿甚したす。䌚議䞭は、珟圚ルヌムにいる党おの人に招埅が衚瀺されたす。 + ミヌティングはJitsiのセキュリティヌずパヌミッションポリシヌを䜿甚したす。ミヌティング䞭は、珟圚ルヌムにいる党おの人に招埅が衚瀺されたす。 暩限がありたせん 音声メッセヌゞを送信するには、マむクの暩限を蚱可しおください。 この操䜜を実行するには、システム蚭定からカメラの暩限を蚱可しおください。 From 4d228a9c18c94a3630139d299cd04a46bd7d4187 Mon Sep 17 00:00:00 2001 From: oksya8and8 Date: Sat, 4 Feb 2023 18:36:51 +0000 Subject: [PATCH 171/189] Translated using Weblate (Japanese) Currently translated at 99.7% (2592 of 2598 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/ --- library/ui-strings/src/main/res/values-ja/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values-ja/strings.xml b/library/ui-strings/src/main/res/values-ja/strings.xml index 490c95b2bc..fc5c62d34b 100644 --- a/library/ui-strings/src/main/res/values-ja/strings.xml +++ b/library/ui-strings/src/main/res/values-ja/strings.xml @@ -2173,7 +2173,7 @@ 自分ずチヌムメむトの非公開のスペヌス ルヌムを敎理するための非公開のスペヌス ここが䌚話のスタヌト地点です。 - ここが%sのスタヌト地点です。 + これは%sの始たりです。 もう少しです確認を埅機しおいたす  あず少しですもう䞀方のデバむスは同じマヌクを衚瀺しおいたすか %sを埅機しおいたす  From a88937b2f96dc1a7d99f82571969110dc7bec63e Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Sat, 4 Feb 2023 18:35:07 +0000 Subject: [PATCH 172/189] Translated using Weblate (Japanese) Currently translated at 99.7% (2592 of 2598 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/ --- library/ui-strings/src/main/res/values-ja/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/ui-strings/src/main/res/values-ja/strings.xml b/library/ui-strings/src/main/res/values-ja/strings.xml index fc5c62d34b..810930a0d8 100644 --- a/library/ui-strings/src/main/res/values-ja/strings.xml +++ b/library/ui-strings/src/main/res/values-ja/strings.xml @@ -1354,8 +1354,8 @@ ダむレクトメッセヌゞ 自分のナヌザヌ名 自分の衚瀺名 - グルヌプチャットでのメッセヌゞの暗号化 - 個別チャットでのメッセヌゞの暗号化 + グルヌプチャットで暗号化されたメッセヌゞ + 1察1のチャットで暗号化されたメッセヌゞ 以䞋がメッセヌゞに含たれる堎合に通知 その他 メンションずキヌワヌド @@ -1366,7 +1366,7 @@ %d件の䞍圚着信音声 - 芏定ず蚭定しお次回から確認しない + 既定に蚭定しお次回から確認しない 鍵の共有リク゚ストの履歎を送信 結果がありたせん 自分に電話をかけるこずはできたせん。参加者が招埅を受け入れるたでお埅ちください From 85bff0e786874fa51d9a0b7921389f41b50f074c Mon Sep 17 00:00:00 2001 From: oksya8and8 Date: Sat, 4 Feb 2023 18:38:44 +0000 Subject: [PATCH 173/189] Translated using Weblate (Japanese) Currently translated at 99.7% (2592 of 2598 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/ --- library/ui-strings/src/main/res/values-ja/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values-ja/strings.xml b/library/ui-strings/src/main/res/values-ja/strings.xml index 810930a0d8..5eaf5d9691 100644 --- a/library/ui-strings/src/main/res/values-ja/strings.xml +++ b/library/ui-strings/src/main/res/values-ja/strings.xml @@ -1236,7 +1236,7 @@ 次に 次に ナヌザヌ名を遞択しおください。 - ナヌザヌ名やパスワヌドが正しくありたせん。 入力したパスワヌドは、スペヌスで開始たたは終了しおいたすので、ご確認ください。 + ナヌザヌ名かパスワヌドが正しくありたせん。入力されたパスワヌドがスペヌスによっお開始しおいるか終了しおいるので、確認しおください。 そのナヌザヌ名は既に䜿甚されおいたす ナヌザヌ名 ナヌザヌ名たたはメヌルアドレス From 033316eec31bd5bedf3fffc03a96d0c184b4723e Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Sat, 4 Feb 2023 18:37:48 +0000 Subject: [PATCH 174/189] Translated using Weblate (Japanese) Currently translated at 99.7% (2592 of 2598 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/ --- library/ui-strings/src/main/res/values-ja/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/ui-strings/src/main/res/values-ja/strings.xml b/library/ui-strings/src/main/res/values-ja/strings.xml index 5eaf5d9691..d37cc518d7 100644 --- a/library/ui-strings/src/main/res/values-ja/strings.xml +++ b/library/ui-strings/src/main/res/values-ja/strings.xml @@ -1008,7 +1008,7 @@ %1$s、%2$s、他%3$d人のナヌザヌが読みたした %1$s、%2$s、%3$sが読みたした - メッセヌゞをマヌクダりンずしお解釈せずにプレヌンテキストずしお送信 + メッセヌゞをマヌクダりンずしお解釈せず、プレヌンテキストずしお送信 ファむルずしお保存 共有 完了 @@ -2173,9 +2173,9 @@ 自分ずチヌムメむトの非公開のスペヌス ルヌムを敎理するための非公開のスペヌス ここが䌚話のスタヌト地点です。 - これは%sの始たりです。 + ここが%sの始たりです。 もう少しです確認を埅機しおいたす  - あず少しですもう䞀方のデバむスは同じマヌクを衚瀺しおいたすか + あず少しですもう䞀方の端末は同じマヌクを衚瀺しおいたすか %sを埅機しおいたす  このナヌザヌがこのセッションを認蚌するたで、送受信されるメッセヌゞには譊告マヌクが付きたす。手動で認蚌するこずも可胜です。 セッションの取埗に倱敗したした From 912c37e3ff5727726bfa03f2494d80add6100047 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 6 Feb 2023 10:36:40 +0100 Subject: [PATCH 175/189] Reset elapsed time on stop action and add a start method to the CountUpTimer --- .../im/vector/lib/attachmentviewer/VideoViewHolder.kt | 2 +- .../im/vector/lib/core/utils/timer/CountUpTimer.kt | 10 +++++++--- .../im/vector/lib/core/utils/timer/CountUpTimerTest.kt | 6 ++---- .../home/room/detail/composer/AudioMessageHelper.kt | 10 +++------- .../detail/composer/voice/VoiceMessageRecorderView.kt | 2 +- .../features/location/live/map/LiveLocationUserItem.kt | 8 +++++--- .../listening/VoiceBroadcastPlayerImpl.kt | 7 ++----- .../recording/VoiceBroadcastRecorderQ.kt | 6 +++--- 8 files changed, 24 insertions(+), 27 deletions(-) diff --git a/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/VideoViewHolder.kt b/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/VideoViewHolder.kt index 30da59750e..64bd31cd8c 100644 --- a/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/VideoViewHolder.kt +++ b/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/VideoViewHolder.kt @@ -110,7 +110,7 @@ class VideoViewHolder constructor(itemView: View) : // Log.v("FOO", "isPlaying $isPlaying $progress/$duration") eventListener?.get()?.onEvent(AttachmentEvents.VideoEvent(isPlaying, progress, duration)) } - it.resume() + it.start() } } try { diff --git a/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/CountUpTimer.kt b/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/CountUpTimer.kt index 435a8603e8..3ed63a407b 100644 --- a/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/CountUpTimer.kt +++ b/library/core-utils/src/main/java/im/vector/lib/core/utils/timer/CountUpTimer.kt @@ -23,18 +23,16 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.launch import java.util.concurrent.atomic.AtomicLong -@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class) class CountUpTimer( private val coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.Main), private val clock: Clock = DefaultClock(), private val intervalInMs: Long = 1_000, - initialTime: Long = 0L, ) { private var counterJob: Job? = null private val lastTime: AtomicLong = AtomicLong(clock.epochMillis()) - private val elapsedTime: AtomicLong = AtomicLong(initialTime) + private val elapsedTime: AtomicLong = AtomicLong(0) private fun startCounter() { counterJob = coroutineScope.launch { @@ -56,6 +54,11 @@ class CountUpTimer( } } + fun start(initialTime: Long = 0L) { + elapsedTime.set(initialTime) + resume() + } + fun pause() { tickListener?.onTick(elapsedTime()) counterJob?.cancel() @@ -71,6 +74,7 @@ class CountUpTimer( tickListener?.onTick(elapsedTime()) counterJob?.cancel() counterJob = null + elapsedTime.set(0L) } fun interface TickListener { diff --git a/library/core-utils/src/test/java/im/vector/lib/core/utils/timer/CountUpTimerTest.kt b/library/core-utils/src/test/java/im/vector/lib/core/utils/timer/CountUpTimerTest.kt index df517751ed..83f11900b1 100644 --- a/library/core-utils/src/test/java/im/vector/lib/core/utils/timer/CountUpTimerTest.kt +++ b/library/core-utils/src/test/java/im/vector/lib/core/utils/timer/CountUpTimerTest.kt @@ -42,10 +42,9 @@ internal class CountUpTimerTest { coroutineScope = this, clock = fakeClock, intervalInMs = AN_INTERVAL, - initialTime = 0, ).also { it.tickListener = tickListener } - timer.resume() + timer.start() advanceTimeBy(AN_INTERVAL / 2) // no tick timer.pause() // tick advanceTimeBy(AN_INTERVAL * 10) // no tick @@ -71,10 +70,9 @@ internal class CountUpTimerTest { coroutineScope = this, clock = fakeClock, intervalInMs = AN_INTERVAL, - initialTime = AN_INITIAL_TIME, ).also { it.tickListener = tickListener } - timer.resume() + timer.start(AN_INITIAL_TIME) advanceTimeBy(AN_INTERVAL) // tick timer.pause() // tick advanceTimeBy(AN_INTERVAL * 10) // no tick diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt index 1929abcc4f..beec925f09 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt @@ -200,7 +200,7 @@ class AudioMessageHelper @Inject constructor( amplitudeTicker?.stop() amplitudeTicker = CountUpTimer(intervalInMs = 50).apply { tickListener = CountUpTimer.TickListener { onAmplitudeTick() } - resume() + start() } } @@ -217,11 +217,7 @@ class AudioMessageHelper @Inject constructor( stopRecordingAmplitudes() } } - - private fun resumeRecordingAmplitudes() { - amplitudeTicker?.resume() - } - + private fun stopRecordingAmplitudes() { amplitudeTicker?.stop() amplitudeTicker = null @@ -231,7 +227,7 @@ class AudioMessageHelper @Inject constructor( playbackTicker?.stop() playbackTicker = CountUpTimer().apply { tickListener = CountUpTimer.TickListener { onPlaybackTick(id) } - resume() + start() } onPlaybackTick(id) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt index 526c774b6d..76656457b9 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt @@ -193,7 +193,7 @@ class VoiceMessageRecorderView @JvmOverloads constructor( val isLocked = startFromLocked || lastKnownState is RecordingUiState.Locked onRecordingTick(isLocked, milliseconds + startMs) } - resume() + start() } onRecordingTick(startFromLocked, milliseconds = startMs) } diff --git a/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationUserItem.kt b/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationUserItem.kt index 6d87ef99f7..fee267a46c 100644 --- a/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationUserItem.kt +++ b/vector/src/main/java/im/vector/app/features/location/live/map/LiveLocationUserItem.kt @@ -79,10 +79,12 @@ abstract class LiveLocationUserItem : VectorEpoxyModel onTick(tick) } + it.start() } } From 43ecb63b178c57ef9dc6dd47af7f9db0da6a637c Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 6 Feb 2023 11:27:16 +0100 Subject: [PATCH 176/189] Remove trailing space --- .../features/home/room/detail/composer/AudioMessageHelper.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt index beec925f09..c55f8ec047 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/AudioMessageHelper.kt @@ -217,7 +217,7 @@ class AudioMessageHelper @Inject constructor( stopRecordingAmplitudes() } } - + private fun stopRecordingAmplitudes() { amplitudeTicker?.stop() amplitudeTicker = null From 54030bdd57c6304bf999f7ea070abcac5e8ebedb Mon Sep 17 00:00:00 2001 From: keda82 Date: Mon, 6 Feb 2023 19:43:34 +0000 Subject: [PATCH 177/189] Translated using Weblate (Swedish) Currently translated at 100.0% (2598 of 2598 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/sv/ --- library/ui-strings/src/main/res/values-sv/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/library/ui-strings/src/main/res/values-sv/strings.xml b/library/ui-strings/src/main/res/values-sv/strings.xml index caf9913299..30d5f377a7 100644 --- a/library/ui-strings/src/main/res/values-sv/strings.xml +++ b/library/ui-strings/src/main/res/values-sv/strings.xml @@ -2919,4 +2919,5 @@ Du kan inte pÃ¥börja ett röstmeddelande eftersom du för nÀrvarande spelar in en röstsÀndning. VÀnligen avsluta din röstsÀndning för att börja spela in ett röstmeddelande Kan inte starta röstsÀndning Startade en röstsÀndning + AnvÀnd inline kodformat \ No newline at end of file From 84010d03f1d02131fec1e39127bd93c5f0f2c0c1 Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Mon, 6 Feb 2023 17:24:18 +0000 Subject: [PATCH 178/189] Translated using Weblate (Japanese) Currently translated at 99.7% (2592 of 2598 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/ --- .../src/main/res/values-ja/strings.xml | 768 +++++++++--------- 1 file changed, 384 insertions(+), 384 deletions(-) diff --git a/library/ui-strings/src/main/res/values-ja/strings.xml b/library/ui-strings/src/main/res/values-ja/strings.xml index d37cc518d7..f51f2ba81a 100644 --- a/library/ui-strings/src/main/res/values-ja/strings.xml +++ b/library/ui-strings/src/main/res/values-ja/strings.xml @@ -3,8 +3,8 @@ %sの招埅 %1$sが%2$sを招埅したした %1$sがあなたを招埅したした - %1$sが参加したした - %1$sが退出したした + %1$sがルヌムに参加したした + %1$sがルヌムから退出したした %1$sが招埅を拒吊したした %1$sが%2$sを远攟したした %1$sが%2$sのブロックを解陀したした @@ -14,7 +14,7 @@ %1$sが衚瀺名を%2$sに蚭定したした %1$sが衚瀺名を%2$sから%3$sに倉曎したした %1$sが衚瀺名%2$sを削陀したした - %1$sがテヌマを%2$sに倉曎したした + %1$sがトピックを%2$sに倉曎したした %1$sがルヌム名を%2$sに倉曎したした %sがビデオ通話を発信したした。 %sが音声通話を発信したした。 @@ -30,7 +30,7 @@ å…šå“¡ アバタヌも倉曎されたした %1$sがルヌム名を削陀したした - %1$sがルヌムの説明を削陀したした + %1$sがルヌムのトピックを削陀したした %1$sが%2$sにルヌムぞの招埅を送りたした %1$sが%2$sの招埅を受け入れたした ** 埩号化できたせん%s ** @@ -58,8 +58,8 @@ %1$sが参加したした ルヌムに参加したした %1$sを招埅したした - ディスカッションを䜜成したした - %1$sがディスカッションを䜜成したした + 䌚話を䜜成したした + %1$sが䌚話を䜜成したした ルヌムを䜜成したした %1$sがルヌムを䜜成したした 招埅 @@ -87,33 +87,33 @@ 䌚話 䞍具合を報告 䞍具合の内容ず状況の説明をお願いしたす。䜕をしたしたか䜕が起こるべきでしたか実際に起こった事象は䜕でしょうか - ここに䞍具合の内容を蚘述 + ここに䞍具合の内容を蚘述しおください スクリヌンショットの画像を送信 クラッシュ時のログを送信 ログを送信 開発者が問題を蚺断するために、このクラむアントのログがバグレポヌトず䞀緒に送信されたす。バグレポヌトは、ログずスクリヌンショットを含めお、公開されるこずはありたせん。䞊蚘の説明文だけを送信したい堎合は、以䞋のチェックを解陀しおください。 あなたは䞍満で端末を振っおいるようです。バグレポヌトの画面を開きたすか 前回アプリケヌションは正垞に停止したせんでした。クラッシュ報告の画面を開きたすか - 䞍具合を報告したした - 䞍具合の報告の送信に倱敗したした (%s) + バグレポヌトを送信したした + バグレポヌトの送信に倱敗したした%s ルヌムに参加 音声通話 ビデオ通話 怜玢 ファむルを送信 - ナヌザヌ名かパスワヌドが正しくありたせん + ナヌザヌ名ずパスワヌドの䞀方あるいは䞡方が正しくありたせん メヌルアドレスの圢匏が正しくありたせん このメヌルアドレスは既に登録されおいたす。 パスワヌドを忘れたしたか 正しいURLを入力しおください 原寞 - 倧き目 - 䞭皋床 - 小さ目 - 通話終了 + 倧 + äž­ + 小 + 通話が終了したした はい いいえ - 続行する + 続行 最新の未読ぞ移動 ルヌムから退出 このルヌムから退出しおよろしいですか @@ -130,7 +130,7 @@ ゜ヌスコヌドを衚瀺 たたは 確認 - 送信䞭 (%s%%) + 送信䞭%s%% 削陀 参加 このルヌムで発蚀する暩限がありたせん。 @@ -145,13 +145,13 @@ グルヌプチャットでのメッセヌゞ ルヌムぞ招埅されたずき 通話ぞの招埅 - 自動発蚀プログラムBotが発蚀した時 + ボットによるメッセヌゞ 端末起動時に開始 - アプリを閉じおいるずきの動䜜 + バックグラりンド同期 同期のリク゚ストを倱敗ずするたでの時間 同期の間隔 - 䞀時保存を消去 - メディアの䞀時保存を消去 + キャッシュを消去 + メディアのキャッシュを消去 メディアファむルを保存 ナヌザヌ蚭定 通知 @@ -160,7 +160,7 @@ 高床な蚭定 暗号 通知察象 - 端末の電話垳 + 端末の連絡先 端末の電話垳の䜿甚を蚱可 電話垳の囜番号 党おのメッセヌゞにタむムスタンプを衚瀺 @@ -168,29 +168,29 @@ ID端末固有番号 公開端末名 公開端末名の曎新 - 最埌のオンラむン日時 + 盎近のオンラむン日時 %1$s @ %2$s 認蚌 ログむン䞭のアカりント 蚀語を遞択 蚀語 むンタヌフェヌス - 電子メヌルを確認しお、本文䞭のURLをクリックしおください。完了したら「続行する」をクリックしおください。 - このメヌルアドレスは既に䜿われおいたす。 - あなたのパスワヌドは曎新されたした + 電子メヌルを確認しお、本文䞭のURLをクリックしおください。完了したら「続行」をクリックしおください。 + このメヌルアドレスは既に䜿甚されおいたす。 + パスワヌドを曎新したした 囜を遞択 3日 1週間 1ヵ月 氞久に - ルヌムの説明 - ルヌムの履歎の可芖範囲 - ルヌムの履歎を読める人は\? + トピック + ルヌムの履歎の衚瀺察象 + 履歎を閲芧できる人は 誰でも メンバヌのみこの蚭定を遞択した時点から メンバヌのみ招埅を送った時点から メンバヌのみ参加した時点から - ブロックされたナヌザヌ + ブロックしたナヌザヌ 高床な蚭定 このルヌムのサヌバヌ内識別ID ラボ @@ -199,14 +199,14 @@ メむンアドレスずしおの蚭定を解陀 セッションID フォントの倧きさ - ずおも小さい - 小さい + 最小 + 小 暙準 - 倧きい - より倧きい - ずおも倧きい - 巚倧 - 発蚀曎新を確認しおいたす + 倧 + 巚倧 + 極倧 + 最倧 + むベントを埅機しおいたす 埩号化された゜ヌスコヌドを衚瀺 名前倉曎 音声通話を開始 @@ -218,7 +218,7 @@ サむンアりト 送信 このホヌムサヌバヌは、あなたがロボットではないこずの確認を求めおいたす - メヌルアドレスの認蚌に倱敗したした電子メヌルのリンクをクリックしたこずを確認しおください + メヌルアドレスの認蚌に倱敗したした。電子メヌル内のリンクを開いたこずを確認しおください 䞍正な圢匏のJSON 有効なJSONを含んでいたせんでした ログむン芁求が倚すぎたす @@ -239,7 +239,7 @@ 結果がありたせん 利甚芏玄 著䜜暩 - 個人情報保護方針 + プラむバシヌポリシヌ ホヌムサヌバヌ IDサヌバヌ この電話番号は既に䜿甚されおいたす。 @@ -248,12 +248,12 @@ 新しいパスワヌド パスワヌドの曎新に倱敗したした %sの党おのメッセヌゞを衚瀺したすか - 倖芳 + テヌマ 公開端末名 ルヌムの゚ンドツヌ゚ンド暗号鍵を゚クスポヌト 認蚌枈 このルヌムに参加しおいたせん。 - このルヌムで暩限がありたせん。 + このルヌムでそれを行う暩限がありたせん。 ルヌム %s は閲芧できたせん。 ナヌザヌ名 ホヌムサヌバヌのURL @@ -261,13 +261,13 @@ Matrixアプリを远加 暩限の数倀は正の敎数で入力しおください。 Matrixの連絡先のみ - 通信先が通話の受取に倱敗したした。 + 盞手が電話に出られたせんでした。 情報 - ${app_name}は、音声通話を実行するためにマむクぞアクセスするための蚱可を必芁ずしおいたす。 - ${app_name}はビデオ通話を行うためにカメラずマむクにアクセスする蚱可を必芁ずしおいたす。 + ${app_name}は、音声通話を実行するためにマむクにアクセスする暩限を必芁ずしおいたす。 + ${app_name}は、ビデオ通話を行うためにカメラずマむクにアクセスする暩限を必芁ずしおいたす。 \n \n通話をするためには、次のポップアップでアクセスを蚱可しおください。 - 発蚀を通報 + コンテンツを報告 写真を撮圱 動画を撮圱 認蚌を開始 @@ -275,7 +275,7 @@ リク゚ストにroom_idがありたせん。 リク゚ストの送信に倱敗したした。 りィゞェットを䜜成できたせん。 - りィゞェットをこのルヌムから削陀しおもよろしいですか + りィゞェットをこのルヌムから削陀しおよろしいですか 䞀臎しおいない堎合は、コミュニケヌションのセキュリティヌが損なわれおいる可胜性がありたす。 このセッションでは、未認蚌のセッションに察しお暗号化されたメッセヌゞを送信しない。 認蚌枈のセッションにのみ暗号化 @@ -289,28 +289,28 @@ 鍵をロヌカルファむルに゚クスポヌト ルヌムの暗号鍵を゚クスポヌト 通話 - 通知あり音量倧 - 通知ありサむレント - 䞍具合の報告 + 通知音量倧 + 通知サむレント + バグレポヌト このナヌザヌにあなたず同じ暩限レベルを䞎えようずしおいたす。この倉曎は取り消せたせん。 \nよろしいですか - 信甚する - 信甚しない + 信頌する + 信頌しない フィンガヌプリント%s リモヌトサヌバヌのIDを認蚌できたせんでした。 - 誰かが䞍圓にあなたの通信を傍受しおいるか、あなたの電話がリモヌトサヌバヌの蚌明曞を信甚しおいない可胜性がありたす。 - サヌバヌの管理者が、これは想定されおいるこずであるず蚀っおいるのであれば、以䞋のフィンガヌプリントが、管理者によるフィンガヌプリントず䞀臎しおいるこずを確認しおください。 + これは、誰かがあなたのトラフィックを傍受しおいるか、あなたの電話機がリモヌトサヌバヌから提䟛された蚌明曞を信頌しおいないこずを意味しおいる可胜性がありたす。 + サヌバヌの管理者が、これは想定されおいるこずであるず述べた堎合は、以䞋のフィンガヌプリントが、管理者によるフィンガヌプリントず䞀臎するこずを確認しおください。 蚌明曞はあなたの電話により信頌されおいたものから倉曎されおいたす。これはきわめお異垞な事態です。この新しい蚌明曞を承認しないこずを匷く掚奚したす。 - 蚌明曞は以前信頌されおいたものから信頌されおいないものぞず倉曎されおいたす。サヌバヌがその蚌明曞を曎新した可胜性がありたす。サヌバヌの管理者に連絡しお、適切なフィンガヌプリントを確認しおください。 + 蚌明曞が以前に信頌されたものから信頌されおいないものに倉曎されたした。サヌバヌが蚌明曞を曎新した可胜性がありたす。サヌバヌの管理者に連絡しお、適切なフィンガヌプリントを確認しおください。 サヌバヌの管理者が䞊蚘のものず䞀臎するフィンガヌプリントを発行した堎合にのみ、蚌明曞を承認しおください。 怜玢 - このアプリの情報をシステム蚭定で衚瀺。 + このアプリケヌションの情報をシステム蚭定で衚瀺。 アプリの情報 自分の衚瀺名を含むメッセヌゞ 自分のナヌザヌ名を含むメッセヌゞ バヌゞョン olmのバヌゞョン - サヌドパヌティヌの䜿甚に関する掲瀺 + 倖郚ラむブラリヌのラむセンス ホヌム画面 逃した通知があるルヌムをピン止め 未読メッセヌゞがあるルヌムをピン止め @@ -320,14 +320,14 @@ 未認蚌 認蚌 他のセッションのナヌザヌ蚭定で、以䞋を比范しお承認しおください - ルヌムのディレクトリを遞択 + ルヌムのディレクトリヌを遞択 サヌバヌ名 %sサヌバヌ䞊の党おのルヌム 党おのロヌカルの%sルヌム 端末のカメラを䜿甚 コマンド゚ラヌ 認識されないコマンド%s - 切 + オフ 音量倧 暗号化されたメッセヌゞ 読み蟌んでいたす  @@ -337,8 +337,8 @@ 党おのメッセヌゞ ホヌム画面にショヌトカットを䜜成 むンラむンURLプレビュヌ - 暗号鍵を芁求しおいる新しいセッション \'%s\' を远加したした。 - 未認蚌のセッション \'%s\' が暗号鍵を芁求しおいたす。 + 暗号鍵を芁求しおいる新しいセッション\'%s\'を远加したした。 + 未認蚌のセッション\'%s\'が暗号鍵を芁求しおいたす。 䜜成 ホヌム ルヌム @@ -358,25 +358,25 @@ %d件の新しいメッセヌゞ アバタヌ - ステッカヌを送る + ステッカヌを送信 ダりンロヌド システムアラヌト - 可胜であれば、英語で説明文を蚘述しおください。 + 可胜であれば、英語で詳现を蚘述しおください。 音声を送信 - スタンプを送信 - 珟圚、有効なステッカヌパックがありたせん。 + ステッカヌを送信 + 珟圚、ステッカヌパックが有効になっおいたせん。 \n \nいく぀か远加したすか - 申し蚳ありたせん、この操䜜を完了するための倖郚アプリが芋぀かりたせん。 - あなたの他のセッションに暗号鍵を再芁求する。 + 申し蚳ありたせん。この操䜜を完了するための倖郚アプリケヌションが芋぀かりたせん。 + あなたの他のセッションに暗号鍵を再芁求。 鍵をこのセッションに送信できるように、メッセヌゞを埩号化できる他の端末で${app_name}を起動しおください。 %d個遞択枈 ナヌザヌをメンションするずき、バむブレヌションで通知 送信の前にメディアをプレビュヌ - アカりントを停止 - 自分のアカりントを停止 + アカりントを無効化 + 自分のアカりントを無効化 分析デヌタを送信 ${app_name}はアプリを改善するため、匿名の分析デヌタを収集したす。 @@ -390,36 +390,36 @@ %d個のりィゞェットが䜿甚䞭 必芁な倉数が芋぀かりたせん。 - 動䜜を衚瀺 + アクションを衚瀺 指定したIDのナヌザヌをブロック 指定したIDのナヌザヌのブロックを解陀 ナヌザヌの暩限レベルを芏定 指定したIDのナヌザヌの管理者暩限を取り消す - 指定したナヌザヌを珟圚のルヌムに招埅 - 指定されたアドレスのルヌムに参加 + 指定したIDのナヌザヌを珟圚のルヌムに招埅 + 指定したアドレスのルヌムに参加 ルヌムから退出 - ルヌムの説明を蚭定 + ルヌムのトピックを蚭定 指定したIDのナヌザヌをこのルヌムから远攟 衚瀺するニックネヌムを倉曎 Markdown曞匏の入/切 Matrixアプリの管理を修正するには %1$sのホヌムサヌバヌを匕き続き䜿甚するには、利甚芏玄を確認しお同意する必芁がありたす。 ゚ラヌ - 今すぐ確認 - アカりントを停止 - この操䜜により、あなたのアカりントは氞久に䜿えなくなりたす。あなたはログむンできなくなり、誰も同じナヌザヌIDを再登録できなくなりたす。アカりントが参加しおいる党おのルヌムから退出し、IDサヌバヌからアカりントの詳现は削陀されたす。 この操䜜は取り消せたせん。 + 確認 + アカりントを無効化 + この操䜜により、あなたのアカりントは氞久に䜿えなくなりたす。ログむンしたり同じナヌザヌIDを再登録したりするこずはできなくなりたす。あなたのアカりントは参加しおいる党おのルヌムから退出し、あなたのIDサヌバヌからアカりントの詳现が削陀されたす。<b>この操䜜は取り消せたせん。</b> \n -\nアカりントを停止しおも、 デフォルトではあなたが送信したメッセヌゞの履歎は消去されたせん。メッセヌゞの履歎の消去を望む堎合は、以䞋のボックスにチェックを入れおください。 +\nアカりントを無効化しおも、<b>デフォルトではあなたが送信したメッセヌゞの履歎は消去されたせん</b>。メッセヌゞの履歎を消去する堎合は、以䞋のボックスにチェックを入れおください。 \n -\nMatrixのメッセヌゞの芋え方は、電子メヌルず同様のものです。メッセヌゞの履歎を消去するず、あなたが送信したメッセヌゞは、新芏たたは未登録のナヌザヌに共有されるこずはありたせんが、既にメッセヌゞを取埗しおいる登録ナヌザヌは、今埌もそのコピヌにアクセスできたす。 - アカりントを停止するずきに、自分の送信した党おのメッセヌゞの履歎を消去しおください譊告: この操䜜により、今埌のナヌザヌは䌚話を䞍完党な圢で芋るこずになりたす - アカりントを停止 +\nMatrixのメッセヌゞの芋え方は、電子メヌルず同様のものです。メッセヌゞの履歎を消去するず、あなたがこれたで送信したメッセヌゞは、新芏たたは未登録のナヌザヌに共有されるこずはありたせんが、既にメッセヌゞを取埗しおいる登録ナヌザヌは、今埌もそのコピヌにアクセスできたす。 + アカりントを無効化する際、党おの送信枈のメッセヌゞを消去譊告今埌のナヌザヌには、䞍完党な䌚話が衚瀺されたす + アカりントを無効化 パスワヌドを入力しおください。 このルヌムは眮き換えられおおり、アクティブではありたせん。 こちらから継続䞭の䌚話を確認 このルヌムは別の䌚話の続きです 以前のメッセヌゞを衚瀺するには、ここをクリックしおください - サヌビス管理者に連絡しおください + サヌビス管理者に連絡 このホヌムサヌバヌはリ゜ヌス制限の1぀を超過しおいるため、 ナヌザヌがログむンできなくなるこずがありたす。 このホヌムサヌバヌはリ゜ヌスの䞊限に達したした。 このホヌムサヌバヌは月間アクティブナヌザヌの䞊限に達しおいるため、 ナヌザヌがログむンできなくなるこずがありたす。 @@ -434,12 +434,12 @@ %d+ 展開 折りたたむ - 承諟 - このホヌムサヌバヌの方針を確認し承諟しおください + 同意 + このホヌムサヌバヌの運営方針を確認し、同意しおください 通話蚭定画面 着信に${app_name}の既定の着信音を䜿甚 着信音 - 着信音を遞んでください: + 着信音を遞んでください 䌚話から远攟 鍵のバックアップ 鍵のバックアップを䜿甚 @@ -449,7 +449,7 @@ リアルタむム性を重芖しお最適化 バックグラりンド同期を行わない 入力䞭通知を送信 - 文字入力䞭であるこずを他のメンバヌに䌝えたす。 + 文字入力䞭であるこずを他のメンバヌに衚瀺。 開封確認メッセヌゞを衚瀺 開封確認メッセヌゞをクリックするず、詳现な䞀芧を確認できたす。 Enterキヌでメッセヌゞを送信 @@ -467,7 +467,7 @@ 暗号化されたメッセヌゞにアクセスできなくなるこずを防ぐため、鍵の安党なバックアップはあなたのセッション党おで有効化しおください。 暗号化されたメッセヌゞは䞍芁です 鍵をバックアップしおいたす  - 続行したすか + よろしいですか バックアップ サむンアりトする前に鍵をバックアップしないず、暗号化されたメッセヌゞにアクセスできなくなりたす。 暗号鍵の管理 @@ -481,11 +481,11 @@ バヌゞョン アルゎリズム 眲名 - 通知に関する問題の解決 - システム蚭定。 + 通知に関する問題解決 + システムの蚭定。 アカりントの蚭定。 カスタム蚭定。 - 起動時に実行 + 端末起動時に開始 バックグラりンド制限の確認 線集 返信 @@ -501,14 +501,14 @@ 䜜成 名前 公開 - 誰でもこのルヌムに参加できるようになりたす + 誰でもこのルヌムに参加できたす 䞀般 セキュリティヌずプラむバシヌ ヘルプず抂芁 ダむレクトメッセヌゞ 線集枈 - 䌚話を怜玢  - 党おのメッセヌゞ (音量倧) + 䌚話を絞り蟌む  + 党おのメッセヌゞ音量倧 党おのメッセヌゞ メンションのみ ミュヌト @@ -525,7 +525,7 @@ いったん有効にするず、暗号化を無効にするこずはできたせん。 セキュリティヌ 詳现を衚瀺 - その他の蚭定 + その他 管理者ずしおの操䜜 ルヌムの蚭定 通知 @@ -534,16 +534,16 @@ アップロヌド ルヌムから退出 - ルヌムから退宀しおいたす  + ルヌムから退出しおいたす  管理者 モデレヌタヌ カスタム - 招埅者 + 招埅䞭 ナヌザヌ %1$sの管理者 %1$sのモデレヌタヌ - %1$sのデフォルトナヌザヌ - %2$sのカスタム (%1$d) + %1$sの既定のナヌザヌ + %2$sのカスタム%1$d タむムラむン ゚ンドツヌ゚ンド暗号化を有効にする  暗号化を有効にする @@ -551,19 +551,19 @@ クロス眲名は有効です \n秘密鍵は端末内にありたす。 クロス眲名は有効です -\n鍵は信頌されおいたす +\n鍵は信頌されおいたす。 \n秘密鍵は䞍明です - クロス眲名は有効です + クロス眲名は有効です。 \n鍵は信頌されおいたせん クロス眲名は無効です - 有効なセッション + 䜿甚䞭のセッション 党おのセッションを衚瀺 セッションを管理 このセッションからサむンアりト %d件のアクティブなセッション - このログむンを認蚌 + この端末を認蚌 QRコヌド はい いいえ @@ -572,11 +572,11 @@ アカりントデヌタ 削陀  削陀の確認 - このむベントを削陀しおよろしいですかルヌム名や説明の倉曎を削陀するず、倉曎が取り消されたすのでご泚意ください。 - 暗号化は有効です - このルヌム内でのメッセヌゞぱンドツヌ゚ンド暗号化されたす。詳现の確認や認蚌はナヌザヌのプロフィヌルをご確認ください。 + このむベントを削陀しおよろしいですかルヌム名やトピックの倉曎を削陀するず、倉曎が取り消されたす。 + 暗号化が有効です + このルヌム内でのメッセヌゞぱンドツヌ゚ンドで暗号化されたす。詳现の確認や認蚌はナヌザヌのプロフィヌルをご確認ください。 暗号化が有効になっおいたせん - 通知蚭定 + 通知の蚭定 切断 サむンアりトしおよろしいですか 既読にする @@ -599,10 +599,10 @@ カメラ ギャラリヌ ステッカヌ - スパムメッセヌゞです + スパムです 䞍適切なメッセヌゞです その他の報告  - コンテンツを報告 + このコンテンツを報告 このコンテンツを報告する理由 報告 ナヌザヌを無芖 @@ -610,9 +610,9 @@ 元の倧きさのたた画像を送信 - 自分に電話をかけるこずはできたせん + 自分に電話を発信するこずはできたせん マヌクダりン曞匏 - メッセヌゞ送信前にマヌクダりン曞匏を適甚したす。これにより、アスタリスクを䜿甚しお斜䜓のテキストを衚瀺するなどの高床な曞匏蚭定が利甚できたす。 + メッセヌゞ送信前にマヌクダりン曞匏を適甚したす。アスタリスクを䜿甚しお斜字䜓のテキストを衚瀺するなどの高床な曞匏蚭定が利甚できたす。 音声ずビデオ 囜際電話番号圢匏で入力しおください電話番号の最初に「+」を付けおください メヌルアドレス @@ -621,24 +621,24 @@ 電話番号 あなたのMatrixアカりントに登録されたメヌルアドレスず電話番号を管理 メヌルアドレスず電話番号 - 有効化 - このセッションで通知が無効化されおいたす。 -\n${app_name} の蚭定をご確認ください。 - このセッションで通知は有効化されおいたす。 + 有効にする + このセッションで通知が無効になっおいたす。 +\n${app_name}の蚭定をご確認ください。 + このセッションで通知は有効になっおいたす。 セッションの蚭定。 - 有効化 - あなたのアカりントで通知が無効化されおいたす。 + 有効にする + あなたのアカりントで通知が無効になっおいたす。 \nアカりント蚭定をご確認ください。 - あなたのアカりントで通知は有効化されおいたす。 + あなたのアカりントで通知は有効になっおいたす。 蚭定を開く - システム蚭定で通知が無効化されおいたす。 -\nシステム蚭定をご確認ください。 - システム蚭定で通知は有効化されおいたす。 + システム蚭定で通知が無効になっおいたす。 +\nシステム蚭定を確認しおください。 + システム蚭定で通知は有効になっおいたす。 バッテリヌ最適化 %d秒 - 拡匵蚭定 + 高床な蚭定 珟圚の蚀語 他の利甚可胜な蚀語 メッセヌゞ゚ディタヌ @@ -650,8 +650,8 @@ ルヌムを䜜成しおいたす  招埅されおいたす %sからの招埅 - 抂ね完了したした。%sの画面にも同じシヌルドアむコンが衚瀺されおいたすか - 盞手ナヌザヌの端末のコヌドをスキャンし、盞互に安党性を認蚌 + 抂ね完了したした。%sにも同じマヌクが衚瀺されおいたすか + 盞手のナヌザヌの端末のコヌドをスキャンし、安党に盞互を認蚌 盞手のコヌドをスキャン スキャンできたせん 拒吊 @@ -660,7 +660,7 @@ 意図しない通話を防止 SSL゚ラヌ。 SSL゚ラヌ盞手のIDが認蚌されおいたせん。 - このURLからホヌムサヌバヌに接続できたせんでした、ご確認ください + このURLからホヌムサヌバヌに接続できたせんでした。URLを確認しおください 有効なMatrixサヌバヌのアドレスではありたせん この電話番号は既に登録されおいたす。 シングルサむンオンを䜿甚しおサむンむン @@ -675,12 +675,12 @@ 電話 サりンドデバむスを遞択 リアルタむム接続を確立できたせんでした。 -\nホヌムサヌバヌの管理者に、通話が正垞に動䜜するためにTURNを蚭定するようご連絡ください。 +\n安定した通話のために、ホヌムサヌバヌの管理者にTURNサヌバヌの蚭定を䟝頌しおください。 電話を切る 拒吊 - 承諟 - りィゞェットを削陀できたせんでした - りィゞェットを远加できたせんでした + 同意 + りィゞェットの削陀に倱敗したした + りィゞェットの远加に倱敗したした ビデオ通話を開始 通話を開始する暩限がありたせん このルヌムで通話を開始する暩限がありたせん @@ -689,10 +689,10 @@ なし トピック ルヌム名 - このルヌム内のメッセヌゞぱンドツヌ゚ンドで暗号化されおいたせん。 + このルヌムのメッセヌゞぱンドツヌ゚ンドで暗号化されおいたせん。 ここでのメッセヌゞぱンドツヌ゚ンドで暗号化されおいたせん。 蚭定 - あなたにはこのルヌムの暗号化を有効にする暩限がありたせん。 + このルヌムの暗号化を有効にする暩限がありたせん。 未読メッセヌゞ タむムラむンでのスワむプによる返信を有効にする タむムラむンで非衚瀺のむベントを衚瀺 @@ -708,7 +708,7 @@ むンテグレヌションが無効になっおいたす むンテグレヌションマネヌゞャヌ むンテグレヌションを蚱可 - FCMトヌクンが正垞に取埗されたした: + FCMトヌクンを正垞に取埗したした \n%1$s Firebaseトヌクン Playサヌビスを修正 @@ -719,17 +719,17 @@ 1぀以䞊のテストが倱敗したした。調査甚のバグレポヌトを送信しおください。 1぀以䞊のテストが倱敗したした。提案された修正を詊しおください。 基本的な蚺断はOKです。 それでも通知が届かない堎合は、調査甚のバグレポヌトを送信しおください。 - 実行しおいたす %1$dの%2$d + 実行しおいたす %2$d個のうち%1$d個目のテスト テストを実行 - 蚺断トラブルシュヌティング + 問題解決に関する調査結果 むベントごずの通知の優先順䜍 - メヌルであなたに送ったリンクをクリックしお確認しおください。 + メヌルで送信したリンクをクリックしたこずを確認しおください。 %sを削陀したすか ブロックされたナヌザヌを絞り蟌む - トピックを倉曎 - ルヌムをアップグレヌド - m.room.server_acl eventsを送信 - 暩限を倉曎 + トピックの倉曎 + ルヌムのアップグレヌド + m.room.server_acl eventsの送信 + 暩限の倉曎 ルヌム名の倉曎 履歎の芋え方の倉曎 ルヌムの暗号化の有効化 @@ -748,7 +748,7 @@ ルヌムに関する倉曎を行うために必芁な圹割を遞択 ルヌムの暩限 暩限 - ルヌムに関する倉曎を行うために必芁な圹割を衚瀺し曎新したす。 + ルヌムに関する倉曎を行うために必芁な圹割を衚瀺し曎新。 ブロックを解陀するず、ナヌザヌはルヌムに再び参加できるようになりたす。 ナヌザヌをブロック ブロックする理由 @@ -760,48 +760,48 @@ ナヌザヌを远攟 このナヌザヌの招埅をキャンセルしおよろしいですか 招埅をキャンセル - このナヌザヌを解陀するず、そのナヌザヌからの党おのメッセヌゞが再び衚瀺されたす。 + このナヌザヌの無芖を解陀するず、そのナヌザヌからの党おのメッセヌゞが再び衚瀺されたす。 ナヌザヌの無芖を解陀 このナヌザヌを無芖するず、あなたが共有しおいるルヌムからそのナヌザヌのメッセヌゞが削陀されたす。 \n \nこの操䜜は、蚭定からい぀でも元に戻すこずができたす。 ナヌザヌを無芖 降栌 - あなたは自分自身を降栌させようずしおいたす。この倉曎は取り消せたせん。あなたがルヌムの䞭で最埌の特暩ナヌザヌである堎合、特暩を再取埗するこずはできたせん。 + あなたは自分自身を降栌させようずしおいたす。この倉曎は取り消せたせん。あなたがルヌムの䞭で最埌の特暩ナヌザヌである堎合、特暩を再取埗するこずはできなくなりたす。 降栌したすか 招埅をキャンセル - このルヌムは公開されおいたせん。 招埅がなければ再び参加するこずはできたせん。 + このルヌムは公開されおいたせん。再床参加するには、招埅が必芁です。 連絡先ぞのアクセスを蚱可したす。 QRコヌドをスキャンするには、カメラぞのアクセスを蚱可する必芁がありたす。 通話を保留したした %sが通話を保留したした 保留 - 通話をやり盎す + 通話を再開 ビデオ通話が行われおいたす  - 有効な認蚌情報がないため、暩限がありたせん - ${app_name} 呌び出し倱敗 - ルヌムディレクトリの党おのルヌムを衚瀺露骚なコンテンツのあるルヌムを含むする。 + 有効な認蚌情報がないため、蚱可されおいたせん + ${app_name}が呌び出しに倱敗したした + ルヌムディレクトリヌの党おのルヌムを衚瀺露骚なコンテンツのあるルヌムを含む。 露骚なコンテンツのあるルヌムを衚瀺 - ルヌムディレクトリ + ルヌムディレクトリヌ 新着情報 - 非公開 + 非公開にする 切り替える 远加 %1$sが゚ンドツヌ゚ンド暗号化認識されおいないアルゎリズム %2$sを有効にしたした。 ゚ンドツヌ゚ンド暗号化認識されおいないアルゎリズム %1$sを有効にしたした。 - 䌚話を始める + 䌚話を開始 %1$sが゚ンドツヌ゚ンド暗号化を有効にしたした。 ゚ンドツヌ゚ンド暗号化を有効にしたした。 - ゲストがルヌムに参加するのを拒吊したした。 - %1$sはゲストがルヌムに参加するのを拒吊したした。 - ゲストがルヌムに参加するのを拒吊したした。 - %1$sはゲストがルヌムに参加するのを拒吊したした。 + ゲストがルヌムに参加するこずを拒吊したした。 + %1$sはゲストがルヌムに参加するこずを拒吊したした。 + ゲストがルヌムに参加するこずを拒吊したした。 + %1$sはゲストがルヌムに参加するこずを拒吊したした。 ここにゲストが参加するこずを蚱可したした。 %1$sはここにゲストが参加するこずを蚱可したした。 - ゲストがルヌムに参加するのを蚱可したした。 - %1$sはゲストがルヌムに参加するのを蚱可したした。 - システムデフォルト - このルヌムのメむンおよび代替のアドレスを倉曎したした。 + ゲストがルヌムに参加するこずを蚱可したした。 + %1$sはゲストがルヌムに参加するこずを蚱可したした。 + システムの既定 + このルヌムのメむンおよび代替アドレスを倉曎したした。 このルヌムの代替アドレスを倉曎したした。 このルヌムの代替アドレス %1$s を削陀したした。 @@ -850,7 +850,7 @@ %sがこのルヌムのサヌバヌのアクセス制埡リストを倉曎したした。 ・IPリテラルに䞀臎するサヌバヌはブロックされおいたす。 - ・IPリテラルに䞀臎するサヌバヌを蚱可したす。 + ・IPリテラルに䞀臎するサヌバヌを蚱可されおいたす。 ・%sに䞀臎するサヌバヌは蚱可されおいたす。 ・%sに䞀臎するサヌバヌはブロックされおいたす。 %sがこのルヌムのサヌバヌアクセス制埡リストを蚭定したした。 @@ -881,8 +881,8 @@ \n䌚話を読み蟌んでいたす \n倚くのルヌムに参加しおいる堎合、読み蟌みに時間がかかるかもしれたせん %1$sがこのルヌムから退出したした。理由%2$s - このルヌムに参加したした。理由%1$s - %1$sがこのルヌムに参加したした。理由%2$s + 参加したした。理由%1$s + %1$sが参加したした。理由%2$s このルヌムに参加したした。理由%1$s %1$sがこのルヌムに参加したした。理由%2$s %1$sがあなたを招埅したした。 理由%2$s @@ -890,8 +890,8 @@ %1$sが%2$sを招埅したした。 理由%3$s あなたの招埅です。理由%1$s %1$sの招埅です。理由%2$s - メッセヌゞを送っおいたす  - メッセヌゞを送りたした + メッセヌゞを送信しおいたす  + メッセヌゞを送信したした 初期同期 \nアカりントデヌタをむンポヌトしおいたす 初期同期 @@ -910,7 +910,7 @@ %1$s、%2$s、%3$sず%4$s %1$s、%2$sず%3$s - %1$sの暩限レベルを%2$sから%3$sぞ倉曎したした。 + %1$sの暩限レベルを%2$sから%3$sぞ カスタム カスタム (%1$d) 既定 @@ -933,12 +933,12 @@ %1$sにルヌムぞの招埅を送りたした ルヌムのアバタヌを削陀したした %1$sがルヌムのアバタヌを削陀したした - ルヌムの説明を削陀したした + ルヌムのトピックを削陀したした ルヌム名を削陀したした - ディスカバリヌ蚭定を管理したす。 + ディスカバリヌの蚭定を管理。 ディスカバリヌ発芋 これにより、珟圚のキヌたたはフレヌズが眮き換えられたす。 - 新しいセキュリティヌキヌを生成するか、既存のバックアップに新しいセキュリティヌフレヌズを蚭定したす。 + 新しいセキュリティヌキヌを生成するか、既存のバックアップに新しいセキュリティヌフレヌズを蚭定しおください。 サヌバヌ䞊の暗号鍵をバックアップしお、暗号化されたメッセヌゞずデヌタぞのアクセスが倱われるのを防ぎたしょう。 メッセヌゞ䜜成画面に絵文字キヌボヌドを開くためのボタンを远加 絵文字キヌボヌドを衚瀺 @@ -946,17 +946,17 @@ アカりントのむベントを衚瀺 招埅、远攟、ブロックは圱響を受けたせん。 参加・退出むベントを衚瀺 - /confettiコマンドを䜿甚するか、❄たたは🎉を含むメッセヌゞを送信 + /confettiコマンドを䜿甚するず、❄たたは🎉を含むメッセヌゞを送信 チャットで゚フェクトを衚瀺 ホヌムサヌバヌがこの機胜をサポヌトしおいる堎合は、チャット内のリンクをプレビュヌしたす。 - ボット、ブリッゞ、りィゞェット、ステッカヌパックを管理したす。 -\nむンテグレヌションマネヌゞャヌは、構成デヌタを受信し、ナヌザヌに代わっおりィゞェットの倉曎や、ルヌム招埅の送信、暩限の蚭定などを行うこずができたす。 + むンテグレヌションマネヌゞャヌを䜿甚するず、ボット、ブリッゞ、りィゞェット、ステッカヌパックを管理できたす。 +\n蚭定デヌタを受信し、ナヌザヌに代わっおりィゞェットの倉曎、ルヌムぞの招埅の送信、暩限レベルの蚭定を行うこずができたす。 むンテグレヌション統合 - アプリがバックグラりンドにある堎合、着信メッセヌゞは通知されたせん。 - ${app_name}は正確な時間に定期的にバックグラりンドで同期したす構成可胜。 -\nこれは無線ずバッテリヌの䜿甚量に圱響し、${app_name}がむベントを埅機しおいるこずを瀺す氞続的な通知が衚瀺されたす。 - ${app_name}は、端末の限られたリ゜ヌスバッテリヌの残量を維持する方法でバックグラりンド同期をしたす。 -\n端末の状態によっおは、OSによっお同期が延期される堎合がありたす。 + アプリがバックグラりンドにある堎合、受信するメッセヌゞは通知されたせん。 + ${app_name}は、正確な時間蚭定可胜に定期的にバックグラりンドで同期したす。 +\nこれは無線ずバッテリヌの䜿甚量に圱響したす。たた、${app_name}がむベントを埅機しおいるこずを瀺す氞続的な通知が衚瀺されたす。 + ${app_name}は、端末の限られたリ゜ヌスバッテリヌの残量を維持する方法でバックグラりンド同期を行いたす。 +\nバッテリヌの状態によっおは、OSによっお同期が延期される堎合がありたす。 LEDの色、振動、音を遞択しおください  通知サむレントを蚭定 通話の通知を蚭定 @@ -973,17 +973,17 @@ \nこの゚ラヌは${app_name}の管理倖です。 これはいく぀かの理由で発生する可胜性がありたす。 埌で再詊行するずうたくいくかもしれたせん。システム蚭定でGoogle Playサヌビスのデヌタ䜿甚量が制限されおいないか、端末の時刻が正しいかどうかを確認しおください。カスタムROMで生じるこずもありたす。 ${app_name}はバッテリヌ最適化の圱響を受けたせん。 制限を無効にする - 起動時の開始を有効にする + 端末起動時の開始を有効にする 端末を再起動するずサヌビスが開始したす。 通知がクリックされたした 通知をクリックしおください。 通知が衚瀺されない堎合は、システム蚭定を確認しおください。 - 通知を衚瀺 + 通知の衚瀺 通知を衚瀺しおいたす。 クリックしおください プッシュ通知の受信に倱敗したした。 アプリケヌションを再むンストヌルするず解決するかもしれたせん。 アプリケヌションはプッシュ通知を受信しおいたす アプリケヌションはプッシュ通知を埅機しおいたす プッシュ通知のテスト - FCMトヌクンのホヌムサヌバヌぞの登録に倱敗したした: + FCMトヌクンのホヌムサヌバヌぞの登録に倱敗したした \n%1$s FCMトヌクンがホヌムサヌバヌに登録されたした。 トヌクンの登録 @@ -994,20 +994,20 @@ \nこの゚ラヌは${app_name}の管理倖です。Googleによるず、この゚ラヌは、FCMに登録されおいる端末䞊のアプリの数が倚すぎるこずを瀺唆しおいたす。 この゚ラヌは、アプリの数が極端に倚い堎合にのみ発生するため、平均的なナヌザヌには圱響したせん。 ${app_name}はGoogle Playサヌビスを䜿甚しおプッシュメッセヌゞを配信しおいたすが、正しく蚭定されおいないようです: \n%1$s - FCMトヌクンの取埗に倱敗したした: + FCMトヌクンの取埗に倱敗したした \n%1$s 🎉党おのサヌバヌの参加がブロックされおいたすこのルヌムは䜿甚できなくなりたした。 倉曎はありたせん。 - • サヌバヌにマッチするIPリテラルが犁止されおいたす。 - • サヌバヌにマッチするIPリテラルが蚱可されるようになりたした。 + • IPリテラルに䞀臎するサヌバヌが犁止されるようになりたした。 + • IPリテラルに䞀臎するサヌバヌが蚱可されるようになりたした。 • %sに䞀臎するサヌバヌが蚱可リストから削陀されたした。 • %sに䞀臎するサヌバヌが蚱可されるようになりたした。 - • %sに䞀臎するサヌバヌが犁止リストから削陀されたした。 - • %sに䞀臎するサヌバヌは犁止されおいたす。 + • %sに䞀臎するサヌバヌがブロックリストから削陀されたした。 + • %sに䞀臎するサヌバヌはブロックされおいたす。 - %1$s、%2$s、他%3$d人のナヌザヌが読みたした + %1$s、%2$s、他%3$d人のナヌザヌが閲芧枈 - %1$s、%2$s、%3$sが読みたした + %1$s、%2$s、%3$sが閲芧枈 メッセヌゞをマヌクダりンずしお解釈せず、プレヌンテキストずしお送信 ファむルずしお保存 共有 @@ -1023,7 +1023,7 @@ ナヌザヌ名を入力しおください。 無芖 共有 - 続行するには利甚芏玄を承認する必芁がありたす。 + 続行するには、このサヌビスの利甚芏玄に同意する必芁がありたす。 党おブロック 蚱可 ルヌムID @@ -1041,7 +1041,7 @@ 新しいむベント 䞍明なIP - %2$d個の鍵のうち%1$d個のむンポヌトに成功。 + %2$d個の鍵のうち%1$d個をむンポヌトしたした。 鍵のバックアップを管理 鍵の゚クスポヌトに成功したした @@ -1061,7 +1061,7 @@ %sはあなたを招埅しおいたす このルヌムでグルヌプ通話を開始する暩限がありたせん 音声通話を開始 - 安党バックアップを蚭定 + セキュアバックアップを蚭定 鍵のバックアップで管理 鍵のバックアップを䜿甚 暗号化されたメッセヌゞずデヌタぞのアクセスが倱われるのを防ぎたしょう @@ -1070,9 +1070,9 @@ バックアップの状態を確認しおいたす バックアップを削陀しおいたす  このセッションで鍵のバックアップを䜿甚するには、パスフレヌズたたはリカバリヌキヌでバックアップを埩元しおください。 - バックアップには未認蚌のセッション %s による䞍正な眲名がありたす - バックアップには認蚌枈のセッション %s による䞍正な眲名がありたす - バックアップには未認蚌のセッション %s による有効な眲名がありたす + バックアップには未認蚌のセッション %s による䞍正な眲名がありたす。 + バックアップには認蚌枈のセッション %s による䞍正な眲名がありたす。 + バックアップには未認蚌のセッション %s による有効な眲名がありたす。 バックアップには認蚌枈のセッション %s による眲名がありたす。 バックアップにはこのセッションによる有効な眲名がありたす。 バックアップには%sずいうIDの䞍明のセッションによる眲名がありたす。 @@ -1103,12 +1103,12 @@ リカバリヌキヌを入力 リカバリヌキヌを䜿甚 ログアりトしたりこの端末を倱くしたりするず、メッセヌゞにアクセスできなくなる可胜性がありたす。 - 続行したすか + よろしいですか 予期しない゚ラヌ リカバリヌキヌ パスフレヌズを䜿甚しおリカバリヌキヌを生成䞭です。数秒かかるこずがありたす。 リカバリヌキヌを共有  - コピヌをしおください + コピヌしおください äž­æ­¢ 䞊曞き 別のセッションで鍵のバックアップを既に蚭定しおいるようです。䞊曞きしたすか @@ -1125,7 +1125,7 @@ 鍵のコピヌを暗号化しおホヌムサヌバヌに保存したす。バックアップを保護するためにパスフレヌズを蚭定しおください。 \n \n最倧限のセキュリティヌを確保するために、アカりントのパスワヌドず異なるものに蚭定するこずが倧切です。 - パスフレヌズを䜿甚しおバックアップを保護したす。 + パスフレヌズを䜿甚しおバックアップを保護したしょう。 暗号化されたメッセヌゞは、゚ンドツヌ゚ンドの暗号化によっお保護されおいたす。これらの暗号化されたメッセヌゞを読むための鍵を持っおいるのは、あなたず受信者だけです。 \n \n鍵を倱くさないよう、鍵を安党にバックアップしおください。 @@ -1134,36 +1134,36 @@ ${app_name}によるリカバリヌキヌの生成を望む堎合、パスフレヌズを削陀しおください。 マヌクダりンが無効です。 マヌクダりンが有効です。 - ”%s”ずのコマンドはいく぀かのパラメヌタが欠けおいるか䞍正です。 - 新しいセッションが暗号鍵を芁請しおいたす。 + コマンド\"%s\"はいく぀かのパラメヌタヌを欠いおいるか、パラメヌタヌが正しくありたせん。 + 新しいセッションが暗号鍵を芁求しおいたす。 \nセッション名%1$s -\n最埌のオンラむン日時%2$s -\n新たにログむンしお新しいセッションを開始しなかった堎合、この芁求を無芖しおください。 +\n盎近のオンラむン日時%2$s +\n新しいセッションにログむンしなかった堎合、この芁求を無芖しおください。 未認蚌のセッションが暗号鍵を芁求しおいたす。 \nセッション名%1$s -\n最埌のオンラむン日時%2$s -\n新しいセッションにログむンしおいない堎合、この芁求を無芖しおください。 +\n盎近のオンラむン日時%2$s +\n新しいセッションにログむンしなかった堎合、この芁求を無芖しおください。 鍵の共有リク゚スト - カスタムカメラ画面の代わりにシステムカメラを䜿甚したす。 + カスタムカメラ画面の代わりにシステムカメラを開始。 䜿甚䞭のりィゞェットがありたせん むンテグレヌションを管理 - DRM保護されおいるメディアを読み蟌む + DRMで保護されおいるメディアを読み蟌む マむクの䜿甚 カメラの䜿甚 - このりィゞェットは次のリ゜ヌスの䜿甚を芁求したす: + このりィゞェットは次のリ゜ヌスの䜿甚を芁求しおいたす 珟圚の䌚議から退出し、もう䞀぀の䌚議に参加したすか - 申し蚳ありたせんが、ビデオ䌚議に参加する途䞭で問題が発生したした - 申し蚳ありたせんが、叀い端末Android OS 6.0以前はJitsiを䜿甚したビデオ䌚議をサポヌトしおいたせん + 申し蚳ありたせんが、グルヌプ通話に参加する際に問題が発生したした + 申し蚳ありたせんが、叀い端末Android OS 6.0以前はJitsiを䜿甚したグルヌプ通話をサポヌトしおいたせん あなたの衚瀺名 りィゞェットの読み蟌みに倱敗したした。 \n%s りィゞェットを再読み蟌み これを䜿甚するず、クッキヌが蚭定され、デヌタが%sず共有される可胜性がありたす りィゞェットを远加した人 - **送信に倱敗 - ルヌムを開いおください + **送信に倱敗したした - ルヌムを開いおください 新しい招埅 %1$sず%2$s - %1$sに%2$sず%3$s + %2$sず%3$sで%1$s %d件の通知 @@ -1181,7 +1181,7 @@ 暗号化されたメッセヌゞの埩元 ルヌムのバヌゞョン - ブロックされたナヌザヌ%d人 + %d人のブロックされたナヌザヌ このルヌムのあるスペヌスのメンバヌは、誰でもこのルヌムを発芋し参加できたす。ルヌムをスペヌスに远加できるのは、ルヌムの管理者だけです。 スペヌスのメンバヌのみ @@ -1191,23 +1191,23 @@ 非公開 䞍明のアクセス蚭定%s 誰でもルヌムにノックができ、メンバヌがその参加を承認たたは拒吊できたす - 珟圚のルヌムディレクトリの芋え方を取埗できたせん%1$s。 - このルヌムを%1$sのルヌムディレクトリに公開したすか + 珟圚のルヌムディレクトリヌの芋え方を取埗できたせん%1$s。 + このルヌムを%1$sのルヌムディレクトリヌに公開したすか このアドレスを非公開にする このアドレスを公開 - アドレスを蚭定すれば、他のナヌザヌがあなたのホヌムサヌバヌ (%1$s) を通じおこのルヌムを芋぀けられるようになりたす。 + アドレスを蚭定するず、他のナヌザヌがあなたのホヌムサヌバヌ%1$sを通じおこのルヌムを芋぀けられるようになりたす。 ロヌカルアドレス - 新しい公開アドレス䟋: #alias:server) + 新しい公開アドレス䟋#alias:server) 他の公開アドレスはただありたせん。以䞋から远加できたす。 他の公開アドレスはただありたせん。 - \"%1$s\"を非公開にしたすか + アドレス\"%1$s\"を非公開にしたすか 公開 手動で新しいアドレスを公開 他の公開アドレス - 公開アドレスを通しお、どのサヌバヌのどのナヌザヌでも、このルヌムに参加できたす。アドレスを公開するには、たずロヌカルアドレスずしお蚭定する必芁がありたす。 + 公開アドレスを通しお、どのサヌバヌのナヌザヌでも、このルヌムに参加できたす。アドレスを公開するには、たずロヌカルアドレスずしお蚭定する必芁がありたす。 公開アドレス - このルヌムのアドレスず、ルヌムディレクトリにおける芋え方を管理できたす。 - スペヌスのアドレスを管理できたす。 + このルヌムのアドレスず、ルヌムディレクトリヌにおける芋え方を管理。 + このスペヌスのアドレスを管理。 スペヌスのアドレス ルヌムのアドレス ゲストの参加を蚱可 @@ -1219,27 +1219,27 @@ ホヌムサヌバヌAPIのURL アクセスを取り消す 衚瀺 - このルヌムのメッセヌゞぱンドツヌ゚ンドで暗号化されおいたす。 + このチャットのメッセヌゞぱンドツヌ゚ンドで暗号化されおいたす。 ダむレクトメッセヌゞ 新しいダむレクトメッセヌゞを送信 - メヌルアドレス任意 - メヌルアドレス - アカりント埩旧甚のメヌルアドレスを蚭定したす。埌からオプションで知人に芋぀けおもらえるようにできたす。 + 電子メヌル任意 + 電子メヌル + アカりント埩旧甚のメヌルアドレスを蚭定したす。埌からこのメヌルアドレスによっお知人に芋぀けおもらえるようにできたす任意。 メヌルアドレスを蚭定 メヌルアドレスを確認したした 発芋可胜なメヌルアドレス 続行するには利甚芏玄を承認しおください ホヌムサヌバヌの利甚芏玄を承認したら、再詊行しおください。 - 次に + 次ぞ 次ぞ - 次に - 次に - 次に + 次ぞ + 次ぞ + 次ぞ ナヌザヌ名を遞択しおください。 - ナヌザヌ名かパスワヌドが正しくありたせん。入力されたパスワヌドがスペヌスによっお開始しおいるか終了しおいるので、確認しおください。 + ナヌザヌ名やパスワヌドが正しくありたせん。入力されたパスワヌドがスペヌスで開始たたは終了しおいたす。確認しおください。 そのナヌザヌ名は既に䜿甚されおいたす ナヌザヌ名 - ナヌザヌ名たたはメヌルアドレス + ナヌザヌ名たたは電子メヌル %sでサむンむン %sでサむンアップ %sで続行 @@ -1255,8 +1255,8 @@ カスタムず高床な蚭定 組織向けのプレミアムホスティング 組織向けのプレミアムホスティング - 最倧のパブリックサヌバヌで、数癟䞇人に無料で参加 - メヌルず同じように、アカりントには1぀のホヌムがありたすが、誰ずでも話すこずができたす + 最倧の公開サヌバヌで、数癟䞇人に無料で参加 + 電子メヌルず同じように、アカりントには1぀のホヌムがありたすが、誰ずでも話すこずができたす サヌバヌを遞択 始めたしょう ゚クスペリ゚ンスを拡匵およびカスタマむズ @@ -1266,30 +1266,30 @@ ここが%sずのダむレクトメッセヌゞのスタヌト地点です。 倉曎履歎はありたせん メッセヌゞの倉曎履歎 - ファむル %1$s をダりンロヌドしたした! + ファむル %1$s をダりンロヌドしたした ビデオを%d%%圧瞮しおいたす 画像を圧瞮しおいたす  暗号化されたルヌムで完党な履歎を衚瀺 フィヌドバックを送信 - フィヌドバックを送信できたせんでした (%s) - ありがずうございたす、あなたのフィヌドバックは正垞に送信されたした + フィヌドバックの送信に倱敗したした%s + ありがずうございたす。フィヌドバックを正垞に送信したした 远加で確認が必芁な事項がある堎合は、連絡可 フィヌドバック 珟圚「スペヌス」のベヌタ版を䜿甚しおいたす。あなたのフィヌドバックは今埌のバヌゞョンに反映されたす。ご意芋を最倧限に参考にさせおいただくため、あなたのプラットフォヌムずナヌザヌ名を蚘録させおいただきたす。 スペヌスに぀いおのフィヌドバック - 提案の送信に倱敗したした(%s) - ありがずうございたす、提案は正垞に送信されたした + 提案の送信に倱敗したした%s + ありがずうございたす。提案を送信したした トヌクンの登録 - アプリケヌションの衚瀺名: - App ID: - Push Key: + アプリケヌションの衚瀺名 + App ID + Push Key 登録されたプッシュゲヌトりェむはありたせん プッシュ通知に関するルヌルが定矩されおいたせん プッシュ通知に関するルヌル - あなたは既にこのルヌムを芋おいたす - その他のサヌドパヌティヌの䜿甚に関する掲瀺 + 既にこのルヌムを衚瀺しおいたす + その他の倖郚ラむブラリヌのラむセンス Matrix SDKのバヌゞョン - ファむル\"%1$s\"から゚ンドツヌ゚ンド暗号鍵をむンポヌトしたす。 + ファむル\"%1$s\"から゚ンドツヌ゚ンド暗号鍵をむンポヌト。 鍵のバックアップデヌタの取埗䞭に゚ラヌが発生したした 信頌情報の取埗䞭に゚ラヌが発生したした ルヌムが䜜成されたしたが、䞀郚の招埅が送信されおいたせん。理由 @@ -1297,7 +1297,7 @@ \n%s ルヌムの蚭定 トピック - ルヌムの説明任意 + ルヌムのトピック任意 ルヌム名 このルヌムはプレビュヌできたせん。参加したすか 珟圚、このルヌムにはアクセスできたせん。 @@ -1308,7 +1308,7 @@ 䞍正な圢匏のむベントです。衚瀺できたせん ルヌムの管理者によっおモデレヌトされたむベント リアクション - リアクションを芋る + リアクションを衚瀺 リアクションを远加 同意 リアクション @@ -1319,14 +1319,14 @@ 未読メッセヌゞはありたせん 未読はありたせん %sがセッションの認蚌を芁求しおいたす - リトラむ - 他のホヌムサヌバヌに接続しようずしおいるようですね。サむンアりトしたすか + 再詊行 + 他のホヌムサヌバヌに接続しようずしおいるようです。サむンアりトしたすか IDサヌバヌを䜿甚しおいたせん 䞍明な゚ラヌ このルヌムを含む参加枈のスペヌス - このルヌムにアクセスできるスペヌスを決定したす。スペヌスが遞択されるず、そのメンバヌはルヌム名を芋぀けお参加できたす。 + このルヌムにアクセスできるスペヌスを遞択しおください。遞択したスペヌスのメンバヌはルヌム名を怜玢し、参加できるようになりたす。 了解 - 完了したした + 認蚌したした メッセヌゞの新しい鍵 暗号化されたメッセヌゞを決しお倱わないために セキュアバックアップ @@ -1340,7 +1340,7 @@ アクセス可胜なスペヌス スペヌスのメンバヌに発芋ずアクセスを蚱可したす。 スペヌス %s のメンバヌが怜玢、プレビュヌ、参加できたす。 - 非公開招埅のみ + 非公開招埅者のみ参加可胜 既定のメディア゜ヌス 既定の圧瞮率 ルヌムのアップグレヌド @@ -1356,7 +1356,7 @@ 自分の衚瀺名 グルヌプチャットで暗号化されたメッセヌゞ 1察1のチャットで暗号化されたメッセヌゞ - 以䞋がメッセヌゞに含たれる堎合に通知 + 以䞋の堎合に通知 その他 メンションずキヌワヌド 通知のデフォルト @@ -1366,32 +1366,32 @@ %d件の䞍圚着信音声 - 既定に蚭定しお次回から確認しない + 既定に蚭定し、次回から確認しない 鍵の共有リク゚ストの履歎を送信 結果がありたせん - 自分に電話をかけるこずはできたせん。参加者が招埅を受け入れるたでお埅ちください + 自分に電話を発信するこずはできたせん。参加者が招埅を受け入れるたでお埅ちください ミヌティングはJitsiのセキュリティヌずパヌミッションポリシヌを䜿甚したす。ミヌティング䞭は、珟圚ルヌムにいる党おの人に招埅が衚瀺されたす。 暩限がありたせん 音声メッセヌゞを送信するには、マむクの暩限を蚱可しおください。 この操䜜を実行するには、システム蚭定からカメラの暩限を蚱可しおください。 この操䜜を実行するための暩限がありたせん。システム蚭定から暩限を付䞎しおください。 IDサヌバヌに接続できたせんでした - IDサヌバヌのURLを入力 + IDサヌバヌのURLを入力しおください 同意する - 同意を撀回 + 同意を取り消す あなたの連絡先から他のナヌザヌを発芋するために、メヌルアドレスや電話番号をこのIDサヌバヌに送信するこずに同意しおいたす。 メヌルず電話番号を送信 - %sにメヌルを送りたした。メヌルを確認しおリンクをクリックしおください - %sにメヌルを送りたした。メヌルの確認リンクをクリックしおください + %sにメヌルを送りたした。メヌルを確認しお承認リンクをクリックしおください + %sにメヌルを送りたした。メヌルを確認しお承認リンクをクリックしおください 発芋可胜な電話番号 IDサヌバヌずの接続を解陀するず、他のナヌザヌによっお芋぀けられなくなり、たた、メヌルアドレスや電話で他のナヌザヌを招埅するこずもできなくなりたす。 電話番号を远加するず、発芋可胜に蚭定する電話番号を遞択できるようになりたす。 メヌルアドレスを远加するず、発芋可胜に蚭定するメヌルアドレスを遞択できるようになりたす。 - 珟圚、IDサヌバヌを䜿甚しおいたせん。自分の連絡先を芋぀けたり、連絡先から芋぀けおもらったりするには、以䞋でIDサヌバヌを蚭定しおください。 - 珟圚%1$sを䜿っお自分の連絡先を芋぀けたり、連絡先から芋぀けおもらったりできるようにしおいたす。 + 珟圚、IDサヌバヌを䜿甚しおいたせん。連絡先を芋぀けたり、連絡先から芋぀けおもらったりするには、以䞋でIDサヌバヌを蚭定しおください。 + 珟圚%1$sを䜿甚しお、自分の連絡先を芋぀けたり、連絡先から芋぀けおもらったりできるようにしおいたす。 IDサヌバヌを倉曎 IDサヌバヌの蚭定 - IDサヌバヌの切断 + IDサヌバヌから切断 IDサヌバヌ ボット、ブリッゞ、りィゞェット、ステッカヌパックを䜿甚 他の人が芋぀けられるように @@ -1399,18 +1399,18 @@ 線集履歎を衚瀺 提案 リンクをクリップボヌドにコピヌしたした - メむン画面に未読通知専甚のタブを远加する。 + メむン画面に未読通知専甚のタブを远加。 ルヌム名を怜玢 - 名前もしくはID (#䟋えば:matrix.org) - ルヌムディレクトリを芋る + 名前もしくはID#example:matrix.org + ルヌムディレクトリヌを芋る 新しいルヌムを䜜成 お探しのものが芋぀かりたせんか - あなたの提案をここに曞いおください - ご意芋・ご感想をお聞かせください。 + 提案をここに曞いおください + 意芋・感想を聞かせおください。 提案する - フォヌマット: - URL: - セッションの衚瀺名: + フォヌマット + URL + セッションの衚瀺名 以䞋のうちいずれかが流出、あるいはハッキングされた恐れがありたす。 \n \n- あなたのパスワヌド @@ -1419,9 +1419,9 @@ \n- あなたの端末が䜿甚しおいるむンタヌネット接続 \n \n蚭定画面からパスワヌドずリカバリヌキヌを早急に倉曎するこずを掚奚したす。 - メヌルアドレス + 電子メヌル アドレス - 続行する + 続行 ファむル このナヌザヌはスペヌスから远攟されたす。 \n @@ -1442,7 +1442,7 @@ %sずのビデオ通話 呌び出しおいたす  ホヌムサヌバヌを遞択 - %sのURLにあるホヌムサヌバヌに接続できたせん。リンクを確認するか、手動でホヌムサヌバヌを遞択しおください。 + URL %s のホヌムサヌバヌに接続できたせん。リンクを確認するか、手動でホヌムサヌバヌを遞択しおください。 埌で スペヌス スレッドから @@ -1452,7 +1452,7 @@ PINコヌドを有効にする これを「招埅者のみ参加可胜」に蚭定したした。 ルヌムの蚭定 - コンテンツが報告されたした + コンテンツを報告したした ヘルプずサポヌト ヘルプ ${app_name}の運営方針 @@ -1477,7 +1477,7 @@ 皮類 確認枈 遞択枈 - ビデオ + 動画 画像 スクリヌンショット 接続 @@ -1503,7 +1503,7 @@ è­Šå‘Š è­Šå‘Š 成功したした - 続行する + 続行 譊告 䜍眮情報 メディア @@ -1542,11 +1542,11 @@ 䜍眮情報を共有 スペヌスに関する倉曎を行うために必芁な圹割を曎新する暩限がありたせん スペヌスに関する倉曎を行うために必芁な圹割を遞択 - スペヌスに関する倉曎を行うために必芁な圹割を衚瀺し曎新したす。 + スペヌスに関する倉曎を行うために必芁な圹割を衚瀺し曎新。 絞り蟌む スレッド スレッド - スペヌスをアップグレヌド + スペヌスのアップグレヌド スペヌス名の倉曎 スペヌスの暩限 応答がありたせん @@ -1564,29 +1564,29 @@ ルヌム名を蚭定 アカりントの蚭定 キヌワヌド - ルヌムから退出したした + ルヌムから退出しおいたす 吹き出しでメッセヌゞを衚瀺 電子メヌルによる通知 - セッションからサむンアりトしたした + セッションからサむンアりトしおいたす なし メンションずキヌワヌドのみ ルヌムのスレッドを絞り蟌む 退出 携垯端末では、暗号化されたルヌムでのメンションずキヌワヌドの通知は受信できたせん。 - ルヌムの暗号化の有効化 + スペヌスの暗号化の有効化 スペヌスのメむンアドレスの倉曎 スペヌスのアバタヌの倉曎 アンケヌトを䜜成 アンケヌトを䜜成 - 暗号化が正しく蚭定されおいないため、メッセヌゞを送るこずができたせん。クリックしお蚭定を開いおください。 - 暗号化が正しく蚭定されおいないため、メッセヌゞを送るこずができたせん。管理者に連絡しお、暗号化を正しい状態に埩元しおください。 - %2$dの%1$d - あなたは既にこのスレッドを芋おいたす - ルヌムに衚瀺 - ルヌムに衚瀺 + 暗号化が正しく蚭定されおいないため、メッセヌゞを送信できたせん。クリックしお蚭定を開いおください。 + 暗号化が正しく蚭定されおいないため、メッセヌゞを送信できたせん。管理者に連絡しお、暗号化を正しい状態に埩元しおください。 + %2$d個のうち%1$d個 + 既にこのスレッドを衚瀺しおいたす + ルヌム内で衚瀺 + ルヌム内で衚瀺 スレッドを衚瀺 このルヌムぞの参加は蚱可されおいたせん - "トピック: " + "トピック " トラブルシュヌティング 鍵のバックアップのバナヌを閉じる キヌワヌドに「%s」を含めるこずはできたせん @@ -1636,8 +1636,8 @@ 音声メッセヌゞを録音 あなたのホヌムサヌバヌの運営方針 䞀番䞋に移動 - %sが読みたした - %1$sず%2$sが読みたした + %sが閲芧枈 + %1$sず%2$sが閲芧枈 ファむルを䜿甚 暗号化を有効にしたすか キャンセルしたした @@ -1667,20 +1667,20 @@ 新しいPINコヌド PINコヌドを再蚭定 PINコヌドを忘れたしたか - PINコヌドを入力 + PINコヌドを入力しおください PINコヌドを確認 サヌドパヌティヌ補ラむブラリヌ これはい぀でも蚭定から無効にできたす 私たちは、情報を第䞉者ず共有するこずはありたせん - 私たちは、アカりントのデヌタを蚘録したり分析したりするこずはありたせん + 私たちは、アカりントのいかなるデヌタも蚘録したり分析したりするこずは<b>ありたせん</b> ${app_name}の改善を手䌝う このルヌムを「リンクを知っおいる人が参加可胜」に蚭定したした。 - どのナヌザヌも無芖しおいたせん + 無芖しおいるナヌザヌはいたせん キヌワヌドを入力するずリアクションを怜玢できたす。 倉曎を加えたせんでした %1$sは倉曎を加えたせんでした - %d人のナヌザヌが読みたした + %d人のナヌザヌが閲芧枈 スペヌスぞのアクセス このサヌバヌは運営方針を提䟛しおいたせん。 @@ -1697,7 +1697,7 @@ 連絡先を発芋するには、連絡先のデヌタメヌルアドレスず電話番号をあなたのIDサヌバヌに送信する必芁がありたす。プラむバシヌの保護のため、デヌタは送信前にハッシュ化されたす。 メヌルアドレスず電話番号を%sに送信 このIDサヌバヌは運営方針を提䟛しおいたせん - IDサヌバヌの運営方針を隠す + IDサヌバヌの運営方針を衚瀺しない IDサヌバヌの運営方針を衚瀺 アカりントの新しいパスワヌドを蚭定  シェむクを怜出したした @@ -1710,18 +1710,18 @@ 埩号゚ラヌを自動的に報告する。 倉曎を有効にするにはアプリケヌションの再起動が必芁です。 LaTeXによる数孊衚蚘を有効にする - 以䞋が含たれる堎合に通知 + 以䞋の堎合に通知 アップグレヌドするず、このルヌムの新しいバヌゞョンが䜜成されたす。今ある党おのメッセヌゞは、アヌカむブしたルヌムに残りたす。 誰がアクセスできたすか 通知は%1$sで管理できたす。 暗号化されたルヌムでのメンションずキヌワヌドによる通知は、携垯端末では利甚できたせん。 ナヌザヌを無芖し、そのメッセヌゞを非衚瀺に蚭定 - %sずのコマンドは認識されおいたすが、スレッドではサポヌトされおいたせん。 + コマンド\"%s\"は認識されおいたすが、スレッドではサポヌトされおいたせん。 誰でもスペヌスを発芋し参加できたす 法的情報 ナヌザヌに関する情報を衚瀺 - このルヌムにおいおのみアバタヌを倉曎 - このルヌムにおいおのみ衚瀺名を倉曎 + このルヌムでのみアバタヌを倉曎 + このルヌムでのみ衚瀺名を倉曎 ナヌザヌの無芖を解陀し、以埌のメッセヌゞを衚瀺 続行するには%sを入力しおください 有効なリカバリヌキヌではありたせん @@ -1733,12 +1733,12 @@ アンケヌトの終了埌に結果を公開 結果はアンケヌトを終了した埌でのみ明らかにされたす 以䞋で開く - 暗号化のアップグレヌドが利甚可胜です + 暗号化のアップグレヌドが利甚できたす SSSSキヌをリカバリヌキヌから生成しおいたす ${app_name} iOS \n${app_name} Android - ${app_name}りェブ版 -\n${app_name}デスクトップ版 + ${app_name} りェブ版 +\n${app_name} デスクトップ版 リカバリヌキヌを遞択、盎接入力、あるいはクリップボヌドからペヌスト リカバリヌキヌを䜿甚 暗号化されたルヌムでのみサポヌト @@ -1775,8 +1775,8 @@ 録音を削陀 音声メッセヌゞがアクティブの間は返信や線集はできたせん このアンケヌトを削陀しおよろしいですか䞀床削陀するず埩元するこずはできたせん。 - 共有デヌタの取り扱いに倱敗したした - 回転ずクロップ + 共有デヌタを取り扱えたせんでした + 回転ずトリミング ルヌムを探す 既存のルヌムずスペヌスを远加 スレッドのメッセヌゞを有効にする @@ -1789,16 +1789,16 @@ \n既存のスペヌスを別のスペヌスに远加できたす。 あなたのホヌムサヌバヌはただスペヌスをサポヌトしおいないようです 画像を远加 - このコンテンツは䞍適切な投皿ずしお報告されおいたす。 + このコンテンツを䞍適切な投皿ずしお報告したした。 \n \nこのナヌザヌのコンテンツをこれ以䞊芋たくなければ、ナヌザヌを無芖しおそのメッセヌゞを非衚瀺にできたす。 - このコンテンツはスパムずしお報告されおいたす。 + このコンテンツをスパムずしお報告したした。 \n \nこのナヌザヌのコンテンツをこれ以䞊芋たくなければ、ナヌザヌを無芖しおそのメッセヌゞを非衚瀺にできたす。 - このコンテンツが報告されおいたす。 + このコンテンツを報告したした。 \n \nこのナヌザヌのコンテンツをこれ以䞊芋たくなければ、ナヌザヌを無芖しおそのメッセヌゞを非衚瀺にできたす。 - %1$sにより%2$sに + %1$sが%2$sにアップロヌド 質問あるいはトピック アンケヌトの質問あるいはトピック 少々お埅ちください。少し時間がかかるかもしれたせん。 @@ -1851,13 +1851,13 @@ ゚ンドツヌ゚ンドで暗号化されおおり、登録に電話番号は䞍芁です。広告もデヌタ収集もありたせん。 䌚話の保存先を自分で決められ、自分で管理できる独立したコミュニケヌション。Matrixをもずに。 オンラむン䞊でも察面の䌚話ず同じレベルでプラむバシヌを守る、安党で独立したコミュニケヌション。 - 安党なメッセヌゞ。 + 安党なメッセヌゞのやりずり。 䞻導暩を握るのは、あなたです。 ${app_name}の䜿甚に関するヘルプ 詳现なログは、むラむラシェむクでログを送信する際に、より倚くのログを提䟛するこずで、開発者にずっおの助けになりたす。有効にした堎合でも、メッセヌゞの内容やその他のプラむベヌトな情報は蚘録されたせん。 ルヌムのアップグレヌドは高床な䜜業であり、䞍具合や欠けおいる機胜、セキュリティヌ䞊の脆匱性がある堎合に掚奚されたす。 \nアップグレヌドは通垞、ルヌムがサヌバヌ䞊で凊理される仕方にだけ圱響したす。 - 䞀床有効にしたルヌムの暗号化は無効にするこずはできたせん。暗号化されたルヌムで送信されたメッセヌゞは、サヌバヌからは芋るこずができず、そのルヌムのメンバヌだけが芋るこずができたす。暗号化を有効にするず、倚くのボットやブリッゞが正垞に動䜜しなくなる堎合がありたす。 + 䞀床有効にしたルヌムの暗号化は無効にするこずはできたせん。暗号化されたルヌムで送信されたメッセヌゞは、サヌバヌからは閲芧できず、そのルヌムのメンバヌだけが閲芧できたす。暗号化を有効にするず、倚くのボットやブリッゞが正垞に動䜜しなくなる可胜性がありたす。 %sしお、このルヌムを皆に玹介したしょう。 このコヌドを共有し、スキャンしお远加しおもらい、䌚話を始めたしょう。 正しい参加者が%sにアクセスできるようにしたしょう。 @@ -1884,8 +1884,8 @@ 合蚈%1$d祚の投祚に基づく最終結果 - 新しいセッションが認蚌されたした。セッションは暗号化されたメッセヌゞにアクセスでき、他のナヌザヌには信頌枈ずしお衚瀺されたす。 - このルヌムを同じホヌムサヌバヌ䞊で組織内のチヌムずのコラボレヌションにのみ䜿甚するなら、このオプションを有効にするずいいかもしれたせん。これは埌から倉曎できたせん。 + 新しいセッションが認蚌されたした。セッションは暗号化されたメッセヌゞにアクセスするこずができたす。たた、セッションは他のナヌザヌに「信頌枈」ずしお衚瀺されたす。 + このルヌムを同じホヌムサヌバヌ䞊で組織内のチヌムずのコラボレヌションにのみ䜿甚する堎合、このオプションを有効にするずいいかもしれたせん。これは埌から倉曎できたせん。 %sに属しおいないナヌザヌによるこのルヌムぞの参加を、今埌氞久に拒吊 プレヌンテキストメッセヌゞの前に ( ͡° ͜ʖ ͡°) を付ける このメヌルアドレスのドメむンの登録は蚱可されおいたせん @@ -1898,16 +1898,16 @@ デバッグ甚の情報を画面に衚瀺 初期同期䞭  説明文が短すぎたす - サむンむンしお暗号鍵を取り戻さなければ、暗号化されたメッセヌゞにアクセスできなくなりたす。 + サむンむンしお暗号鍵を埩旧しないず、暗号化されたメッセヌゞにアクセスできなくなりたす。 再サむンむン - 有効なホヌムサヌバヌを発芋できたせん。識別子を確認しおください + 有効なホヌムサヌバヌを発芋できたせん。IDを確認しおください どこかのホヌムサヌバヌで既にアカりントを登録しおいる堎合、以䞋でMatrix ID䟋@user:domain.comずパスワヌドを䜿甚しおください。 入力したコヌドが正しくありたせん。確認しおください。 - これは正しいナヌザヌ識別子ではありたせん。正しいフォヌマットは「@user:homeserver.org」です。 - パスワヌドをお忘れの堎合、戻っおパスワヌドを再蚭定しおください。 + これは正しいナヌザヌIDではありたせん。正しいフォヌマットは「@user:homeserver.org」です。 + パスワヌドを忘れた堎合、戻っおパスワヌドを再蚭定しおください。 メヌルボックスを確認しおください カスタムサヌバヌに接続 - 既にアカりントを持っおいたす + 既にアカりントがありたす 既存のサヌバヌに参加したすか この質問をスキップ 友達ず家族 @@ -1928,14 +1928,14 @@ 非公開で招埅が必芁なルヌムは衚瀺されおいたせん。 \nルヌムを远加する暩限はありたせん。 非公開で招埅が必芁なルヌムは衚瀺されおいたせん。 - 知人に芋぀けおもらえるように電話番号を蚭定できたす。任意です。 + 知人に芋぀けおもらえるように電話番号を蚭定できたす任意。 メッセヌゞキヌ 埩旧甚のパスフレヌズ - ニックネヌムの色を倉曎 + 衚瀺名の色を倉曎 パスワヌドはただ倉曎されおいたせん。 \n \n倉曎䜜業を䞭止したすか - %1$sに確認メヌルを送信したした。 + %1$sに認蚌メヌルを送信したした。 メヌルボックスを確認しおください サむンむンに戻る 元の倧きさのたたメディアファむルを送信 @@ -1948,15 +1948,15 @@ 信頌されおいたせん 認蚌の芁求 %sがキャンセルしたした - 既読 + 閲芧枈 認蚌 - メヌルアドレスが正しくないようです + メヌルアドレスの圢匏が正しくありたせん 囜際電話番号の圢匏を䜿甚しおください。 囜際電話番号は「+」から始めおください コヌドを%1$sに送信したした。以䞋に入力しお認蚌しおください。 このメヌルアドレスはどのアカりントにも登録されおいたせん パスワヌドを倉曎するず、党おのセッションでの゚ンドツヌ゚ンド暗号鍵がリセットされ、暗号化されたメッセヌゞ履歎が読めなくなりたす。パスワヌドを再蚭定する前に、鍵のバックアップを蚭定するか、他のセッションからルヌムの鍵を゚クスポヌトしおおいおください。 - パスワヌドの再蚭定を確認するために確認メヌルを送信したす。 + パスワヌドの再蚭定を確認するために認蚌メヌルを送信したす。 このメヌルアドレスはどのアカりントにも登録されおいたせん。 このアプリでは、このホヌムサヌバヌにアカりントを䜜成できたせん。 \n @@ -1964,12 +1964,12 @@ 申し蚳ありたせんが、このサヌバヌはアカりントの新芏登録を受け入れおいたせん。 このアプリではこのホヌムサヌバヌにサむンむンできたせん。このホヌムサヌバヌは次のサむンむンの方法に察応しおいたす%1$s \n -\nりェブクラむ゚ントを䜿甚しおサむンむンしたすか - %1$sを読み蟌み䞭に゚ラヌが発生したした%2$d - 利甚したいサヌバヌのアドレスを入力しおください - 利甚したいModular Elementたたはサヌバヌのアドレスを入力しおください +\nりェブクラむアントを䜿甚しおサむンむンしたすか + ペヌゞ %1$s を読み蟌んでいる際に゚ラヌが発生したした%2$d + 䜿甚したいサヌバヌのアドレスを入力しおください + 䜿甚したいModular Elementたたはサヌバヌのアドレスを入力しおください 迷っおいたすか%s - みんなず繋がる手助けをいたしたす。 + みんなず繋がる手助けをいたしたす 自分のコヌド 招埅を%1$sず他%2$d人に送信したした @@ -2000,13 +2000,13 @@ QRコヌドがスキャンされおいたせん 内容を通知に衚瀺 プッシュ通知は無効になっおいたす - ナヌザヌのブロックを解陀できたせんでした + ナヌザヌのブロックの解陀に倱敗したした %1$sぞの招埅を取り消したすか 連絡先を取埗しおいたす  RiotはElementになりたした このメッセヌゞにアクセスできたせん アバタヌを蚭定 - IDサヌバヌのURLを入力 + IDサヌバヌのURLを入力しおください マむクのミュヌトを解陀 マむクをミュヌト %1$sを䜿甚 @@ -2025,22 +2025,22 @@ \n - 認蚌しおいる盞手が接続しおいるホヌムサヌバヌ \n - あなたか盞手のむンタヌネット接続 \n - あなたか盞手の端末 - セキュアではない - 信頌できないサむンむン + セキュアではありたせん + 信頌されおいないサむンむン 䜿甚できない文字が含たれおいたす ${app_name}の改善ず課題抜出のために、匿名の䜿甚状況デヌタの送信をお願いしたす。耇数の端末での䜿甚を分析するために、あなたの党端末共通のランダムな識別子を生成したす。 \n \n%sで利甚芏玄を閲芧できたす。 最初の怜玢結果のみ衚瀺しおいたす。文字をもっず入力しおください  matrix.toリンクのフォヌマットが正しくありたせんでした - 泚意この端末には暗号鍵を含む個人情報が保存されおいたす。 + 泚意この端末には暗号鍵を含む個人デヌタが保存されおいたす。 \n -\nこの端末での䜿甚を終了、たたは他のアカりントにサむンむンしたい堎合、このデヌタをクリアしおください。 +\nこの端末での䜿甚を終了、たたは他のアカりントにサむンむンする堎合、このデヌタをクリアしおください。 この端末に珟圚保存されおいる党おのデヌタをクリアしたすか \nアカりントデヌタずメッセヌゞにアクセスするにはもう䞀床サむンむンしおください。 珟圚のセッションはナヌザヌ %1$s のものですが、あなたが提䟛しおいる認蚌情報はナヌザヌ %2$s のものです。この操䜜は${app_name}ではサポヌトされおいたせん。 -\nたずデヌタをクリアし、その埌、別のアカりントにサむンむンしおください。 - 暗号化されたメッセヌゞがどの端末でも読めるように、サむンむンしおこの端末にのみ保存されおいる暗号鍵を取り戻しおください。 +\nデヌタをクリアし、その埌、別のアカりントにサむンむンしおください。 + 暗号化されたメッセヌゞがどの端末でも読めるように、サむンむンしおこの端末にのみ保存されおいる暗号鍵を埩旧しおください。 あなたのホヌムサヌバヌ%1$sの管理者があなたを%2$sのアカりントからサむンアりトしたした%3$s。 いく぀かの原因が考えられたす \n @@ -2052,10 +2052,10 @@ リク゚ストが倚すぎたす。%1$d秒埌に再詊行できたす  - このホヌムサヌバヌは叀いバヌゞョンです。管理者にアップグレヌドを芁請しおください。続行できたすが、いく぀かの機胜が正しく䜜動しない可胜性がありたす。 + このホヌムサヌバヌは叀いバヌゞョンです。管理者にアップグレヌドを䟝頌しおください。続行できたすが、いく぀かの機胜が正しく䜜動しない可胜性がありたす。 ホヌムサヌバヌのバヌゞョンが叀すぎたす - ただいた%1$sにメヌルを送信したした。 -\nアカりント登録を続行するにはメヌル内のリンクをクリックしおください。 + %1$sにメヌルを送信したした。 +\nアカりント登録を続行するには、メヌル内のリンクをクリックしおください。 CAPTCHA認蚌を行っおください アカりントがただ䜜成されおいたせん。登録を䞭止したすか %1$sにアカりント登録 @@ -2074,8 +2074,8 @@ サヌバヌ䞊の暗号鍵をバックアップしお、暗号化されたメッセヌゞずデヌタぞのアクセスが倱われるのを防ぎたしょう。 いたキャンセルするず、ログむンできなくなった際に、暗号化されたメッセヌゞずデヌタを倱っおしたう可胜性がありたす。 \n -\nたた、蚭定から、安党なバックアップの蚭定や鍵の管理を行うこずができたす。 - USBメモリヌもしくはバックアップドラむブに保存 +\n蚭定から、セキュアバックアップの蚭定や鍵の管理を行うこずもできたす。 + USBメモリヌやバックアップ甚のドラむブに保存 鍵のバックアップの蚭定 自己眲名キヌを同期しおいたす ナヌザヌキヌを同期しおいたす @@ -2087,12 +2087,12 @@ 鍵は既に最新です 鍵をリセット 質問は空にできたせん - ここで送受信されるメッセヌゞぱンドツヌ゚ンド暗号化されおいたす。 + ここでのメッセヌゞぱンドツヌ゚ンドで暗号化されおいたす。 \n -\nメッセヌゞは安党に保護されおおり、メッセヌゞのロックを解陀できる固有の鍵を持っおいるのはあなたず受信者だけです。 - この郚屋のメッセヌゞぱンドツヌ゚ンド暗号化されおいたす。 +\nメッセヌゞは安党に保護されおおり、メッセヌゞのロックを解陀するための固有の鍵は、あなたず受信者だけが持っおいたす。 + このルヌムのメッセヌゞぱンドツヌ゚ンドで暗号化されおいたす。 \n -\nメッセヌゞは安党に保護されおおり、メッセヌゞのロックを解陀できる固有の鍵を持っおいるのはあなたず受信者だけです。 +\nメッセヌゞは安党に保護されおおり、メッセヌゞのロックを解陀するための固有の鍵は、あなたず受信者だけが持っおいたす。 ステヌトキヌ リカバリヌキヌを以䞋に保存 送信者が意図的に鍵を送信しなかったため、このメッセヌゞにアクセスするこずができたせん @@ -2109,8 +2109,8 @@ ナビゲヌションのメニュヌを開く 承諟したした %sが承諟したした - %sが認蚌枈 - %sを認蚌する + %sを認蚌枈 + %sを認蚌 絵文字を比范しお認蚌 絵文字を比范しお認蚌 察面でない堎合は、代わりに絵文字を比范しおください @@ -2124,31 +2124,31 @@ あなただけが知っおいる秘密のパスワヌドを入力しおください。バックアップ甚にセキュリティヌキヌを生成したす。 暗号化されたメッセヌゞにアクセスするには、ログむンを認蚌し、本人確認を行う必芁がありたす。 暗号化されたメッセヌゞにアクセスするには、あなたの他のセッションからログむンを認蚌し、本人確認を行う必芁がありたす。 - 詳しく知る + 詳现を確認 セキュリティヌを高めるために、䜿い捚おコヌドが䞀臎しおいるのを確認しお、%sを認蚌したしょう。 暗号化の蚭定が正しくありたせん。 暗号化を埩元 - 暗号化を有効な状態に取り戻すために、管理者に連絡しおください。 + 暗号化を正垞な状態に戻すために、管理者に連絡しおください。 このナヌザヌずのメッセヌゞぱンドツヌ゚ンドで暗号化されおおり、第䞉者が解読するこずはできたせん。 このコヌドを盞手の画面に珟れおいるコヌドず比范しおください。 絵文字を比范しお、同じ順番で珟れおいるこずを確認しおください。 セキュリティヌを高めるために、察面で行うか、他の通信手段を利甚したしょう。 - 遞択された゚モヌトを虹色にしお送信したす - 遞択されたテキストを虹色にしお送信したす - ${app_name}がID%1$sのむベントを凊理䞭に゚ラヌが発生したした - ${app_name}は%1$sずいう皮類のむベントに察応しおいたせん + 指定した゚モヌトを虹色で送信 + 指定したテキストを虹色で送信 + ${app_name}は、ID \'%1$s\'のむベントのコンテンツを描画しおいる際に゚ラヌに遭遇したした + ${app_name}は\'%1$s\'ずいう皮類のむベントに察応しおいたせん 既読通知ぞ移動 倧切に保護したしょう 完了 アカりントパスワヌドず違うものにしおください。 続行するには%sを入力しおください。 認蚌を䞭止したした - 今䞭止するず、%1$s%2$sを認蚌したせん。認蚌は盞手のナヌザヌプロフィヌル画面からもう䞀床開始できたす。 + 䞭止するず、%1$s%2$sを認蚌したせん。認蚌は、盞手のナヌザヌプロフィヌル画面から改めお開始できたす。 䞭止するず、新しい端末では暗号化されたメッセヌゞが読めず、他のナヌザヌに信頌されたせん 䞭止するず、この端末では暗号化されたメッセヌゞが読めず、他のナヌザヌに信頌されたせん - 自分ではない + ログむンしおいたせん 新しいセッションを認蚌しお、暗号化されたメッセヌゞにアクセスできるようにしたしょう。 - 新しいログむン。あなたですか + 新しいログむンです。ログむンしたしたか ${app_name} Android ルヌムの管理者によっお削陀されおいたす。理由%1$s ナヌザヌによっお削陀されおいたす。理由%1$s @@ -2157,17 +2157,17 @@ 既存のセッションにアクセスできない堎合 %1$sずいうタむプのアカりントデヌタを削陀したすか \n -\n予期しないトラブルを起こす可胜性があるので泚意しおください。 +\n予期しない動䜜が起こる可胜性があるため、泚意しお䜿甚しおください。 %1$s%2$sが新しいセッションでサむンむンしたした - このセッションは%1$s%2$sによっお認蚌されおいるので、メッセヌゞのセキュリティは信頌できたす。 - 既存のセッションでこのセッションを認蚌しお、暗号化されたメッセヌゞぞアクセスできるようにしたしょう。 - あなたはこのセッションを認蚌しおいるので、メッセヌゞのセキュリティは信頌できたす。 + このセッションは%1$s%2$sによっお認蚌されおいるので、メッセヌゞのセキュリティヌは信頌できたす。 + 既存のセッションでこのセッションを認蚌しお、暗号化されたメッセヌゞにアクセスできるようにしたしょう。 + あなたはこのセッションを認蚌しおいるので、メッセヌゞのセキュリティヌは信頌できたす。 利甚可胜な暗号情報がありたせん 既定のバヌゞョン 非公開のルヌムずダむレクトメッセヌゞにおける゚ンドツヌ゚ンド暗号化は、あなたのサヌバヌの管理者により既定ずしお無効にされおいたす。 理由を含める - %1$sが%2$sの暩限レベルを倉曎したした。 - %1$sの暩限レベルを倉曎したした。 + %1$sが%2$s倉曎したした。 + %1$s倉曎したした。 誰ず䜿いたすか 䜜成するスペヌスの皮類を遞択しおください 自分ずチヌムメむトの非公開のスペヌス @@ -2177,9 +2177,9 @@ もう少しです確認を埅機しおいたす  あず少しですもう䞀方の端末は同じマヌクを衚瀺しおいたすか %sを埅機しおいたす  - このナヌザヌがこのセッションを認蚌するたで、送受信されるメッセヌゞには譊告マヌクが付きたす。手動で認蚌するこずも可胜です。 + このナヌザヌがこのセッションを認蚌するたで、送受信されるメッセヌゞには譊告マヌクが付きたす。手動で認蚌するこずもできたす。 セッションの取埗に倱敗したした - チヌムの仲間を招埅したしょう + 誰がチヌムの仲間ですか %sを探せるようになりたす 私のスペヌス %1$s %2$s に参加しおください スキップ @@ -2207,14 +2207,14 @@ 再認蚌が必芁です 党おリセット 連絡先 - 認蚌をキャンセルしたした。あらためお開始しおください。 + 認蚌をキャンセルしたした。改めお開始しおください。 抌し続けお録音し、離すず送信 PINコヌドを蚭定しおください %d個のサヌバヌアクセス制埡リストの倉曎 眮き換えられたルヌムに参加 - このルヌムが発芋できたせん。存圚するこずを確認しおください。 + このルヌムを発芋できたせん。存圚するこずを確認しおください。 指王や顔画像など、端末に固有の生䜓認蚌を有効にする。 絵文字で認蚌 テキストを䜿っお手動で認蚌 @@ -2234,7 +2234,7 @@ 未読のメッセヌゞ数のみを通知に衚瀺。 2分間${app_name}を䜿甚しないず、PINコヌドが芁求されたす。 🔐 ${app_name}で話したしょう - 個人情報保護の芳点から、${app_name}はハッシュ化されたメヌルアドレスず電話番号の送信のみをサポヌトしおいたす。 + プラむバシヌの保護の芳点から、${app_name}はハッシュ化されたメヌルアドレスず電話番号の送信のみをサポヌトしおいたす。 アプリの名前を倉曎したしたアプリは最新版で、アカりントにはログむン枈です。 ステヌトむベントを送信 ステヌトむベント @@ -2259,7 +2259,7 @@ \n \n続行しおよろしいですか このリンクを再確認しおください - ログむンを認蚌しおください%1$s + 新しいログむンがあなたのアカりントにアクセスしおいたす。ログむンを認蚌しおください%1$s 機密ストレヌゞのアクセスに倱敗したした この蚭定を有効にするず、党おのアクティビティヌにFLAG_SECUREを远加したす。倉曎を有効にするにはアプリケヌションの再起動が必芁です。 このアカりントは無効化されおいたす。 @@ -2267,7 +2267,7 @@ 印刷しお安党な堎所に保管 %2$sず%1$sが蚭定されたした。 \n -\n安党な堎所で保管しおくださいそれらは、アクティブなセッションを党お倱っおしたった際、暗号化されたメッセヌゞや安党な情報のロックを解陀するために必芁ずなりたす。 +\n安党な堎所で保管しおくださいアクティブなセッションを党お倱っおしたった際、暗号化されたメッセヌゞや安党な情報のロックを解陀するために必芁ずなりたす。 䜜成したアむデンティティヌキヌを公開しおいたす アプリケヌションのスクリヌンショットを防ぐ 続行するには%1$sか%2$sを䜿甚しおください。 @@ -2307,7 +2307,7 @@ ビデオ通話が拒吊されたした 音声通話が拒吊されたした %1$sは通話を拒吊したした - このデバむスを認蚌可胜な他の端末が党くない堎合にのみ、続行しおください。 + この端末を認蚌できる他の端末が党くない堎合にのみ、続行しおください。 このセッションを信頌枈ずしお認蚌するず、暗号化されたメッセヌゞにアクセスするこずができたす。このアカりントにサむンむンしなかった堎合は、あなたのアカりントのセキュリティヌが砎られおいる可胜性がありたす アカりントのセキュリティヌが砎られおいる可胜性がありたす 遞択したスペヌスに远加 @@ -2328,7 +2328,7 @@ 保存しお続行 蚭定画面からい぀でもプロフィヌルを曎新できたす - これは埌から倉曎できたす。 + 衚瀺名にプロフィヌル画像を远加したしょう プロフィヌル画像を远加 これは埌から倉曎できたす 衚瀺名 @@ -2340,14 +2340,14 @@ 衚瀺名を遞択 あなたのアカりント %s が䜜成されたした おめでずうございたす - 近日䞭にスレッドはベヌタ版ずなりたす。 + 近日䞭にスレッド機胜はベヌタ版ずなりたす。 \n \nその準備ずしお、この時点以前に䜜成されたスレッドは、通垞の返信ずしお衚瀺するように倉曎したす。 \n -\nスレッドはMatrixの仕様の䞀郚になったため、これは䞀床限りの倉曎です。 - スレッドはベヌタ版になりたす 🎉 +\nスレッド機胜はMatrixの仕様の䞀郚になったため、これは䞀床限りの倉曎です。 + スレッド機胜はベヌタ版になりたす 🎉 無効にする - スレッドに぀いおのフィヌドバック + スレッド機胜に぀いおのフィヌドバック フィヌドバックを送信 ベヌタ版 ベヌタ版 @@ -2363,7 +2363,7 @@ ${app_name}をシンプルにするために、タブはオプションになりたした。右䞊のメニュヌから管理できたす。 新しいレむアりトにようこそ アニメヌション画像を自動再生 - ゚ンドポむントのホヌムサヌバヌぞの登録に倱敗したした: + ゚ンドポむントのホヌムサヌバヌぞの登録に倱敗したした \n%1$s ゚ンドポむントがホヌムサヌバヌに登録されたした。 ゚ンドポむントの登録 @@ -2371,14 +2371,14 @@ ${app_name}は通知の衚瀺に暩限が必芁です。 \n暩限を䞎えおください。 - %1$sず他%2$d名 + %1$sず他%2$d人 %1$sず%2$s ホヌムサヌバヌがサポヌトしおいないため、スレッド機胜は䞍安定かもしれたせん。スレッドのメッセヌゞが安定しお衚瀺されないおそれがありたす。%sスレッド機胜を有効にしおよろしいですか - スレッドベヌタ版 - スレッドを甚いるず、䌚話のテヌマを保ったり、䌚話を远跡したりするのが容易になりたす。%sスレッドを有効にするずアプリケヌションが再起動したす。再起動には時間がかかる可胜性がありたす。 - スレッドベヌタ版 - ${app_name}は通知を衚瀺するために蚱可を必芁ずしおいたす。通知にはメッセヌゞや招埅などが衚瀺されたす。 + スレッド機胜ベヌタ版 + スレッド機胜を䜿うず、䌚話のテヌマを維持したり、䌚話を簡単に远跡したりするこずができたす。%sスレッド機胜を有効にするずアプリケヌションが再起動したす。再起動には時間がかかる可胜性がありたす。 + スレッド機胜ベヌタ版 + ${app_name}は、通知を衚瀺するための暩限を必芁ずしおいたす。通知にはメッセヌゞや招埅などが衚瀺されたす。 \n \n通知を衚瀺するには、次のポップアップでアクセスを蚱可しおください。 メヌルアドレスが認蚌されおいたせん。メヌルボックスを確認しおください @@ -2399,7 +2399,7 @@ このステップをスキップ 問題ありたせん 進みたしょう - ナヌザヌ名 / メヌルアドレス / 電話番号 + ナヌザヌ名 / 電子メヌル / 電話番号 あなたは人間ですか %sに送信された手順に埓っおください。 パスワヌドを再蚭定 @@ -2410,7 +2410,7 @@ メヌルアドレスを認蚌 コヌドを再送信 コヌドが%sに送信されたした - 電話番号を確認しおください + 電話番号を確認 党おの端末からサむンアりト パスワヌドを再蚭定 パスワヌドは8文字以䞊に蚭定しおください。 @@ -2427,7 +2427,7 @@ リッチテキスト゚ディタヌを有効にする 最初のメッセヌゞを送信する際にダむレクトメッセヌゞを䜜成 遅延DMを有効にする - スペヌスがありたせん。 + ただスペヌスがありたせん。 新しいレむアりトを有効にする アクティビティヌ順 アルファベット順 @@ -2445,12 +2445,12 @@ \n \nアプリケヌションが再起動したす。再起動には時間がかかる可胜性がありたす。 初期同期のリク゚スト - %sの子スペヌスを折りたたむ - %sの子スペヌスを展開 + %sのサブスペヌスを折りたたむ + %sのサブスペヌスを展開 ルヌムを探す スペヌスを倉曎 ルヌムを䜜成 - チャットを開始 + 䌚話を開始 党おの䌚話 ${app_name}にようこそ、 \n%s。 @@ -2481,7 +2481,7 @@ 音声配信を終了したした。 %1$sが音声配信を終了したした。 - %1$dを遞択したした + %1$d個遞択枈 有効にするず、このアプリケヌションを䜿甚しおいる際にも、他のナヌザヌにオフラむンずしお衚瀺されたす。 最近のチャットをシステムの共有メニュヌに衚瀺 @@ -2490,7 +2490,7 @@ 自動的に蚭定 フォントの倧きさを遞択 ⚠ 未認蚌の端末がこのルヌムにありたす。あなたが送信するメッセヌゞを埩号化するこずはできたせん。 - このルヌムの未認蚌のセッションに暗号化されたメッセヌゞを送信しない。 + このルヌムの未認蚌のセッションに察しお暗号化されたメッセヌゞを送信しない。 あなたのホヌムサヌバヌはスレッドの䞀芧衚瀺をただサポヌトしおいたせん。 ここに新しいリク゚ストず招埅が衚瀺されたす。 リッチテキスト゚ディタヌを詊しおみるプレヌンテキストモヌドは近日公開 @@ -2524,7 +2524,7 @@ ルヌムのタむムラむンで音声配信を録音しお送信するこずを可胜にしたす。 音声配信を有効にする 未読のメッセヌゞがある堎合は、ここに衚瀺されたす。 - 報告するこずはありたせん。 + 未読はありたせん。 クラむアントの情報の保存を有効にする セッション名は連絡先にも衚瀺されたす。 セッション名を蚭定するず、端末をより簡単に認識できるようになりたす。 @@ -2704,7 +2704,7 @@ %sの利甚芏玄ず運営方針を確認しおください サヌバヌの運営方針 問い合わせる - 自分でサヌバヌを運営したいですか + 自分でサヌバヌを運営したすか サヌバヌのURL ホヌムサヌバヌのアドレスを入力しおください あなたのホヌムサヌバヌのアドレスを入力しおください。ここにあなたの党おのデヌタがホストされたす @@ -2713,7 +2713,7 @@ 8文字以䞊にしおください アカりントを䜜成 ホヌムに移動 - プロフィヌルを倉曎 + プロフィヌルを蚭定 ${app_name}は職堎利甚にも最適です。䞖界で最も安党な組織によっお信頌されおいたす。 音声配信 スペヌスの䞀芧を開く @@ -2746,11 +2746,11 @@ このホヌムサヌバヌは数字だけからなるナヌザヌ名を承諟したせん。 最初のメッセヌゞを送信するず、%sを䌚話に招埅 Nightly build - あなたの他の端末でコヌドをスキャンするか、もしくは反察に、このデバむスでスキャンしおください + あなたの他の端末でコヌドをスキャンするか、もしくは反察に、この端末でスキャンしおください Element Matrix ServicesEMSは、高速、安党でリアルタむムのコミュニケヌション向きの、堅牢で安定したホスティングサヌビスです。<a href=\"${ftue_ems_url}\">element.io/ems</a>で方法を調べたしょう。 アカりントにサむンむンするサヌバヌ アカりントを䜜成するサヌバヌ - スレッドは、改良した通知など新機胜の远加䜜業䞭です。フィヌドバックをお聞かせください + スレッド機胜に぀いおは、改良した通知など新機胜の远加などを行っおいたす。フィヌドバックをお聞かせください 🔒 セキュリティヌの蚭定で、党おのルヌムに関しお認蚌枈のセッションにのみ暗号化を行うよう蚭定したした。 プレれンスステヌタス衚瀺 取り蟌み䞭 From a226385ed39e54c3564e45c642498053fb370b29 Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Mon, 6 Feb 2023 19:43:55 +0000 Subject: [PATCH 179/189] Translated using Weblate (Swedish) Currently translated at 100.0% (2598 of 2598 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/sv/ --- library/ui-strings/src/main/res/values-sv/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values-sv/strings.xml b/library/ui-strings/src/main/res/values-sv/strings.xml index 30d5f377a7..597dd90b2f 100644 --- a/library/ui-strings/src/main/res/values-sv/strings.xml +++ b/library/ui-strings/src/main/res/values-sv/strings.xml @@ -2919,5 +2919,5 @@ Du kan inte pÃ¥börja ett röstmeddelande eftersom du för nÀrvarande spelar in en röstsÀndning. VÀnligen avsluta din röstsÀndning för att börja spela in ett röstmeddelande Kan inte starta röstsÀndning Startade en röstsÀndning - AnvÀnd inline kodformat + AnvÀnd inline-kodformat \ No newline at end of file From 7460e76a47464d8ae6db3479f3091f9181150c2a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Feb 2023 23:03:13 +0000 Subject: [PATCH 180/189] Bump flipper from 0.177.0 to 0.178.1 Bumps `flipper` from 0.177.0 to 0.178.1. Updates `com.facebook.flipper:flipper` from 0.177.0 to 0.178.1 - [Release notes](https://github.com/facebook/flipper/releases) - [Commits](https://github.com/facebook/flipper/compare/v0.177.0...v0.178.1) Updates `com.facebook.flipper:flipper-network-plugin` from 0.177.0 to 0.178.1 - [Release notes](https://github.com/facebook/flipper/releases) - [Commits](https://github.com/facebook/flipper/compare/v0.177.0...v0.178.1) --- updated-dependencies: - dependency-name: com.facebook.flipper:flipper dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: com.facebook.flipper:flipper-network-plugin dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index 413199363a..307ba4b833 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.177.0" +def flipper = "0.178.1" def epoxy = "5.0.0" def mavericks = "3.0.1" def glide = "4.14.2" From fa47c4b87ef66e7987d41f4ab6387867e954f46a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 7 Feb 2023 12:34:58 +0100 Subject: [PATCH 181/189] Ensure storage is computed in background, to not block display of general settings. --- .../im/vector/app/core/utils/FileUtils.kt | 2 + .../settings/VectorSettingsGeneralFragment.kt | 50 +++++++++---------- 2 files changed, 25 insertions(+), 27 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/utils/FileUtils.kt b/vector/src/main/java/im/vector/app/core/utils/FileUtils.kt index c735b8b33d..63f141ef52 100644 --- a/vector/src/main/java/im/vector/app/core/utils/FileUtils.kt +++ b/vector/src/main/java/im/vector/app/core/utils/FileUtils.kt @@ -17,6 +17,7 @@ package im.vector.app.core.utils import android.content.Context +import androidx.annotation.WorkerThread import timber.log.Timber import java.io.File import java.util.Locale @@ -125,6 +126,7 @@ fun getFileExtension(fileUri: String): String? { * Size * ========================================================================================== */ +@WorkerThread fun getSizeOfFiles(root: File): Long { return root.walkTopDown() .onEnter { diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt index 548a7be180..c90f55d22a 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt @@ -250,37 +250,28 @@ class VectorSettingsGeneralFragment : // clear medias cache findPreference(VectorPreferences.SETTINGS_CLEAR_MEDIA_CACHE_PREFERENCE_KEY)!!.let { - val size = getSizeOfFiles(File(requireContext().cacheDir, DiskCache.Factory.DEFAULT_DISK_CACHE_DIR)) + session.fileService().getCacheSize() - - it.summary = TextUtils.formatFileSize(requireContext(), size.toLong()) - - it.onPreferenceClickListener = Preference.OnPreferenceClickListener { - lifecycleScope.launch(Dispatchers.Main) { - // On UI Thread - displayLoadingView() - - Glide.get(requireContext()).clearMemory() - session.fileService().clearCache() - - var newSize: Long - - withContext(Dispatchers.IO) { - // On BG thread - Glide.get(requireContext()).clearDiskCache() - - newSize = getSizeOfFiles(File(requireContext().cacheDir, DiskCache.Factory.DEFAULT_DISK_CACHE_DIR)) - newSize += session.fileService().getCacheSize() + lifecycleScope.launch(Dispatchers.Main) { + it.summary = getString(R.string.loading) + val size = getCacheSize() + it.summary = TextUtils.formatFileSize(requireContext(), size) + it.onPreferenceClickListener = Preference.OnPreferenceClickListener { + lifecycleScope.launch(Dispatchers.Main) { + // On UI Thread + displayLoadingView() + Glide.get(requireContext()).clearMemory() + session.fileService().clearCache() + val newSize = withContext(Dispatchers.IO) { + // On BG thread + Glide.get(requireContext()).clearDiskCache() + getCacheSize() + } + it.summary = TextUtils.formatFileSize(requireContext(), newSize) + hideLoadingView() } - - it.summary = TextUtils.formatFileSize(requireContext(), newSize) - - hideLoadingView() + false } - - false } } - // Sign out findPreference("SETTINGS_SIGN_OUT_KEY")!! .onPreferenceClickListener = Preference.OnPreferenceClickListener { @@ -292,6 +283,11 @@ class VectorSettingsGeneralFragment : } } + private suspend fun getCacheSize(): Long = withContext(Dispatchers.IO) { + getSizeOfFiles(File(requireContext().cacheDir, DiskCache.Factory.DEFAULT_DISK_CACHE_DIR)) + + session.fileService().getCacheSize() + } + override fun onResume() { super.onResume() // Refresh identity server summary From ae2fbf1ecdacaac85c7e335330557b84807e04e5 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 7 Feb 2023 12:37:43 +0100 Subject: [PATCH 182/189] Changelog --- changelog.d/5918.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/5918.bugfix diff --git a/changelog.d/5918.bugfix b/changelog.d/5918.bugfix new file mode 100644 index 0000000000..826e20aff6 --- /dev/null +++ b/changelog.d/5918.bugfix @@ -0,0 +1 @@ +If media cache is large, Settings > General takes a long time to open From 67ae54f0a5eb7f4244c0557b10cbdec31c85f2eb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Feb 2023 11:53:49 +0000 Subject: [PATCH 183/189] Bump com.autonomousapps.dependency-analysis from 1.18.0 to 1.19.0 (#8085) Bumps com.autonomousapps.dependency-analysis from 1.18.0 to 1.19.0. --- updated-dependencies: - dependency-name: com.autonomousapps.dependency-analysis dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 84334d679b..296b23bb2b 100644 --- a/build.gradle +++ b/build.gradle @@ -48,7 +48,7 @@ plugins { id "com.google.devtools.ksp" version "1.8.0-1.0.8" // Dependency Analysis - id 'com.autonomousapps.dependency-analysis' version "1.18.0" + id 'com.autonomousapps.dependency-analysis' version "1.19.0" // Gradle doctor id "com.osacky.doctor" version "0.8.1" } From e3625360371539b8c7787aba6e94bdcf3fe6b644 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Feb 2023 11:54:45 +0000 Subject: [PATCH 184/189] Bump com.google.devtools.ksp from 1.8.0-1.0.8 to 1.8.10-1.0.9 Bumps [com.google.devtools.ksp](https://github.com/google/ksp) from 1.8.0-1.0.8 to 1.8.10-1.0.9. - [Release notes](https://github.com/google/ksp/releases) - [Commits](https://github.com/google/ksp/compare/1.8.0-1.0.8...1.8.10-1.0.9) --- updated-dependencies: - dependency-name: com.google.devtools.ksp dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 296b23bb2b..ee12d2a4bc 100644 --- a/build.gradle +++ b/build.gradle @@ -45,7 +45,7 @@ plugins { // Detekt id "io.gitlab.arturbosch.detekt" version "1.22.0" // Ksp - id "com.google.devtools.ksp" version "1.8.0-1.0.8" + id "com.google.devtools.ksp" version "1.8.10-1.0.9" // Dependency Analysis id 'com.autonomousapps.dependency-analysis' version "1.19.0" From 6e44f52c4a5e745a6a8a4bc51beee43d0c7c1055 Mon Sep 17 00:00:00 2001 From: ByeongsuPark Date: Tue, 7 Feb 2023 22:41:47 +0900 Subject: [PATCH 185/189] Fix the next button disable issue after visiting homeserver screen Signed-off-by: byeongsu@soongsil.ac.kr --- changelog.d/7928.bugfix | 1 + .../onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 changelog.d/7928.bugfix diff --git a/changelog.d/7928.bugfix b/changelog.d/7928.bugfix new file mode 100644 index 0000000000..30fead9a7d --- /dev/null +++ b/changelog.d/7928.bugfix @@ -0,0 +1 @@ +Fix the next button disabled issue after going to change homeserver screen diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt index aad54877c9..2c016f7077 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt @@ -23,6 +23,7 @@ import android.view.View import android.view.ViewGroup import androidx.autofill.HintConstants import androidx.core.view.isVisible +import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R @@ -103,7 +104,7 @@ class FtueAuthCombinedLoginFragment : combine(views.loginInput.editText().textChanges(), views.loginPasswordInput.editText().textChanges()) { account, password -> views.loginSubmit.isEnabled = account.isNotEmpty() && password.isNotEmpty() - }.launchIn(viewLifecycleOwner.lifecycleScope) + }.flowWithLifecycle(lifecycle).launchIn(viewLifecycleOwner.lifecycleScope) } private fun submit() { From 4226296990ab2fb939dff77779ab543140cf45e2 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 7 Feb 2023 14:52:12 +0100 Subject: [PATCH 186/189] Get Event from cache without using a `Room` --- .../sdk/api/session/events/EventService.kt | 8 ++++++++ .../database/query/EventEntityQueries.kt | 6 ++++++ .../session/events/DefaultEventService.kt | 20 ++++++++++++++++++- .../timeline/factory/TimelineItemFactory.kt | 6 ++---- 4 files changed, 35 insertions(+), 5 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/EventService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/EventService.kt index 7f275bf952..11ef3f0d2f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/EventService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/EventService.kt @@ -28,4 +28,12 @@ interface EventService { roomId: String, eventId: String ): Event + + /** + * Get an Event from cache. Return null if not found. + */ + fun getEventFromCache( + roomId: String, + eventId: String + ): Event? } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt index 4805c36f8c..75232f01f1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt @@ -47,6 +47,12 @@ internal fun EventEntity.Companion.where(realm: Realm, eventId: String): RealmQu .equalTo(EventEntityFields.EVENT_ID, eventId) } +internal fun EventEntity.Companion.where(realm: Realm, roomId: String, eventId: String): RealmQuery { + return realm.where() + .equalTo(EventEntityFields.ROOM_ID, roomId) + .equalTo(EventEntityFields.EVENT_ID, eventId) +} + internal fun EventEntity.Companion.whereRoomId(realm: Realm, roomId: String): RealmQuery { return realm.where() .equalTo(EventEntityFields.ROOM_ID, roomId) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/DefaultEventService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/DefaultEventService.kt index 51d305f441..50ea588dff 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/DefaultEventService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/DefaultEventService.kt @@ -18,13 +18,18 @@ package org.matrix.android.sdk.internal.session.events import org.matrix.android.sdk.api.session.events.EventService import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.internal.database.RealmSessionProvider +import org.matrix.android.sdk.internal.database.mapper.asDomain +import org.matrix.android.sdk.internal.database.model.EventEntity +import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.session.call.CallEventProcessor import org.matrix.android.sdk.internal.session.room.timeline.GetEventTask import javax.inject.Inject internal class DefaultEventService @Inject constructor( private val getEventTask: GetEventTask, - private val callEventProcessor: CallEventProcessor + private val callEventProcessor: CallEventProcessor, + private val realmSessionProvider: RealmSessionProvider, ) : EventService { override suspend fun getEvent(roomId: String, eventId: String): Event { @@ -36,4 +41,17 @@ internal class DefaultEventService @Inject constructor( return event } + + override fun getEventFromCache(roomId: String, eventId: String): Event? { + return realmSessionProvider.withRealm { realm -> + EventEntity.where( + realm = realm, + roomId = roomId, + eventId = eventId + ) + .findFirst() + ?.asDomain() + + } + } } 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 d44713f404..84b71ceedf 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 @@ -19,15 +19,13 @@ package im.vector.app.features.home.room.detail.timeline.factory import im.vector.app.core.epoxy.TimelineEmptyItem import im.vector.app.core.epoxy.TimelineEmptyItem_ import im.vector.app.core.epoxy.VectorEpoxyModel -import im.vector.app.core.extensions.isVoiceBroadcast import im.vector.app.features.analytics.DecryptionFailureTracker import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventVisibilityHelper import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants +import im.vector.app.features.voicebroadcast.model.isVoiceBroadcast import org.matrix.android.sdk.api.session.Session 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.getTimelineEvent import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.getRelationContent import timber.log.Timber @@ -143,7 +141,7 @@ class TimelineItemFactory @Inject constructor( event.root.isRedacted() -> messageItemFactory.create(params) relationContent?.type == RelationType.REFERENCE -> { // Hide the decryption error for VoiceBroadcast chunks - val relatedEvent = relationContent.eventId?.let { session.getRoom(event.roomId)?.getTimelineEvent(it) } + val relatedEvent = relationContent.eventId?.let { session.eventService().getEventFromCache(event.roomId, it) } if (relatedEvent?.isVoiceBroadcast() != true) encryptedItemFactory.create(params) else null } else -> encryptedItemFactory.create(params) From 8775c4d0a238508c8d02040bfde946b5bd0b31e7 Mon Sep 17 00:00:00 2001 From: yostyle Date: Tue, 7 Feb 2023 16:54:24 +0100 Subject: [PATCH 187/189] Fix lint --- .../android/sdk/internal/session/events/DefaultEventService.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/DefaultEventService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/DefaultEventService.kt index 50ea588dff..4ba5c3b946 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/DefaultEventService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/DefaultEventService.kt @@ -51,7 +51,6 @@ internal class DefaultEventService @Inject constructor( ) .findFirst() ?.asDomain() - } } } From b37604ce39e23c308fa00a9dd2fd4ac55f5a9c5b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 8 Feb 2023 09:50:19 +0100 Subject: [PATCH 188/189] Changelog for version 1.5.24 --- CHANGES.md | 34 ++++++++++++++++++++++++++++++++++ changelog.d/5918.bugfix | 1 - changelog.d/6457.bugfix | 1 - changelog.d/7801.bugfix | 1 - changelog.d/7820.misc | 1 - changelog.d/7864.sdk | 1 - changelog.d/7864.wip | 1 - changelog.d/7928.bugfix | 1 - changelog.d/7975.bugfix | 1 - changelog.d/8005.sdk | 1 - changelog.d/8011.feature | 1 - changelog.d/8012.bugfix | 1 - changelog.d/8031.bugfix | 1 - changelog.d/8042.misc | 1 - changelog.d/8058.misc | 1 - changelog.d/8062.bugfix | 2 -- 16 files changed, 34 insertions(+), 16 deletions(-) delete mode 100644 changelog.d/5918.bugfix delete mode 100644 changelog.d/6457.bugfix delete mode 100644 changelog.d/7801.bugfix delete mode 100644 changelog.d/7820.misc delete mode 100644 changelog.d/7864.sdk delete mode 100644 changelog.d/7864.wip delete mode 100644 changelog.d/7928.bugfix delete mode 100644 changelog.d/7975.bugfix delete mode 100644 changelog.d/8005.sdk delete mode 100644 changelog.d/8011.feature delete mode 100644 changelog.d/8012.bugfix delete mode 100644 changelog.d/8031.bugfix delete mode 100644 changelog.d/8042.misc delete mode 100644 changelog.d/8058.misc delete mode 100644 changelog.d/8062.bugfix diff --git a/CHANGES.md b/CHANGES.md index 76b46bbbe7..08f375f8c1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,37 @@ +Changes in Element v1.5.24 (2023-02-08) +======================================= + +Features ✹ +---------- + - [Rich text editor] Add inline code to rich text editor ([#8011](https://github.com/vector-im/element-android/issues/8011)) + +Bugfixes 🐛 +---------- + - If media cache is large, Settings > General takes a long time to open ([#5918](https://github.com/vector-im/element-android/issues/5918)) + - Fix that replies to @roomba would be highlighted as a room ping. Contributed by Nico. ([#6457](https://github.com/vector-im/element-android/issues/6457)) + - Cannot select text properly in plain text mode when using Rich Text Editor. ([#7801](https://github.com/vector-im/element-android/issues/7801)) + - Fix the next button disabled issue after going to change homeserver screen ([#7928](https://github.com/vector-im/element-android/issues/7928)) + - Fix extra new lines added to inline code ([#7975](https://github.com/vector-im/element-android/issues/7975)) + - [Voice Broadcast] Use internal playback timer to compute the current playback position ([#8012](https://github.com/vector-im/element-android/issues/8012)) + - Do not send any request to Posthog if no consent is provided. ([#8031](https://github.com/vector-im/element-android/issues/8031)) + - [Voice Broadcast] We should not be able to start broadcasting if there is already a live broadcast in the Room ([#8062](https://github.com/vector-im/element-android/issues/8062)) + +In development 🚧 +---------------- + - [Poll] History list: unmock data ([#7864](https://github.com/vector-im/element-android/issues/7864)) + +SDK API changes ⚠ +------------------ + - [Poll] Adding PollHistoryService ([#7864](https://github.com/vector-im/element-android/issues/7864)) + - [Push rules] Call /actions api before /enabled api ([#8005](https://github.com/vector-im/element-android/issues/8005)) + +Other changes +------------- + - Let the user know when we are not able to decrypt the voice broadcast chunks ([#7820](https://github.com/vector-im/element-android/issues/7820)) + - [Voice Broadcast] Show Live broadcast in the room list only if the feature flag is enabled in the lab ([#8042](https://github.com/vector-im/element-android/issues/8042)) + - Improve the `CountUpTimer` implementation ([#8058](https://github.com/vector-im/element-android/issues/8058)) + + Changes in Element v1.5.22 (2023-01-25) ======================================= diff --git a/changelog.d/5918.bugfix b/changelog.d/5918.bugfix deleted file mode 100644 index 826e20aff6..0000000000 --- a/changelog.d/5918.bugfix +++ /dev/null @@ -1 +0,0 @@ -If media cache is large, Settings > General takes a long time to open diff --git a/changelog.d/6457.bugfix b/changelog.d/6457.bugfix deleted file mode 100644 index 89ba075378..0000000000 --- a/changelog.d/6457.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix that replies to @roomba would be highlighted as a room ping. Contributed by Nico. diff --git a/changelog.d/7801.bugfix b/changelog.d/7801.bugfix deleted file mode 100644 index 4ab8d7feda..0000000000 --- a/changelog.d/7801.bugfix +++ /dev/null @@ -1 +0,0 @@ -Cannot select text properly in plain text mode when using Rich Text Editor. diff --git a/changelog.d/7820.misc b/changelog.d/7820.misc deleted file mode 100644 index 1f59cb9afe..0000000000 --- a/changelog.d/7820.misc +++ /dev/null @@ -1 +0,0 @@ -Let the user know when we are not able to decrypt the voice broadcast chunks diff --git a/changelog.d/7864.sdk b/changelog.d/7864.sdk deleted file mode 100644 index b7c6a5b339..0000000000 --- a/changelog.d/7864.sdk +++ /dev/null @@ -1 +0,0 @@ -[Poll] Adding PollHistoryService diff --git a/changelog.d/7864.wip b/changelog.d/7864.wip deleted file mode 100644 index da04806b8b..0000000000 --- a/changelog.d/7864.wip +++ /dev/null @@ -1 +0,0 @@ -[Poll] History list: unmock data diff --git a/changelog.d/7928.bugfix b/changelog.d/7928.bugfix deleted file mode 100644 index 30fead9a7d..0000000000 --- a/changelog.d/7928.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix the next button disabled issue after going to change homeserver screen diff --git a/changelog.d/7975.bugfix b/changelog.d/7975.bugfix deleted file mode 100644 index b34c784b27..0000000000 --- a/changelog.d/7975.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix extra new lines added to inline code diff --git a/changelog.d/8005.sdk b/changelog.d/8005.sdk deleted file mode 100644 index 1849776d50..0000000000 --- a/changelog.d/8005.sdk +++ /dev/null @@ -1 +0,0 @@ -[Push rules] Call /actions api before /enabled api diff --git a/changelog.d/8011.feature b/changelog.d/8011.feature deleted file mode 100644 index 700a528fc1..0000000000 --- a/changelog.d/8011.feature +++ /dev/null @@ -1 +0,0 @@ -[Rich text editor] Add inline code to rich text editor \ No newline at end of file diff --git a/changelog.d/8012.bugfix b/changelog.d/8012.bugfix deleted file mode 100644 index bd2ee3dd08..0000000000 --- a/changelog.d/8012.bugfix +++ /dev/null @@ -1 +0,0 @@ -[Voice Broadcast] Use internal playback timer to compute the current playback position diff --git a/changelog.d/8031.bugfix b/changelog.d/8031.bugfix deleted file mode 100644 index 0e7ff28509..0000000000 --- a/changelog.d/8031.bugfix +++ /dev/null @@ -1 +0,0 @@ -Do not send any request to Posthog if no consent is provided. diff --git a/changelog.d/8042.misc b/changelog.d/8042.misc deleted file mode 100644 index dbfe98140c..0000000000 --- a/changelog.d/8042.misc +++ /dev/null @@ -1 +0,0 @@ -[Voice Broadcast] Show Live broadcast in the room list only if the feature flag is enabled in the lab diff --git a/changelog.d/8058.misc b/changelog.d/8058.misc deleted file mode 100644 index d864b3c7a8..0000000000 --- a/changelog.d/8058.misc +++ /dev/null @@ -1 +0,0 @@ -Improve the `CountUpTimer` implementation diff --git a/changelog.d/8062.bugfix b/changelog.d/8062.bugfix deleted file mode 100644 index af1a370350..0000000000 --- a/changelog.d/8062.bugfix +++ /dev/null @@ -1,2 +0,0 @@ - [Voice Broadcast] We should not be able to start broadcasting if there is already a live broadcast in the Room - \ No newline at end of file From 427d9a70d52becb91c39e9447b0f18bf0aea3669 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 8 Feb 2023 09:51:08 +0100 Subject: [PATCH 189/189] Adding fastlane file for version 1.5.24 --- fastlane/metadata/android/en-US/changelogs/40105240.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/en-US/changelogs/40105240.txt diff --git a/fastlane/metadata/android/en-US/changelogs/40105240.txt b/fastlane/metadata/android/en-US/changelogs/40105240.txt new file mode 100644 index 0000000000..aaceef9ce6 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40105240.txt @@ -0,0 +1,2 @@ +Main changes in this version: Mainly bugfixing, in particular fix message not appearing on the timeline. +Full changelog: https://github.com/vector-im/element-android/releases