From 3267a0410a9a0bb69a46e0fca70491e3687413cd Mon Sep 17 00:00:00 2001 From: Danny Seymour <danny@seymour.family> Date: Thu, 7 Apr 2022 02:18:19 -0700 Subject: [PATCH 001/187] fix: tweak styling of message bubbles * Decreases the size of rounded corners * Increases the maximum width of message bubbles to help avoid unnecessary unused space on screen Signed-off-by: Danny Seymour <danny@seymour.family> --- changelog.d/5712.misc | 1 + library/ui-styles/src/main/res/values/dimens.xml | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 changelog.d/5712.misc diff --git a/changelog.d/5712.misc b/changelog.d/5712.misc new file mode 100644 index 0000000000..549306c63f --- /dev/null +++ b/changelog.d/5712.misc @@ -0,0 +1 @@ +Decreases the size of rounded corners and increases the maximum width of message bubbles to help avoid unnecessary unused space on screen diff --git a/library/ui-styles/src/main/res/values/dimens.xml b/library/ui-styles/src/main/res/values/dimens.xml index 81d5a77297..419390129a 100644 --- a/library/ui-styles/src/main/res/values/dimens.xml +++ b/library/ui-styles/src/main/res/values/dimens.xml @@ -49,9 +49,9 @@ <dimen name="chat_bubble_margin_start">28dp</dimen> - <dimen name="chat_bubble_margin_end">62dp</dimen> - <dimen name="chat_bubble_fixed_size">300dp</dimen> - <dimen name="chat_bubble_corner_radius">12dp</dimen> + <dimen name="chat_bubble_margin_end">6dp</dimen> + <dimen name="chat_bubble_fixed_size">350sp</dimen> + <dimen name="chat_bubble_corner_radius">8dp</dimen> <!-- Onboarding --> <item name="ftue_auth_gutter_start_percent" format="float" type="dimen">0.05</item> From a97addfa3be0871438e15e543b8ebebfb4c47660 Mon Sep 17 00:00:00 2001 From: rudmannn <109571086+rudmannn@users.noreply.github.com> Date: Sun, 7 Aug 2022 11:14:10 +0800 Subject: [PATCH 002/187] replace android:gravity for backward compatibility --- vector/src/main/res/drawable/poll_option_checked.xml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/drawable/poll_option_checked.xml b/vector/src/main/res/drawable/poll_option_checked.xml index 28ab94a421..2324792eac 100644 --- a/vector/src/main/res/drawable/poll_option_checked.xml +++ b/vector/src/main/res/drawable/poll_option_checked.xml @@ -10,5 +10,9 @@ </item> <item android:drawable="@drawable/ic_check_on_white" - android:gravity="center" /> -</layer-list> \ No newline at end of file + android:top="2dp" + android:bottom="2dp" + android:left="2dp" + android:right="2dp" + /> +</layer-list> From cd71abeb9fe70777808ed15382ca9248ec1eeef7 Mon Sep 17 00:00:00 2001 From: eidonia <bastien_rambeaud@hotmail.com> Date: Sat, 6 Aug 2022 01:32:21 +0200 Subject: [PATCH 003/187] [Bugfix #5029] disable emoji keyboard not applies to reply Signed-off-by: eidonia <bastien_rambeaud@hotmail.com> --- changelog.d/5029.bugfix | 1 + .../app/features/home/room/detail/TimelineFragment.kt | 6 ++++++ .../home/room/detail/composer/MessageComposerView.kt | 3 +++ vector/src/main/res/layout/composer_layout.xml | 4 +++- .../res/layout/composer_layout_constraint_set_compact.xml | 4 +++- .../res/layout/composer_layout_constraint_set_expanded.xml | 4 +++- 6 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 changelog.d/5029.bugfix diff --git a/changelog.d/5029.bugfix b/changelog.d/5029.bugfix new file mode 100644 index 0000000000..9e8bbd7b7b --- /dev/null +++ b/changelog.d/5029.bugfix @@ -0,0 +1 @@ +Disable emoji keyboard not applies in reply 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 f164183914..f67dca48f6 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 @@ -353,6 +353,8 @@ class TimelineFragment @Inject constructor( private var lockSendButton = false private val currentCallsViewPresenter = CurrentCallsViewPresenter() + private val isEmojiKeyboardVisible: Boolean + get() = vectorPreferences.showEmojiKeyboard() private val lazyLoadedViews = RoomDetailLazyLoadedViews() private val emojiPopup: EmojiPopup by lifecycleAwareLazy { @@ -1571,6 +1573,10 @@ class TimelineFragment @Inject constructor( attachmentTypeSelector.show(views.composerLayout.views.attachmentButton) } + override fun onExpandOrCompactChange() { + views.composerLayout.views.composerEmojiButton.isVisible = isEmojiKeyboardVisible + } + override fun onSendMessage(text: CharSequence) { sendTextMessage(text) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerView.kt index 1522960cc9..b1b2c87e9c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerView.kt @@ -46,6 +46,7 @@ class MessageComposerView @JvmOverloads constructor( fun onCloseRelatedMessage() fun onSendMessage(text: CharSequence) fun onAddAttachment() + fun onExpandOrCompactChange() } val views: ComposerLayoutBinding @@ -96,6 +97,7 @@ class MessageComposerView @JvmOverloads constructor( } currentConstraintSetId = R.layout.composer_layout_constraint_set_compact applyNewConstraintSet(animate, transitionComplete) + callback?.onExpandOrCompactChange() } fun expand(animate: Boolean = true, transitionComplete: (() -> Unit)? = null) { @@ -105,6 +107,7 @@ class MessageComposerView @JvmOverloads constructor( } currentConstraintSetId = R.layout.composer_layout_constraint_set_expanded applyNewConstraintSet(animate, transitionComplete) + callback?.onExpandOrCompactChange() } fun setTextIfDifferent(text: CharSequence?): Boolean { diff --git a/vector/src/main/res/layout/composer_layout.xml b/vector/src/main/res/layout/composer_layout.xml index bed10f7606..fb0d80278a 100644 --- a/vector/src/main/res/layout/composer_layout.xml +++ b/vector/src/main/res/layout/composer_layout.xml @@ -119,8 +119,10 @@ android:background="?android:attr/selectableItemBackground" android:contentDescription="@string/a11y_open_emoji_picker" android:src="@drawable/ic_insert_emoji" + android:visibility="invisible" app:tint="?vctr_content_tertiary" - tools:ignore="MissingConstraints,MissingPrefix" /> + tools:ignore="MissingConstraints,MissingPrefix" + tools:visibility="visible" /> <ImageButton android:id="@+id/sendButton" diff --git a/vector/src/main/res/layout/composer_layout_constraint_set_compact.xml b/vector/src/main/res/layout/composer_layout_constraint_set_compact.xml index 7b37fb9c60..81b978caa6 100644 --- a/vector/src/main/res/layout/composer_layout_constraint_set_compact.xml +++ b/vector/src/main/res/layout/composer_layout_constraint_set_compact.xml @@ -155,13 +155,15 @@ android:background="?android:attr/selectableItemBackground" android:contentDescription="@string/a11y_open_emoji_picker" android:src="@drawable/ic_insert_emoji" + android:visibility="invisible" app:layout_constraintBottom_toBottomOf="@id/attachmentButton" app:layout_constraintEnd_toEndOf="@id/composerEditTextOuterBorder" app:layout_constraintStart_toEndOf="@id/composerEditText" app:layout_constraintTop_toTopOf="@id/attachmentButton" app:layout_goneMarginEnd="8dp" app:tint="?vctr_content_quaternary" - tools:ignore="MissingPrefix" /> + tools:ignore="MissingPrefix" + tools:visibility="visible" /> <ImageButton android:id="@+id/sendButton" diff --git a/vector/src/main/res/layout/composer_layout_constraint_set_expanded.xml b/vector/src/main/res/layout/composer_layout_constraint_set_expanded.xml index 72bdf37911..8cdb388bf9 100644 --- a/vector/src/main/res/layout/composer_layout_constraint_set_expanded.xml +++ b/vector/src/main/res/layout/composer_layout_constraint_set_expanded.xml @@ -166,6 +166,7 @@ android:background="?android:attr/selectableItemBackground" android:contentDescription="@string/a11y_open_emoji_picker" android:src="@drawable/ic_insert_emoji" + android:visibility="invisible" app:layout_constraintBottom_toBottomOf="@id/sendButton" app:layout_constraintEnd_toEndOf="@id/composerEditTextOuterBorder" app:layout_constraintStart_toEndOf="@id/composerEditText" @@ -173,7 +174,8 @@ app:layout_goneMarginBottom="52dp" app:layout_goneMarginEnd="8dp" app:tint="?vctr_content_quaternary" - tools:ignore="MissingPrefix" /> + tools:ignore="MissingPrefix" + tools:visibility="visible" /> <ImageButton android:id="@+id/sendButton" From dccc64384c9f59324c45a1e631edff481667c8a7 Mon Sep 17 00:00:00 2001 From: networkException <git@nwex.de> Date: Tue, 31 May 2022 21:53:47 +0200 Subject: [PATCH 004/187] Implement Mode.ANIMATED_THUMBNAIL used for autoplaying animated images This patch introduces a new `ImageContentRenderer` mode used for autoplaying animated images. The mode shares url resolving semantics with `FULL_SIZE` and `STICKER`, as such not just fetching thumbnail data but shares sizing semantics with `THUMBNAIL` (scaling by image height). This change fixes animated images not playing in cases in which only a static thumbnail would be loaded. This new mode will only be chosen if the message content is actually a playable image, as such limiting bandwith usage to the required amount by avoiding to load normal images fully (still fetching animated images will increase bandwith usage as a whole of course). Signed-off-by: networkException <git@nwex.de> --- .../room/detail/timeline/factory/MessageItemFactory.kt | 9 ++++++++- .../im/vector/app/features/media/ImageContentRenderer.kt | 3 +++ 2 files changed, 11 insertions(+), 1 deletion(-) 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 28e256c064..fece5786fe 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 @@ -453,12 +453,15 @@ class MessageItemFactory @Inject constructor( maxWidth = maxWidth, allowNonMxcUrls = informationData.sendState.isSending() ) + + val playable = messageContent.mimeType == MimeTypes.Gif + return MessageImageVideoItem_() .attributes(attributes) .leftGuideline(avatarSizeProvider.leftGuideline) .imageContentRenderer(imageContentRenderer) .contentUploadStateTrackerBinder(contentUploadStateTrackerBinder) - .playable(messageContent.mimeType == MimeTypes.Gif) + .playable(playable) .highlighted(highlight) .mediaData(data) .apply { @@ -472,6 +475,10 @@ class MessageItemFactory @Inject constructor( callback?.onImageMessageClicked(messageContent, data, view, emptyList()) } } + }.apply { + if (playable && vectorPreferences.autoplayAnimatedImages()) { + mode(ImageContentRenderer.Mode.ANIMATED_THUMBNAIL) + } } } diff --git a/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt b/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt index 74c9b5c6b8..65d37b0d0d 100644 --- a/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt +++ b/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt @@ -85,6 +85,7 @@ class ImageContentRenderer @Inject constructor( enum class Mode { FULL_SIZE, + ANIMATED_THUMBNAIL, THUMBNAIL, STICKER } @@ -231,6 +232,7 @@ class ImageContentRenderer @Inject constructor( val contentUrlResolver = activeSessionHolder.getActiveSession().contentUrlResolver() val resolvedUrl = when (mode) { Mode.FULL_SIZE, + Mode.ANIMATED_THUMBNAIL, Mode.STICKER -> resolveUrl(data) Mode.THUMBNAIL -> contentUrlResolver.resolveThumbnail(data.url, size.width, size.height, ContentUrlResolver.ThumbnailMethod.SCALE) } @@ -269,6 +271,7 @@ class ImageContentRenderer @Inject constructor( finalHeight = height finalWidth = width } + Mode.ANIMATED_THUMBNAIL, Mode.THUMBNAIL -> { finalHeight = min(maxImageWidth * height / width, maxImageHeight) finalWidth = finalHeight * width / height From 5fede491ee9d7bc0ce449d15b00ccca8e484bde9 Mon Sep 17 00:00:00 2001 From: networkException <git@nwex.de> Date: Tue, 31 May 2022 22:05:05 +0200 Subject: [PATCH 005/187] ImageContentRenderer: Only animate images using ANIMATED_THUMBNAIL mode This patch removes the dependency on `VectorSettings` as well as only enable animated rendering if the image is actually playable. Signed-off-by: networkException <git@nwex.de> --- .../java/im/vector/app/features/media/ImageContentRenderer.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt b/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt index 65d37b0d0d..333b1f0eae 100644 --- a/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt +++ b/vector/src/main/java/im/vector/app/features/media/ImageContentRenderer.kt @@ -38,7 +38,6 @@ import im.vector.app.core.glide.GlideRequest import im.vector.app.core.glide.GlideRequests import im.vector.app.core.ui.model.Size import im.vector.app.core.utils.DimensionConverter -import im.vector.app.features.settings.VectorPreferences import kotlinx.parcelize.Parcelize import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.content.ContentUrlResolver @@ -65,7 +64,6 @@ class ImageContentRenderer @Inject constructor( private val localFilesHelper: LocalFilesHelper, private val activeSessionHolder: ActiveSessionHolder, private val dimensionConverter: DimensionConverter, - private val vectorPreferences: VectorPreferences ) { @Parcelize @@ -134,7 +132,7 @@ class ImageContentRenderer @Inject constructor( createGlideRequest(data, mode, imageView, size) .let { - if (vectorPreferences.autoplayAnimatedImages()) it + if (mode == Mode.ANIMATED_THUMBNAIL) it else it.dontAnimate() } .transform(cornerTransformation) From 2bca94d92b57987cc61b9a3a32ad5a6e1028a841 Mon Sep 17 00:00:00 2001 From: networkException <git@nwex.de> Date: Tue, 31 May 2022 22:48:17 +0200 Subject: [PATCH 006/187] Changelog: Add .bugfix entry for pull request #6215 Signed-off-by: networkException <git@nwex.de> --- changelog.d/6215.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/6215.bugfix diff --git a/changelog.d/6215.bugfix b/changelog.d/6215.bugfix new file mode 100644 index 0000000000..5291d7d604 --- /dev/null +++ b/changelog.d/6215.bugfix @@ -0,0 +1 @@ +Fix animated images not autoplaying sometimes if only a thumbnail was fetched from the server From 6452a95e0a19d84c3fb898541c033e000be576ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artjom=20K=C3=B6nig?= <artjom.koenig.ext@bwi.de> Date: Mon, 22 Aug 2022 13:36:53 +0200 Subject: [PATCH 007/187] clean up pin code and biometrics on logout --- changelog.d/6906.bugfix | 1 + .../src/main/java/im/vector/app/features/MainActivity.kt | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 changelog.d/6906.bugfix diff --git a/changelog.d/6906.bugfix b/changelog.d/6906.bugfix new file mode 100644 index 0000000000..9b6a76f5cb --- /dev/null +++ b/changelog.d/6906.bugfix @@ -0,0 +1 @@ +Delete pin code key and the key used for biometrics authentication on logout diff --git a/vector/src/main/java/im/vector/app/features/MainActivity.kt b/vector/src/main/java/im/vector/app/features/MainActivity.kt index 14fae80325..cb77911314 100644 --- a/vector/src/main/java/im/vector/app/features/MainActivity.kt +++ b/vector/src/main/java/im/vector/app/features/MainActivity.kt @@ -41,9 +41,10 @@ import im.vector.app.features.analytics.VectorAnalytics import im.vector.app.features.home.HomeActivity import im.vector.app.features.home.ShortcutsHandler import im.vector.app.features.notifications.NotificationDrawerManager -import im.vector.app.features.pin.PinCodeStore import im.vector.app.features.pin.PinLocker import im.vector.app.features.pin.UnlockedActivity +import im.vector.app.features.pin.lockscreen.crypto.LockScreenKeyRepository +import im.vector.app.features.pin.lockscreen.pincode.PinCodeHelper import im.vector.app.features.popup.PopupAlertManager import im.vector.app.features.session.VectorSessionStore import im.vector.app.features.settings.VectorPreferences @@ -133,10 +134,11 @@ class MainActivity : VectorBaseActivity<ActivityMainBinding>(), UnlockedActivity @Inject lateinit var vectorPreferences: VectorPreferences @Inject lateinit var uiStateRepository: UiStateRepository @Inject lateinit var shortcutsHandler: ShortcutsHandler - @Inject lateinit var pinCodeStore: PinCodeStore + @Inject lateinit var pinCodeHelper: PinCodeHelper @Inject lateinit var pinLocker: PinLocker @Inject lateinit var popupAlertManager: PopupAlertManager @Inject lateinit var vectorAnalytics: VectorAnalytics + @Inject lateinit var lockScreenKeyRepository: LockScreenKeyRepository override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -284,9 +286,10 @@ class MainActivity : VectorBaseActivity<ActivityMainBinding>(), UnlockedActivity vectorPreferences.clearPreferences() uiStateRepository.reset() pinLocker.unlock() - pinCodeStore.deletePinCode() + pinCodeHelper.deletePinCode() vectorAnalytics.onSignOut() vectorSessionStore.clear() + lockScreenKeyRepository.deleteSystemKey() } withContext(Dispatchers.IO) { // On BG thread From 33e613fe963f06c59abcf5c01e9f95f1ce82fee6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 22 Sep 2022 14:31:31 +0000 Subject: [PATCH 008/187] Bump dagger from 2.42 to 2.44 Bumps `dagger` from 2.42 to 2.44. Updates `hilt-android-gradle-plugin` from 2.42 to 2.44 - [Release notes](https://github.com/google/dagger/releases) - [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md) - [Commits](https://github.com/google/dagger/compare/dagger-2.42...dagger-2.44) Updates `dagger` from 2.42 to 2.44 - [Release notes](https://github.com/google/dagger/releases) - [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md) - [Commits](https://github.com/google/dagger/compare/dagger-2.42...dagger-2.44) Updates `dagger-compiler` from 2.42 to 2.44 - [Release notes](https://github.com/google/dagger/releases) - [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md) - [Commits](https://github.com/google/dagger/compare/dagger-2.42...dagger-2.44) Updates `hilt-android` from 2.42 to 2.44 - [Release notes](https://github.com/google/dagger/releases) - [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md) - [Commits](https://github.com/google/dagger/compare/dagger-2.42...dagger-2.44) Updates `hilt-android-testing` from 2.42 to 2.44 - [Release notes](https://github.com/google/dagger/releases) - [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md) - [Commits](https://github.com/google/dagger/compare/dagger-2.42...dagger-2.44) Updates `hilt-compiler` from 2.42 to 2.44 - [Release notes](https://github.com/google/dagger/releases) - [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md) - [Commits](https://github.com/google/dagger/compare/dagger-2.42...dagger-2.44) --- updated-dependencies: - dependency-name: com.google.dagger:hilt-android-gradle-plugin dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: com.google.dagger:dagger dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: com.google.dagger:dagger-compiler dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: com.google.dagger:hilt-android dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: com.google.dagger:hilt-android-testing dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: com.google.dagger:hilt-compiler dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index fd758dd859..c4dba43cf0 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -11,7 +11,7 @@ def gradle = "7.2.2" // Ref: https://kotlinlang.org/releases.html def kotlin = "1.7.10" def kotlinCoroutines = "1.6.4" -def dagger = "2.43.2" +def dagger = "2.44" def appDistribution = "16.0.0-beta04" def retrofit = "2.9.0" def arrow = "0.8.2" From 75935c824baa57f84e2be89a8134a44e8b6721ea Mon Sep 17 00:00:00 2001 From: ericdecanini <eddecanini@gmail.com> Date: Thu, 22 Sep 2022 20:35:04 -0400 Subject: [PATCH 009/187] Implements MSC3881 (enabled and device_id fields for Pusher API) --- .../sdk/api/session/pushers/HttpPusher.kt | 4 ++++ .../android/sdk/api/session/pushers/Pusher.kt | 5 +++-- .../internal/database/mapper/PushersMapper.kt | 8 ++++++-- .../sdk/internal/database/model/PusherEntity.kt | 13 +++---------- .../internal/session/pushers/AddPusherTask.kt | 2 ++ .../session/pushers/DefaultPushersService.kt | 4 +++- .../sdk/internal/session/pushers/JsonPusher.kt | 16 +++++++++++++++- .../im/vector/app/core/pushers/PushersManager.kt | 2 ++ 8 files changed, 38 insertions(+), 16 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/HttpPusher.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/HttpPusher.kt index 1ae23e2b70..4110bb0a42 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/HttpPusher.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/HttpPusher.kt @@ -58,6 +58,10 @@ data class HttpPusher( */ val url: String, + val enabled: Boolean, + + val deviceId: String, + /** * If true, the homeserver should add another pusher with the given pushkey and App ID in addition * to any others with different user IDs. Otherwise, the homeserver must remove any other pushers diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/Pusher.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/Pusher.kt index b85ab32b21..92ac6c483b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/Pusher.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/Pusher.kt @@ -24,8 +24,9 @@ data class Pusher( val profileTag: String? = null, val lang: String?, val data: PusherData, - - val state: PusherState + val enabled: Boolean, + val deviceId: String?, + val state: PusherState, ) { companion object { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/PushersMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/PushersMapper.kt index 2dba2c228b..c3a37f5b95 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/PushersMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/PushersMapper.kt @@ -33,7 +33,9 @@ internal object PushersMapper { profileTag = pushEntity.profileTag, lang = pushEntity.lang, data = PusherData(pushEntity.data?.url, pushEntity.data?.format), - state = pushEntity.state + enabled = pushEntity.enabled, + deviceId = pushEntity.deviceId, + state = pushEntity.state, ) } @@ -46,7 +48,9 @@ internal object PushersMapper { deviceDisplayName = pusher.deviceDisplayName, profileTag = pusher.profileTag, lang = pusher.lang, - data = PusherDataEntity(pusher.data?.url, pusher.data?.format) + data = PusherDataEntity(pusher.data?.url, pusher.data?.format), + enabled = pusher.enabled, + deviceId = pusher.deviceId, ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PusherEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PusherEntity.kt index af8e4f2d37..c08f695168 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PusherEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PusherEntity.kt @@ -18,15 +18,6 @@ package org.matrix.android.sdk.internal.database.model import io.realm.RealmObject import org.matrix.android.sdk.api.session.pushers.PusherState -// TODO -// at java.lang.Thread.run(Thread.java:764) -// Caused by: java.lang.IllegalArgumentException: 'value' is not a valid managed object. -// at io.realm.ProxyState.checkValidObject(ProxyState.java:213) -// at io.realm.im_vector_matrix_android_internal_database_model_PusherEntityRealmProxy -// .realmSet$data(im_vector_matrix_android_internal_database_model_PusherEntityRealmProxy.java:413) -// at org.matrix.android.sdk.internal.database.model.PusherEntity.setData(PusherEntity.kt:16) -// at org.matrix.android.sdk.internal.session.pushers.AddHttpPusherWorker$doWork$$inlined$fold$lambda$2.execute(AddHttpPusherWorker.kt:70) -// at io.realm.Realm.executeTransaction(Realm.java:1493) internal open class PusherEntity( var pushKey: String = "", var kind: String? = null, @@ -35,7 +26,9 @@ internal open class PusherEntity( var deviceDisplayName: String? = null, var profileTag: String? = null, var lang: String? = null, - var data: PusherDataEntity? = null + var data: PusherDataEntity? = null, + var enabled: Boolean = true, + var deviceId: String? = null, ) : RealmObject() { private var stateStr: String = PusherState.UNREGISTERED.name diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherTask.kt index 7d81e19265..c284f006ca 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherTask.kt @@ -71,6 +71,8 @@ internal class DefaultAddPusherTask @Inject constructor( echo.profileTag = pusher.profileTag echo.data?.format = pusher.data?.format echo.data?.url = pusher.data?.url + echo.enabled = pusher.enabled + echo.deviceId = pusher.deviceId echo.state = PusherState.REGISTERED } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt index e912d9ccf8..082b5b63eb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt @@ -78,7 +78,9 @@ internal class DefaultPushersService @Inject constructor( appDisplayName = appDisplayName, deviceDisplayName = deviceDisplayName, data = JsonPusherData(url, EVENT_ID_ONLY.takeIf { withEventIdOnly }), - append = append + append = append, + enabled = enabled, + deviceId = deviceId, ) override suspend fun addEmailPusher( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/JsonPusher.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/JsonPusher.kt index 71a1ea8c66..f009549b5a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/JsonPusher.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/JsonPusher.kt @@ -33,6 +33,8 @@ import java.security.InvalidParameterException * "device_display_name": "Alice's Phone", * "profile_tag": "xyz", * "lang": "en-US", + * "enabled": true, + * "device_id": "abc123", * "data": { * "url": "https://example.com/_matrix/push/v1/notify" * } @@ -112,7 +114,19 @@ internal data class JsonPusher( * The default is false. */ @Json(name = "append") - val append: Boolean? = false + val append: Boolean? = false, + + /** + * Whether the pusher should actively create push notifications + */ + @Json(name = "org.matrix.msc3881.enabled") + val enabled: Boolean = false, + + /** + * The device_id of the session that registered the pusher + */ + @Json(name = "org.matrix.msc3881.device_id") + val deviceId: String? = null, ) { init { // Do some parameter checks. It's ok to throw Exception, to inform developer of the problem diff --git a/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt b/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt index c77f454ab0..8be9b192fe 100644 --- a/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt +++ b/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt @@ -70,6 +70,8 @@ class PushersManager @Inject constructor( appNameProvider.getAppName(), activeSessionHolder.getActiveSession().sessionParams.deviceId ?: "MOBILE", gateway, + enabled = true, + deviceId = activeSessionHolder.getActiveSession().sessionParams.deviceId ?: "MOBILE", append = false, withEventIdOnly = true ) From 5c27f65d3e8d5558aa00ad565da8f97ef5c29579 Mon Sep 17 00:00:00 2001 From: ericdecanini <eddecanini@gmail.com> Date: Thu, 22 Sep 2022 20:43:37 -0400 Subject: [PATCH 010/187] Adds changelog file --- changelog.d/7217.wip | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7217.wip diff --git a/changelog.d/7217.wip b/changelog.d/7217.wip new file mode 100644 index 0000000000..a8cc2a3ef3 --- /dev/null +++ b/changelog.d/7217.wip @@ -0,0 +1 @@ +Implements MSC3881: Parses `enabled` and `device_id` fields from updated Pusher API From 8c7901177eb83dec5a2ca19728a63b918214ecdd Mon Sep 17 00:00:00 2001 From: ericdecanini <eddecanini@gmail.com> Date: Thu, 22 Sep 2022 20:44:46 -0400 Subject: [PATCH 011/187] Adds comments to new fields in HttpPusher --- .../matrix/android/sdk/api/session/pushers/HttpPusher.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/HttpPusher.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/HttpPusher.kt index 4110bb0a42..c4ec57a766 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/HttpPusher.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/HttpPusher.kt @@ -58,8 +58,14 @@ data class HttpPusher( */ val url: String, + /** + * Whether the pusher should actively create push notifications + */ val enabled: Boolean, + /** + * The device ID of the session that registered the pusher + */ val deviceId: String, /** From b60bb295adb508a7cb8bb65cef5ba63c31b1035b Mon Sep 17 00:00:00 2001 From: "Auri B. P" <auri97@gmail.com> Date: Thu, 22 Sep 2022 09:38:20 +0000 Subject: [PATCH 012/187] Translated using Weblate (Catalan) Currently translated at 100.0% (2419 of 2419 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ca/ --- .../src/main/res/values-ca/strings.xml | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/library/ui-strings/src/main/res/values-ca/strings.xml b/library/ui-strings/src/main/res/values-ca/strings.xml index 863fa13fbb..25c490807e 100644 --- a/library/ui-strings/src/main/res/values-ca/strings.xml +++ b/library/ui-strings/src/main/res/values-ca/strings.xml @@ -2674,4 +2674,40 @@ <string name="device_manager_verification_status_detail_other_session_verified">Aquesta sessió està llesta per a missatges segurs.</string> <string name="device_manager_verification_status_detail_current_session_verified">La teva sessió actual està llesta per a missatges segurs.</string> <string name="device_manager_verification_status_detail_current_session_unverified">Verifica la teva sessió actual obtenir missatges segurs millorats.</string> + <string name="labs_enable_deferred_dm_summary">Crea missatge directe només al primer missatge</string> + <string name="labs_enable_deferred_dm_title">Activa missatges directes programats</string> + <string name="device_manager_verification_status_detail_other_session_unverified">Verifica o tanca aquesta sessió per estar més segur.</string> + <string name="device_manager_other_sessions_recommendation_description_verified">Per estar més segur, tanca qualsevol sessió que no reconeguis o ja no utilitzis.</string> + <string name="device_manager_other_sessions_no_inactive_sessions_found">No s\'han trobat sessions inactives.</string> + <string name="device_manager_other_sessions_no_unverified_sessions_found">No s\'han trobat sessions no verificades.</string> + <string name="device_manager_other_sessions_no_verified_sessions_found">No s\'han trobat sessions verificades.</string> + <string name="device_manager_session_details_title">Detalls de sessió</string> + <string name="device_manager_other_sessions_clear_filter">Esborra filtre</string> + <string name="device_manager_session_details_session_last_activity">Última activitat</string> + <string name="device_manager_session_details_session_name">Nom de la sessió</string> + <string name="device_manager_session_details_description">Informació d\'aplicació, dispositiu i activitat.</string> + <string name="device_manager_session_details_device_ip_address">Adreça IP</string> + <plurals name="device_manager_other_sessions_recommendation_description_inactive"> + <item quantity="one">Pensa en tancar sessió de les sessions antigues (%1$d dia o més) que ja no utilitzis.</item> + <item quantity="other">Pensa en tancar sessió de les sessions antigues (%1$d dies o més) que ja no utilitzis.</item> + </plurals> + <string name="device_manager_other_sessions_recommendation_title_inactive">Inactiu</string> + <string name="device_manager_other_sessions_recommendation_title_unverified">No verificat</string> + <string name="device_manager_other_sessions_recommendation_title_verified">Verificat</string> + <string name="a11y_device_manager_filter">Filtra</string> + <plurals name="device_manager_filter_option_inactive_description"> + <item quantity="one">Inactiu durant %1$d dia o més</item> + <item quantity="other">Inactiu durant %1$d dies o més</item> + </plurals> + <string name="device_manager_filter_option_inactive">Inactiu</string> + <string name="device_manager_filter_option_unverified">No verificat</string> + <string name="device_manager_filter_option_verified">Verificat</string> + <string name="device_manager_filter_option_all_sessions">Totes les sessions</string> + <string name="device_manager_filter_bottom_sheet_title">Filtre</string> + <string name="device_manager_session_last_activity">Última activitat %1$s</string> + <string name="device_manager_device_title">Dispositiu</string> + <string name="device_manager_session_title">Sessió</string> + <string name="device_manager_current_session_title">Sessió actual</string> + <string name="labs_enable_new_app_layout_summary">Element simplificat amb pestanyes opcionals</string> + <string name="labs_enable_new_app_layout_title">Activa la nova visualització</string> </resources> \ No newline at end of file From 204f36dc3d07f49f8b12dd367a8e9ed5facc39ed Mon Sep 17 00:00:00 2001 From: waclaw66 <waclaw66@seznam.cz> Date: Thu, 22 Sep 2022 08:53:08 +0000 Subject: [PATCH 013/187] Translated using Weblate (Czech) Currently translated at 100.0% (2419 of 2419 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/cs/ --- .../src/main/res/values-cs/strings.xml | 46 ++++++++++++++++++- 1 file changed, 45 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 79f8311159..1983036271 100644 --- a/library/ui-strings/src/main/res/values-cs/strings.xml +++ b/library/ui-strings/src/main/res/values-cs/strings.xml @@ -2720,4 +2720,48 @@ <string name="a11y_collapse_space_children">Sbalit podprostory %s</string> <string name="a11y_expand_space_children">Rozbalit podprostory %s</string> <string name="change_space">Změnit prostor</string> -</resources> + <string name="device_manager_session_details_device_ip_address">IP adresa</string> + <string name="device_manager_session_details_session_last_activity">Poslední aktivita</string> + <string name="device_manager_session_details_session_name">Název relace</string> + <string name="device_manager_session_details_description">Informace o aplikacích, zařízeních a aktivitách.</string> + <string name="device_manager_session_details_title">Podrobnosti o relaci</string> + <string name="device_manager_other_sessions_clear_filter">Vyčistit filtr</string> + <string name="device_manager_other_sessions_no_inactive_sessions_found">Nebyly nalezeny žádné neaktivní relace.</string> + <string name="device_manager_other_sessions_no_unverified_sessions_found">Nebyly nalezeny žádné neověřené relace.</string> + <string name="device_manager_other_sessions_no_verified_sessions_found">Nebyly nalezeny žádné ověřené relace.</string> + <plurals name="device_manager_other_sessions_recommendation_description_inactive"> + <item quantity="one">Zvažte odhlášení ze starých relací (%1$d den nebo více), které již nepoužíváte.</item> + <item quantity="few">Zvažte odhlášení ze starých relací (%1$d dny nebo více), které již nepoužíváte.</item> + <item quantity="other">Zvažte odhlášení ze starých relací (%1$d dnů nebo více), které již nepoužíváte.</item> + </plurals> + <string name="device_manager_other_sessions_recommendation_title_inactive">Neaktivní</string> + <string name="device_manager_other_sessions_recommendation_description_unverified">Ověřte své relace pro vylepšené bezpečné zasílání zpráv nebo se odhlaste z těch, které již nepoznáváte nebo nepoužíváte.</string> + <string name="device_manager_other_sessions_recommendation_title_unverified">Neověřeno</string> + <string name="device_manager_other_sessions_recommendation_description_verified">Pro nejlepší zabezpečení se odhlaste z každé relace, kterou již nepoznáváte nebo nepoužíváte.</string> + <string name="device_manager_other_sessions_recommendation_title_verified">Ověřeno</string> + <string name="a11y_device_manager_filter">Filtr</string> + <plurals name="device_manager_filter_option_inactive_description"> + <item quantity="one">Neaktivní po dobu %1$d dne nebo déle</item> + <item quantity="few">Neaktivní po dobu %1$d dnů nebo déle</item> + <item quantity="other">Neaktivní po dobu %1$d dnů nebo déle</item> + </plurals> + <string name="device_manager_filter_option_inactive">Neaktivní</string> + <string name="device_manager_filter_option_unverified_description">Není připraveno na bezpečné zasílání zpráv</string> + <string name="device_manager_filter_option_unverified">Neověřeno</string> + <string name="device_manager_filter_option_verified_description">Připraveno na bezpečné zasílání zpráv</string> + <string name="device_manager_filter_option_verified">Ověřeno</string> + <string name="device_manager_filter_option_all_sessions">Všechny relace</string> + <string name="device_manager_filter_bottom_sheet_title">Filtr</string> + <string name="device_manager_session_last_activity">Poslední aktivita %1$s</string> + <string name="device_manager_device_title">Zařízení</string> + <string name="device_manager_session_title">Relace</string> + <string name="device_manager_current_session_title">Aktuální relace</string> + <string name="device_manager_verification_status_detail_other_session_unverified">Pro nejlepší zabezpečení a spolehlivost tuto relaci ověřte nebo se z ní odhlaste.</string> + <string name="device_manager_verification_status_detail_current_session_unverified">Ověřte svou aktuální relaci pro vylepšené bezpečené zasílání zpráv.</string> + <string name="device_manager_verification_status_detail_other_session_verified">Tato relace je připravena pro bezpečné zasílání zpráv.</string> + <string name="device_manager_verification_status_detail_current_session_verified">Vaše aktuální relace je připravena pro bezpečné zasílání zpráv.</string> + <string name="labs_enable_deferred_dm_summary">Vytvořit přímou zprávu pouze při první zprávě</string> + <string name="labs_enable_deferred_dm_title">Povolit odložené přímé zprávy</string> + <string name="labs_enable_new_app_layout_summary">Zjednodušený Element s volitelnými kartami</string> + <string name="labs_enable_new_app_layout_title">Povolit nový vzhled</string> +</resources> \ No newline at end of file From 589ccc142e3d8a67796b2e301eb2ea74d302eb21 Mon Sep 17 00:00:00 2001 From: Danial Behzadi <dani.behzi@ubuntu.com> Date: Thu, 22 Sep 2022 08:43:15 +0000 Subject: [PATCH 014/187] Translated using Weblate (Persian) Currently translated at 99.9% (2417 of 2419 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 | 5 +++++ 1 file changed, 5 insertions(+) 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 400a8121f9..9012bc2ebe 100644 --- a/library/ui-strings/src/main/res/values-fa/strings.xml +++ b/library/ui-strings/src/main/res/values-fa/strings.xml @@ -2700,4 +2700,9 @@ <string name="labs_enable_deferred_dm_summary">ایجاد پیام خصوصی فقط در نخستین پیام</string> <string name="labs_enable_new_app_layout_summary">المنتی ساده شده با زبانههای انتخابی</string> <string name="labs_enable_new_app_layout_title">به کار انداختن چینش جدید</string> + <string name="device_manager_other_sessions_recommendation_description_unverified">تأیید نشستهایتان برای پیامرسانی امن بهبود یافته یا خروج از آنهایی که تشخیصشان نداده یا دیگر استفاده نمیکنید.</string> + <plurals name="device_manager_filter_option_inactive_description"> + <item quantity="one">غیرفعّال برای ۱ روز یا بیشتر</item> + <item quantity="other">غیرفعّال برای %1$d روز یا بیشتر</item> + </plurals> </resources> \ No newline at end of file From ffa3e4b842728849b8ca55c886177a1b14b6f459 Mon Sep 17 00:00:00 2001 From: Glandos <bugs-github@antipoul.fr> Date: Fri, 23 Sep 2022 12:02:14 +0000 Subject: [PATCH 015/187] Translated using Weblate (French) Currently translated at 100.0% (2419 of 2419 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fr/ --- .../src/main/res/values-fr/strings.xml | 44 ++++++++++++++++++- 1 file changed, 43 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 5a19ccf2da..c7100e3a1e 100644 --- a/library/ui-strings/src/main/res/values-fr/strings.xml +++ b/library/ui-strings/src/main/res/values-fr/strings.xml @@ -2668,4 +2668,46 @@ <string name="a11y_collapse_space_children">Réduire %s enfants</string> <string name="a11y_expand_space_children">Développer %s enfants</string> <string name="change_space">Changer d’espace</string> -</resources> + <string name="device_manager_session_details_device_ip_address">Adresse IP</string> + <string name="device_manager_session_details_session_last_activity">Dernière activité</string> + <string name="device_manager_session_details_session_name">Nom de la session</string> + <string name="device_manager_session_details_description">Application, appareil et information sur l’activité.</string> + <string name="device_manager_session_details_title">Détails de session</string> + <string name="device_manager_other_sessions_clear_filter">Supprimer les filtres</string> + <string name="device_manager_other_sessions_no_inactive_sessions_found">Aucune session inactive n’a été trouvée.</string> + <string name="device_manager_other_sessions_no_unverified_sessions_found">Aucune session non vérifiée n’a été trouvée.</string> + <string name="device_manager_other_sessions_no_verified_sessions_found">Aucune session vérifiée n’a été trouvée.</string> + <plurals name="device_manager_other_sessions_recommendation_description_inactive"> + <item quantity="one">Pensez à vous déconnecter des anciennes sessions (%1$d jour ou plus) que vous n’utilisez plus.</item> + <item quantity="other">Pensez à vous déconnecter des anciennes sessions (%1$d jours ou plus) que vous n’utilisez plus.</item> + </plurals> + <string name="device_manager_other_sessions_recommendation_title_inactive">Inactif</string> + <string name="device_manager_other_sessions_recommendation_description_unverified">Vérifiez vos sessions pour améliorer la sécurité de votre messagerie, ou déconnectez celles que vous ne connaissez pas ou n’utilisez plus.</string> + <string name="device_manager_other_sessions_recommendation_title_unverified">Non vérifié</string> + <string name="device_manager_other_sessions_recommendation_description_verified">Pour une meilleure sécurité, déconnectez toutes les sessions que vous ne connaissez pas ou que vous n’utilisez plus.</string> + <string name="device_manager_other_sessions_recommendation_title_verified">Vérifié</string> + <string name="a11y_device_manager_filter">Filtrer</string> + <plurals name="device_manager_filter_option_inactive_description"> + <item quantity="one">Inactif depuis %1$d jour ou plus</item> + <item quantity="other">Inactif depuis %1$d jours ou plus</item> + </plurals> + <string name="device_manager_filter_option_inactive">Inactif</string> + <string name="device_manager_filter_option_unverified_description">Pas prêt pour une messagerie sécurisée</string> + <string name="device_manager_filter_option_unverified">Non vérifié</string> + <string name="device_manager_filter_option_verified_description">Prêt pour une messagerie sécurisée</string> + <string name="device_manager_filter_option_verified">Vérifié</string> + <string name="device_manager_filter_option_all_sessions">Toutes les sessions</string> + <string name="device_manager_filter_bottom_sheet_title">Filtrer</string> + <string name="device_manager_session_last_activity">Dernière activité %1$s</string> + <string name="device_manager_device_title">Appareil</string> + <string name="device_manager_session_title">Session</string> + <string name="device_manager_current_session_title">Cette session</string> + <string name="device_manager_verification_status_detail_other_session_unverified">Vérifiez ou déconnectez cette session pour une meilleure sécurité et fiabilité.</string> + <string name="device_manager_verification_status_detail_current_session_unverified">Vérifiez votre session pour une sécurité accrue de votre messagerie.</string> + <string name="device_manager_verification_status_detail_other_session_verified">Cette session est prête pour l’envoi de messages sécurisés.</string> + <string name="device_manager_verification_status_detail_current_session_verified">Votre session est prête pour l’envoi de messages sécurisés.</string> + <string name="labs_enable_deferred_dm_summary">Créer la conversation seulement lors du premier message</string> + <string name="labs_enable_deferred_dm_title">Activer les conversations privées différées</string> + <string name="labs_enable_new_app_layout_summary">Un Element simplifié avec des onglets optionnels</string> + <string name="labs_enable_new_app_layout_title">Activer la nouvelle présentation</string> +</resources> \ No newline at end of file From 036620592618c95a1b8f232dd3dc89c0bcb7fea4 Mon Sep 17 00:00:00 2001 From: Linerly <linerly@protonmail.com> Date: Fri, 23 Sep 2022 03:14:37 +0000 Subject: [PATCH 016/187] Translated using Weblate (Indonesian) Currently translated at 100.0% (2419 of 2419 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/id/ --- .../src/main/res/values-in/strings.xml | 42 ++++++++++++++++++- 1 file changed, 41 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 3b30950bd1..c608d82c85 100644 --- a/library/ui-strings/src/main/res/values-in/strings.xml +++ b/library/ui-strings/src/main/res/values-in/strings.xml @@ -2618,4 +2618,44 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.</string> <string name="a11y_collapse_space_children">Tutup %s anak</string> <string name="a11y_expand_space_children">Buka %s anak</string> <string name="change_space">Buat Space</string> -</resources> + <string name="device_manager_session_details_device_ip_address">Alamat IP</string> + <string name="device_manager_session_details_session_last_activity">Aktivitas terakhir</string> + <string name="device_manager_session_details_session_name">Nama sesi</string> + <string name="device_manager_session_details_description">Informasi aplikasi, perangkat, dan aktivitas.</string> + <string name="device_manager_session_details_title">Detail sesi</string> + <string name="device_manager_other_sessions_clear_filter">Hapus Saringan</string> + <string name="device_manager_other_sessions_no_inactive_sessions_found">Tidak ditemukan sesi yang tidak aktif.</string> + <string name="device_manager_other_sessions_no_unverified_sessions_found">Tidak ditemukan sesi yang belum diverifikasi.</string> + <string name="device_manager_other_sessions_no_verified_sessions_found">Tidak ditemukan sesi yang terverifikasi.</string> + <plurals name="device_manager_other_sessions_recommendation_description_inactive"> + <item quantity="other">Pertimbangkan untuk mengeluarkan sesi lawas (%1$d hari atau lebih) yang Anda tidak gunakan lagi.</item> + </plurals> + <string name="device_manager_other_sessions_recommendation_title_inactive">Tidak aktif</string> + <string name="device_manager_other_sessions_recommendation_description_unverified">Verifikasi sesi Anda untuk perpesanan aman yang terbaik atau keluarkan sesi yang Anda tidak kenal atau gunakan lagi.</string> + <string name="device_manager_other_sessions_recommendation_title_unverified">Belum diverifikasi</string> + <string name="device_manager_other_sessions_recommendation_description_verified">Untuk keamanan yang terbaik, keluarkan sesi yang Anda tidak kenal atau gunakan lagi.</string> + <string name="device_manager_other_sessions_recommendation_title_verified">Terverifikasi</string> + <string name="a11y_device_manager_filter">Saring</string> + <plurals name="device_manager_filter_option_inactive_description"> + <item quantity="other">Tidak aktif selama %1$d hari atau lebih</item> + </plurals> + <string name="device_manager_filter_option_inactive">Tidak aktif</string> + <string name="device_manager_filter_option_unverified_description">Belum siap untuk perpesanan aman</string> + <string name="device_manager_filter_option_unverified">Belum diverifikasi</string> + <string name="device_manager_filter_option_verified_description">Siap untuk perpesanan aman</string> + <string name="device_manager_filter_option_verified">Terverifikasi</string> + <string name="device_manager_filter_option_all_sessions">Semua sesi</string> + <string name="device_manager_filter_bottom_sheet_title">Saring</string> + <string name="device_manager_session_last_activity">Aktivitas terakhir %1$s</string> + <string name="device_manager_device_title">Perangkat</string> + <string name="device_manager_session_title">Sesi</string> + <string name="device_manager_current_session_title">Sesi Saat Ini</string> + <string name="device_manager_verification_status_detail_other_session_unverified">Verifikasi atau keluarkan sesi ini untuk keamanan dan keandalan yang terbaik.</string> + <string name="device_manager_verification_status_detail_current_session_unverified">Verifikasi sesi Anda saat ini untuk perpesanan aman yang baik.</string> + <string name="device_manager_verification_status_detail_other_session_verified">Sesi ini siap untuk perpesanan aman.</string> + <string name="device_manager_verification_status_detail_current_session_verified">Sesi Anda saat ini siap untuk perpesanan aman.</string> + <string name="labs_enable_deferred_dm_summary">Buat pesan langsung hanya pada pesan pertama</string> + <string name="labs_enable_deferred_dm_title">Aktifkan pesan langsung tangguhan</string> + <string name="labs_enable_new_app_layout_summary">Sebuah Element yang sederhana dengan fitur tab opsional</string> + <string name="labs_enable_new_app_layout_title">Aktifkan tata letak baru</string> +</resources> \ No newline at end of file From 063b080d2cdf1968575645e11b1966b838cd5b6d Mon Sep 17 00:00:00 2001 From: random <dictionary@tutamail.com> Date: Thu, 22 Sep 2022 14:02:12 +0000 Subject: [PATCH 017/187] Translated using Weblate (Italian) Currently translated at 100.0% (2419 of 2419 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/it/ --- .../src/main/res/values-it/strings.xml | 44 ++++++++++++++++++- 1 file changed, 43 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 b7b0fe91af..b2f9fa9238 100644 --- a/library/ui-strings/src/main/res/values-it/strings.xml +++ b/library/ui-strings/src/main/res/values-it/strings.xml @@ -2659,4 +2659,46 @@ <string name="a11y_collapse_space_children">Riduci contenuto di %s</string> <string name="a11y_expand_space_children">Espandi contenuto di %s</string> <string name="change_space">Cambia spazio</string> -</resources> + <string name="device_manager_session_details_device_ip_address">Indirizzo IP</string> + <string name="device_manager_session_details_session_last_activity">Ultima attività</string> + <string name="device_manager_session_details_session_name">Nome sessione</string> + <string name="device_manager_session_details_description">Applicazione, dispositivo e informazioni di attività.</string> + <string name="device_manager_session_details_title">Dettagli sessione</string> + <string name="device_manager_other_sessions_clear_filter">Annulla filtro</string> + <string name="device_manager_other_sessions_no_inactive_sessions_found">Nessuna sessione inattiva trovata.</string> + <string name="device_manager_other_sessions_no_unverified_sessions_found">Nessuna sessione non verificata trovata.</string> + <string name="device_manager_other_sessions_no_verified_sessions_found">Nessuna sessione verificata trovata.</string> + <plurals name="device_manager_other_sessions_recommendation_description_inactive"> + <item quantity="one">Considera di disconnettere le sessioni vecchie (%1$d giorno o più) che non usi più.</item> + <item quantity="other">Considera di disconnettere le sessioni vecchie (%1$d giorni o più) che non usi più.</item> + </plurals> + <string name="device_manager_other_sessions_recommendation_title_inactive">Inattivo</string> + <string name="device_manager_other_sessions_recommendation_description_unverified">Verifica le tue sessioni per avere conversazioni più sicure o disconnetti quelle che non riconosci o che non usi più.</string> + <string name="device_manager_other_sessions_recommendation_title_unverified">Non verificato</string> + <string name="device_manager_other_sessions_recommendation_description_verified">Per una maggiore sicurezza, disconnetti tutte le sessioni che non riconosci o che non usi più.</string> + <string name="device_manager_other_sessions_recommendation_title_verified">Verificato</string> + <string name="a11y_device_manager_filter">Filtra</string> + <plurals name="device_manager_filter_option_inactive_description"> + <item quantity="one">Inattivo da %1$d giorno o più</item> + <item quantity="other">Inattivo da %1$d giorni o più</item> + </plurals> + <string name="device_manager_filter_option_inactive">Inattivo</string> + <string name="device_manager_filter_option_unverified_description">Non pronto per messaggi sicuri</string> + <string name="device_manager_filter_option_unverified">Non verificato</string> + <string name="device_manager_filter_option_verified_description">Pronto per messaggi sicuri</string> + <string name="device_manager_filter_option_verified">Verificato</string> + <string name="device_manager_filter_option_all_sessions">Tutte le sessioni</string> + <string name="device_manager_filter_bottom_sheet_title">Filtra</string> + <string name="device_manager_session_last_activity">Ultima attività %1$s</string> + <string name="device_manager_device_title">Dispositivo</string> + <string name="device_manager_session_title">Sessione</string> + <string name="device_manager_current_session_title">Sessione attuale</string> + <string name="device_manager_verification_status_detail_other_session_unverified">Verifica o disconnetti questa sessione per una migliore sicurezza e affidabilità.</string> + <string name="device_manager_verification_status_detail_current_session_unverified">Verifica la tua sessione attuale per messaggi più sicuri.</string> + <string name="device_manager_verification_status_detail_other_session_verified">Questa sessione è pronta per i messaggi sicuri.</string> + <string name="device_manager_verification_status_detail_current_session_verified">La tua sessione attuale è pronta per i messaggi sicuri.</string> + <string name="labs_enable_deferred_dm_title">Attiva messaggi diretti differiti</string> + <string name="labs_enable_deferred_dm_summary">Crea messaggio diretto solo al primo messaggio</string> + <string name="labs_enable_new_app_layout_summary">Un Element semplificato con schede opzionali</string> + <string name="labs_enable_new_app_layout_title">Attiva nuova disposizione</string> +</resources> \ No newline at end of file From 06286e6a7a7fa9a9695cbd41f3f34b1a7bae0714 Mon Sep 17 00:00:00 2001 From: Didek <dawid@rejowski.xyz> Date: Thu, 22 Sep 2022 17:57:43 +0000 Subject: [PATCH 018/187] Translated using Weblate (Polish) Currently translated at 97.8% (2367 of 2419 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/pl/ --- .../ui-strings/src/main/res/values-pl/strings.xml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 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 b7b73eb9e6..4109a130a5 100644 --- a/library/ui-strings/src/main/res/values-pl/strings.xml +++ b/library/ui-strings/src/main/res/values-pl/strings.xml @@ -2732,4 +2732,16 @@ <string name="timeline_error_room_not_found">Niestety, ten pokój nie został znaleziony. \nSpróbuj ponownie później.%s</string> <string name="invites_title">Zaproszenia</string> -</resources> + <string name="home_empty_no_unreads_message">Tutaj pojawią się rozmowy które nie zostały jeszcze odczytane.</string> + <string name="home_empty_no_unreads_title">Brak nowych wiadomości.</string> + <string name="change_space">Zmień przestrzeń</string> + <string name="labs_enable_deferred_dm_summary">Stwórz prywatny chat dopiero po wysłaniu pierwszej wiadomości</string> + <string name="labs_enable_deferred_dm_title">Włącz odroczone prywatne chaty</string> + <string name="labs_enable_new_app_layout_summary">Odświeżony wygląd Element z opcjonalnymi kartami</string> + <string name="labs_enable_new_app_layout_title">Włącz nowy układ</string> + <string name="home_empty_space_no_rooms_message">Przestrzenie to nowa metoda na grupowanie razem wielu pokoi i osób. Dodaj tu już istniejący pokój lub stwórz nowy używając przycisku w prawym-dolnym rogu.</string> + <string name="space_list_empty_message">Jest to nowa metoda na grupowanie razem wielu pokoi i osób.</string> + <string name="home_empty_space_no_rooms_title">%s +\nwygląda nieco pusto.</string> + <string name="space_list_empty_title">Brak przestrzeni.</string> +</resources> \ No newline at end of file From 2228ece79a2c04d79fb2a944aa736cd26c26aef4 Mon Sep 17 00:00:00 2001 From: Nui Harime <harime.nui@yandex.ru> Date: Fri, 23 Sep 2022 11:19:19 +0000 Subject: [PATCH 019/187] Translated using Weblate (Russian) Currently translated at 97.3% (2356 of 2419 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 | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 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 7c9d073035..e0ab799a57 100644 --- a/library/ui-strings/src/main/res/values-ru/strings.xml +++ b/library/ui-strings/src/main/res/values-ru/strings.xml @@ -956,11 +956,11 @@ <string name="no_message_edits_found">Изменения не найдены</string> <string name="room_filtering_filter_hint">Отфильтровать беседы…</string> <string name="room_filtering_footer_title">Не можете найти нужное\?</string> - <string name="room_filtering_footer_create_new_room">Создать новую комнату</string> - <string name="room_filtering_footer_create_new_direct_message">Отправить новое личное сообщение</string> + <string name="room_filtering_footer_create_new_room">Создать комнату</string> + <string name="room_filtering_footer_create_new_direct_message">Отправить личное сообщение</string> <string name="room_filtering_footer_open_room_directory">Просмотр каталога комнат</string> <string name="room_directory_search_hint">Имя или ID (#example:matrix.org)</string> - <string name="labs_swipe_to_reply_in_timeline">Включить жест смахивания для ответа в ленте сообщений</string> + <string name="labs_swipe_to_reply_in_timeline">Жест смахивания для ответа в ленте сообщений</string> <string name="link_copied_to_clipboard">Ссылка скопирована в буфер обмена</string> <string name="creating_direct_room">Создаем комнату…</string> <string name="message_view_edit_history">История изменений</string> @@ -2435,7 +2435,7 @@ <string name="location_timeline_failed_to_load_map">Не удалось загрузить карту</string> <string name="a11y_static_map_image">Карта</string> <string name="labs_enable_thread_messages_desc">Примечание: приложение будет перезапущено</string> - <string name="labs_enable_thread_messages">Включить обсуждения сообщений</string> + <string name="labs_enable_thread_messages">Обсуждения сообщений</string> <string name="ftue_auth_use_case_connect_to_server">Подключиться к серверу</string> <string name="ftue_auth_use_case_join_existing_server">Хотите присоединиться к существующему серверу\?</string> <string name="ftue_auth_use_case_skip_partial">Пропустить вопрос</string> @@ -2666,7 +2666,7 @@ <string name="a11y_create_message">Создать беседу или комнату</string> <string name="device_manager_settings_active_sessions_show_all">Показать все сессии (V2, в разработке)</string> <string name="room_list_filter_people">Люди</string> - <string name="home_layout_preferences">Настройки макета</string> + <string name="home_layout_preferences">Настройки оформления</string> <string name="home_layout_preferences_filters">Фильтры</string> <string name="home_layout_preferences_recents">Недавние</string> <string name="room_list_filter_favourites">Избранные</string> @@ -2693,4 +2693,23 @@ <string name="home_empty_no_rooms_title">Добро пожаловать в ${app_name}, \n%s.</string> <string name="onboarding_new_app_layout_feedback_title">Оставить отзыв</string> -</resources> + <string name="device_manager_session_details_session_name">Название сессии</string> + <string name="device_manager_filter_option_inactive">Неактивные</string> + <string name="device_manager_session_details_device_ip_address">IP-адрес</string> + <string name="device_manager_session_details_session_last_activity">Последняя активность</string> + <string name="device_manager_session_details_title">Сведения о сессии</string> + <string name="device_manager_other_sessions_recommendation_description_verified">Для лучшей безопасности выйдите из всех сессий, которые более не признаёте или не используете.</string> + <string name="device_manager_filter_option_verified">Заверено</string> + <string name="device_manager_filter_option_all_sessions">Все сессии</string> + <string name="device_manager_session_last_activity">Последняя активность %1$s</string> + <string name="device_manager_device_title">Устройство</string> + <string name="device_manager_session_title">Сессия</string> + <string name="device_manager_current_session_title">Текущая сессия</string> + <string name="device_manager_verify_session">Заверить сессию</string> + <string name="device_manager_view_details">Подробности</string> + <string name="device_manager_verification_status_detail_other_session_verified">Эта сессия готова к безопасному обмену сообщениями.</string> + <string name="device_manager_verification_status_detail_current_session_verified">Текущая сессия готова к безопасному обмену сообщениями.</string> + <string name="a11y_device_manager_device_type_web">Веб-браузер</string> + <string name="space_list_empty_message">Пространства — это новый способ организации комнат и людей. Создайте пространство, чтобы начать.</string> + <string name="labs_enable_new_app_layout_title">Новое оформление</string> +</resources> \ No newline at end of file From c0b854f54f492c4d700cdd465d84ded0760c3d5a Mon Sep 17 00:00:00 2001 From: Jozef Gaal <preklady@mayday.sk> Date: Thu, 22 Sep 2022 23:45:15 +0000 Subject: [PATCH 020/187] Translated using Weblate (Slovak) Currently translated at 100.0% (2419 of 2419 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/sk/ --- .../src/main/res/values-sk/strings.xml | 46 ++++++++++++++++++- 1 file changed, 45 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 9eb22e3ae3..f37af1a654 100644 --- a/library/ui-strings/src/main/res/values-sk/strings.xml +++ b/library/ui-strings/src/main/res/values-sk/strings.xml @@ -2720,4 +2720,48 @@ <string name="a11y_collapse_space_children">Zbaliť %s podpriestory</string> <string name="a11y_expand_space_children">Rozbaliť %s podpriestory</string> <string name="change_space">Zmeniť priestor</string> -</resources> + <string name="device_manager_session_details_device_ip_address">IP adresa</string> + <string name="device_manager_session_details_session_last_activity">Posledná aktivita</string> + <string name="device_manager_session_details_session_name">Názov relácie</string> + <string name="device_manager_session_details_description">Informácie o aplikácii, zariadení a činnosti.</string> + <string name="device_manager_session_details_title">Podrobnosti o relácii</string> + <string name="device_manager_other_sessions_clear_filter">Zrušiť filter</string> + <string name="device_manager_other_sessions_no_inactive_sessions_found">Nenašli sa žiadne neaktívne relácie.</string> + <string name="device_manager_other_sessions_no_unverified_sessions_found">Nenašli sa žiadne neoverené relácie.</string> + <string name="device_manager_other_sessions_no_verified_sessions_found">Nenašli sa žiadne overené relácie.</string> + <plurals name="device_manager_other_sessions_recommendation_description_inactive"> + <item quantity="one">Zvážte odhlásenie zo starých relácií (%1$d deň alebo viac), ktoré už nepoužívate.</item> + <item quantity="few">Zvážte odhlásenie zo starých relácií (%1$d dni alebo viac), ktoré už nepoužívate.</item> + <item quantity="other">Zvážte odhlásenie zo starých relácií (%1$d dní alebo viac), ktoré už nepoužívate.</item> + </plurals> + <string name="device_manager_other_sessions_recommendation_title_inactive">Neaktívne</string> + <string name="device_manager_other_sessions_recommendation_description_unverified">Overte si relácie pre vylepšené bezpečné zasielanie správ alebo sa odhláste z tých, ktoré už nepoznáte alebo nepoužívate.</string> + <string name="device_manager_other_sessions_recommendation_title_unverified">Neoverené</string> + <string name="device_manager_other_sessions_recommendation_description_verified">V záujme čo najlepšieho zabezpečenia sa odhláste z každej relácie, ktorú už nepoznáte alebo nepoužívate.</string> + <string name="device_manager_other_sessions_recommendation_title_verified">Overené</string> + <string name="a11y_device_manager_filter">Filter</string> + <plurals name="device_manager_filter_option_inactive_description"> + <item quantity="one">Neaktívny už %1$d deň alebo dlhšie</item> + <item quantity="few">Neaktívny už %1$d dni alebo dlhšie</item> + <item quantity="other">Neaktívny už %1$d dní alebo dlhšie</item> + </plurals> + <string name="device_manager_filter_option_inactive">Neaktívne</string> + <string name="device_manager_filter_option_unverified_description">Nie je pripravené na bezpečné zasielanie správ</string> + <string name="device_manager_filter_option_unverified">Neoverené</string> + <string name="device_manager_filter_option_verified_description">Pripravené na bezpečné zasielanie správ</string> + <string name="device_manager_filter_option_verified">Overené</string> + <string name="device_manager_filter_option_all_sessions">Všetky relácie</string> + <string name="device_manager_filter_bottom_sheet_title">Filter</string> + <string name="device_manager_session_last_activity">Posledná aktivita %1$s</string> + <string name="device_manager_device_title">Zariadenie</string> + <string name="device_manager_session_title">Relácia</string> + <string name="device_manager_current_session_title">Aktuálna relácia</string> + <string name="device_manager_verification_status_detail_other_session_unverified">V záujme čo najvyššej bezpečnosti a spoľahlivosti túto reláciu overte alebo sa z nej odhláste.</string> + <string name="device_manager_verification_status_detail_current_session_unverified">Overte svoju aktuálnu reláciu pre vylepšené bezpečné zasielanie správ.</string> + <string name="device_manager_verification_status_detail_other_session_verified">Táto relácia je pripravená na bezpečné zasielanie správ.</string> + <string name="device_manager_verification_status_detail_current_session_verified">Vaša aktuálna relácia je pripravená na bezpečné zasielanie správ.</string> + <string name="labs_enable_deferred_dm_summary">Vytvoriť priamu správu len pri prvej správe</string> + <string name="labs_enable_deferred_dm_title">Povoliť odložené priame správy</string> + <string name="labs_enable_new_app_layout_summary">Zjednodušený Element s voliteľnými kartami</string> + <string name="labs_enable_new_app_layout_title">Zapnúť nové usporiadanie</string> +</resources> \ No newline at end of file From 2a8dfc2a599141f31e1d653d58ddbb2cb4547c03 Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk <igor_ck@outlook.com> Date: Thu, 22 Sep 2022 18:54:23 +0000 Subject: [PATCH 021/187] Translated using Weblate (Ukrainian) Currently translated at 100.0% (2419 of 2419 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/uk/ --- .../src/main/res/values-uk/strings.xml | 48 ++++++++++++++++++- 1 file changed, 47 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 3e511f8459..41626bd156 100644 --- a/library/ui-strings/src/main/res/values-uk/strings.xml +++ b/library/ui-strings/src/main/res/values-uk/strings.xml @@ -2772,4 +2772,50 @@ <string name="a11y_collapse_space_children">Згорнути дочірні елементи %s</string> <string name="a11y_expand_space_children">Розгорнути дочірні елементи %s</string> <string name="change_space">Змінити простір</string> -</resources> + <string name="device_manager_session_details_device_ip_address">IP-адреса</string> + <string name="device_manager_session_details_session_last_activity">Остання активність</string> + <string name="device_manager_session_details_session_name">Назва сеансу</string> + <string name="device_manager_session_details_description">Відомості про застосунок, пристрій та діяльність.</string> + <string name="device_manager_session_details_title">Подробиці сеансу</string> + <string name="device_manager_other_sessions_clear_filter">Очистити фільтр</string> + <string name="device_manager_other_sessions_no_inactive_sessions_found">Неактивних сеансів не знайдено.</string> + <string name="device_manager_other_sessions_no_unverified_sessions_found">Не знайдено не звірених сеансів.</string> + <string name="device_manager_other_sessions_no_verified_sessions_found">Знайдені не звірені сеанси.</string> + <plurals name="device_manager_other_sessions_recommendation_description_inactive"> + <item quantity="one">Подумайте про те, щоб вийти зі старих сеансів (%1$d день або довше), якими ви більше не користуєтесь.</item> + <item quantity="few">Подумайте про те, щоб вийти зі старих сеансів (%1$d дні або довше), якими ви більше не користуєтесь.</item> + <item quantity="many">Подумайте про те, щоб вийти зі старих сеансів (%1$d днів або довше), якими ви більше не користуєтесь.</item> + <item quantity="other">Подумайте про те, щоб вийти зі старих сеансів (%1$d днів або довше), якими ви більше не користуєтесь.</item> + </plurals> + <string name="device_manager_other_sessions_recommendation_title_inactive">Неактивний</string> + <string name="device_manager_other_sessions_recommendation_description_unverified">Звірте свої сеанси для посилення безпеки обміну повідомленнями або вийдіть з тих, які ви більше не впізнаєте або не використовуєте.</string> + <string name="device_manager_other_sessions_recommendation_title_unverified">Не звірений</string> + <string name="device_manager_other_sessions_recommendation_description_verified">Для кращої безпеки виходьте з будь-якого сеансу, який ви більше не впізнаєте або не використовуєте.</string> + <string name="device_manager_other_sessions_recommendation_title_verified">Звірений</string> + <string name="a11y_device_manager_filter">Фільтрувати</string> + <plurals name="device_manager_filter_option_inactive_description"> + <item quantity="one">Неактивний %1$d день або довше</item> + <item quantity="few">Неактивний %1$d дні або довше</item> + <item quantity="many">Неактивний %1$d днів або довше</item> + <item quantity="other">Неактивний %1$d днів або довше</item> + </plurals> + <string name="device_manager_filter_option_inactive">Неактивний</string> + <string name="device_manager_filter_option_unverified_description">Не готовий до безпечного обміну повідомленнями</string> + <string name="device_manager_filter_option_unverified">Не звірений</string> + <string name="device_manager_filter_option_verified">Звірений</string> + <string name="device_manager_filter_option_verified_description">Готовий до безпечного обміну повідомленнями</string> + <string name="device_manager_filter_option_all_sessions">Усі сеанси</string> + <string name="device_manager_filter_bottom_sheet_title">Фільтрувати</string> + <string name="device_manager_session_last_activity">Остання активність %1$s</string> + <string name="device_manager_device_title">Пристрій</string> + <string name="device_manager_session_title">Сеанс</string> + <string name="device_manager_current_session_title">Поточний сеанс</string> + <string name="device_manager_verification_status_detail_other_session_unverified">Звірте або вийдіть з цього сеансу для кращої безпеки та надійності.</string> + <string name="device_manager_verification_status_detail_current_session_unverified">Звірте свій поточний сеанс для посилення безпеки обміну повідомленнями.</string> + <string name="device_manager_verification_status_detail_other_session_verified">Цей сеанс готовий до безпечного обміну повідомленнями.</string> + <string name="device_manager_verification_status_detail_current_session_verified">Ваш поточний сеанс готовий до безпечного обміну повідомленнями.</string> + <string name="labs_enable_deferred_dm_summary">Створюйте приватні повідомлення лише за надсилання першого повідомлення</string> + <string name="labs_enable_deferred_dm_title">Увімкнути відкладені приватні повідомлення</string> + <string name="labs_enable_new_app_layout_summary">Спрощений Element з опціональними вкладками</string> + <string name="labs_enable_new_app_layout_title">Увімкнути новий вигляд</string> +</resources> \ No newline at end of file From f415a7f5db5fac74f18de40f06b98b0c24119db9 Mon Sep 17 00:00:00 2001 From: phardyle <bradney_ccea@aleeas.com> Date: Thu, 22 Sep 2022 16:52:31 +0000 Subject: [PATCH 022/187] Translated using Weblate (Chinese (Simplified)) Currently translated at 98.1% (2375 of 2419 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, 4 insertions(+), 1 deletion(-) 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 db1dab92e2..69a2fc5eef 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 @@ -2604,4 +2604,7 @@ <string name="onboarding_new_app_layout_feedback_title">提供反馈</string> <string name="onboarding_new_app_layout_feedback_message">点击右上角查看反馈选项。</string> <string name="onboarding_new_app_layout_button_try">试用</string> -</resources> + <string name="space_list_empty_message">空间是对房间和人进行分组的新方式。创建一个空间来开始吧。</string> + <string name="labs_enable_new_app_layout_title">启用新布局</string> + <string name="device_manager_session_details_device_ip_address">IP地址</string> +</resources> \ No newline at end of file From c0504a20022c8c8ba4a75522338de460dc876757 Mon Sep 17 00:00:00 2001 From: Jeff Huang <s8321414@gmail.com> Date: Fri, 23 Sep 2022 01:55:02 +0000 Subject: [PATCH 023/187] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (2419 of 2419 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/zh_Hant/ --- .../src/main/res/values-zh-rTW/strings.xml | 42 ++++++++++++++++++- 1 file changed, 41 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 78caa2cc2e..876084d566 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 @@ -2616,4 +2616,44 @@ <string name="a11y_collapse_space_children">折疊 %s 個子空間</string> <string name="a11y_expand_space_children">展開 %s 個子空間</string> <string name="change_space">變更空間</string> -</resources> + <string name="device_manager_session_details_device_ip_address">IP 位置</string> + <string name="device_manager_session_details_session_last_activity">最後活動</string> + <string name="device_manager_session_details_session_name">工作階段名稱</string> + <string name="device_manager_session_details_description">應用程式、裝置與活動資訊。</string> + <string name="device_manager_session_details_title">工作階段詳細資訊</string> + <string name="device_manager_other_sessions_clear_filter">清除過濾條件</string> + <string name="device_manager_other_sessions_no_inactive_sessions_found">找不到不活躍的工作階段。</string> + <string name="device_manager_other_sessions_no_unverified_sessions_found">找不到未驗證的工作階段。</string> + <string name="device_manager_other_sessions_no_verified_sessions_found">找不到已驗證的工作階段。</string> + <plurals name="device_manager_filter_option_inactive_description"> + <item quantity="other">閒置%1$d天或更久</item> + </plurals> + <plurals name="device_manager_other_sessions_recommendation_description_inactive"> + <item quantity="other">考慮登出您不再使用的舊工作階段(%1$d天或更久)。</item> + </plurals> + <string name="device_manager_other_sessions_recommendation_title_inactive">不活躍</string> + <string name="device_manager_other_sessions_recommendation_description_unverified">驗證您的工作階段以強化安全通訊或從您無法識別或不再使用的工作階段登出。</string> + <string name="device_manager_other_sessions_recommendation_title_unverified">未驗證</string> + <string name="device_manager_other_sessions_recommendation_description_verified">為取得最佳安全性,請從任何您無法識別或不再使用的工作階段登出。</string> + <string name="device_manager_other_sessions_recommendation_title_verified">已驗證</string> + <string name="a11y_device_manager_filter">過濾</string> + <string name="device_manager_filter_option_inactive">不活躍</string> + <string name="device_manager_filter_option_unverified_description">尚未準備好安全通訊</string> + <string name="device_manager_filter_option_unverified">未驗證</string> + <string name="device_manager_filter_option_verified_description">準備好安全通訊</string> + <string name="device_manager_filter_option_verified">已驗證</string> + <string name="device_manager_filter_option_all_sessions">所有工作階段</string> + <string name="device_manager_filter_bottom_sheet_title">過濾</string> + <string name="device_manager_session_last_activity">最後活動 %1$s</string> + <string name="device_manager_device_title">裝置</string> + <string name="device_manager_session_title">工作階段</string> + <string name="device_manager_current_session_title">目前的工作階段</string> + <string name="device_manager_verification_status_detail_other_session_unverified">驗證或從此工作階段登出以取得最佳安全性與可靠性。</string> + <string name="device_manager_verification_status_detail_current_session_unverified">驗證您目前的工作階段以強化安全通訊。</string> + <string name="device_manager_verification_status_detail_other_session_verified">此工作階段已準備好安全通訊。</string> + <string name="device_manager_verification_status_detail_current_session_verified">您目前的工作階段已準備好安全通訊。</string> + <string name="labs_enable_deferred_dm_summary">僅在第一則訊息上建立直接訊息</string> + <string name="labs_enable_deferred_dm_title">啟用延期直接訊息</string> + <string name="labs_enable_new_app_layout_summary">包含選擇性分頁的簡潔 Element</string> + <string name="labs_enable_new_app_layout_title">啟用新佈局</string> +</resources> \ No newline at end of file From ab7dc527faa176ac778e91abfaf9e5abbab22c38 Mon Sep 17 00:00:00 2001 From: Vri <element.io@vrifox.cc> Date: Thu, 22 Sep 2022 10:05:50 +0000 Subject: [PATCH 024/187] Translated using Weblate (German) Currently translated at 100.0% (76 of 76 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/40104360.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 fastlane/metadata/android/de-DE/changelogs/40104360.txt diff --git a/fastlane/metadata/android/de-DE/changelogs/40104360.txt b/fastlane/metadata/android/de-DE/changelogs/40104360.txt new file mode 100644 index 0000000000..bf249af068 --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/40104360.txt @@ -0,0 +1,3 @@ +Das neue App-Layout kann in den Labor-Einstellungen aktiviert werden. Probier es gerne aus! +Fehler bzgl. ausbleibender Benachrichtigungen und langwierigem inkrementellem Synchronisieren behoben. +Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases From 15a027d7fd0804f21c9ae2ca13dfc394d6e3faa0 Mon Sep 17 00:00:00 2001 From: Glandos <bugs-github@antipoul.fr> Date: Fri, 23 Sep 2022 11:55:28 +0000 Subject: [PATCH 025/187] Translated using Weblate (French) Currently translated at 100.0% (76 of 76 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/40104360.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 fastlane/metadata/android/fr-FR/changelogs/40104360.txt diff --git a/fastlane/metadata/android/fr-FR/changelogs/40104360.txt b/fastlane/metadata/android/fr-FR/changelogs/40104360.txt new file mode 100644 index 0000000000..80f59952d1 --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/40104360.txt @@ -0,0 +1,3 @@ +La nouvelle présentation de l’application est disponibles dans les paramètres expérimentaux. Essayez-là ! +Correction de problèmes sur les notifications manquantes, et la synchronisation incrémentale lente. +Intégralité des changements : https://github.com/vector-im/element-android/releases From 9a438204c4169ecdd83d678867693505660192a3 Mon Sep 17 00:00:00 2001 From: Jozef Gaal <preklady@mayday.sk> Date: Thu, 22 Sep 2022 23:35:37 +0000 Subject: [PATCH 026/187] Translated using Weblate (Slovak) Currently translated at 100.0% (76 of 76 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/sk/ --- fastlane/metadata/android/sk/changelogs/40104360.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 fastlane/metadata/android/sk/changelogs/40104360.txt diff --git a/fastlane/metadata/android/sk/changelogs/40104360.txt b/fastlane/metadata/android/sk/changelogs/40104360.txt new file mode 100644 index 0000000000..af4154b5cf --- /dev/null +++ b/fastlane/metadata/android/sk/changelogs/40104360.txt @@ -0,0 +1,3 @@ +Nové usporiadanie aplikácie môžete povoliť v nastaveniach laboratórií. Vyskúšajte to! +Oprava problémov týkajúcich sa chýbajúcich oznámení a dlhej inkrementálnej synchronizácie. +Úplný zoznam zmien: https://github.com/vector-im/element-android/releases From 4a752f7f43d0a08154c6a6978f5a42a0c432fcfb Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk <igor_ck@outlook.com> Date: Thu, 22 Sep 2022 18:20:11 +0000 Subject: [PATCH 027/187] Translated using Weblate (Ukrainian) Currently translated at 100.0% (76 of 76 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/uk/ --- fastlane/metadata/android/uk/changelogs/40104360.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 fastlane/metadata/android/uk/changelogs/40104360.txt diff --git a/fastlane/metadata/android/uk/changelogs/40104360.txt b/fastlane/metadata/android/uk/changelogs/40104360.txt new file mode 100644 index 0000000000..a2c9bcc4b5 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/40104360.txt @@ -0,0 +1,3 @@ +Новий макет програми можна увімкнути в налаштуваннях лабораторії. Спробуйте! +Виправлено проблеми з відсутністю сповіщень та тривалою інкрементною синхронізацією. +Список усіх змін: https://github.com/vector-im/element-android/releases From e749831ad1bdc642b8891794f5562417840e126b Mon Sep 17 00:00:00 2001 From: random <dictionary@tutamail.com> Date: Thu, 22 Sep 2022 14:04:29 +0000 Subject: [PATCH 028/187] Translated using Weblate (Italian) Currently translated at 100.0% (76 of 76 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/40104360.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 fastlane/metadata/android/it-IT/changelogs/40104360.txt diff --git a/fastlane/metadata/android/it-IT/changelogs/40104360.txt b/fastlane/metadata/android/it-IT/changelogs/40104360.txt new file mode 100644 index 0000000000..c6749d3ff7 --- /dev/null +++ b/fastlane/metadata/android/it-IT/changelogs/40104360.txt @@ -0,0 +1,3 @@ +Nuova disposizione dell'app attivabile nelle impostazioni Laboratori. Provala! +Corretti problemi su notifiche mancanti e sincronizzazioni incrementali lunghe. +Cronologia completa: https://github.com/vector-im/element-android/releases From f45209d19719d950208122096fee312defd48adc Mon Sep 17 00:00:00 2001 From: Danial Behzadi <dani.behzi@ubuntu.com> Date: Thu, 22 Sep 2022 08:41:20 +0000 Subject: [PATCH 029/187] Translated using Weblate (Persian) Currently translated at 100.0% (76 of 76 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/fa/ --- fastlane/metadata/android/fa/changelogs/40104360.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 fastlane/metadata/android/fa/changelogs/40104360.txt diff --git a/fastlane/metadata/android/fa/changelogs/40104360.txt b/fastlane/metadata/android/fa/changelogs/40104360.txt new file mode 100644 index 0000000000..be14e1b9e2 --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40104360.txt @@ -0,0 +1,3 @@ +چینش کارهٔ جدید میتواند در تنظیمات آزمایشگاهها به کار بیفتند. لطفاً بیازماییدش! +رفع مشکلات مربوط به آگاهی غایب و همگامسازی تجمعّی طولانی. +گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases From dc9c0ce573dec4fa4ac02ff5a069118f0a246a5f Mon Sep 17 00:00:00 2001 From: Jeff Huang <s8321414@gmail.com> Date: Fri, 23 Sep 2022 01:45:49 +0000 Subject: [PATCH 030/187] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (76 of 76 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/40104360.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 fastlane/metadata/android/zh-TW/changelogs/40104360.txt diff --git a/fastlane/metadata/android/zh-TW/changelogs/40104360.txt b/fastlane/metadata/android/zh-TW/changelogs/40104360.txt new file mode 100644 index 0000000000..be36b60840 --- /dev/null +++ b/fastlane/metadata/android/zh-TW/changelogs/40104360.txt @@ -0,0 +1,3 @@ +新的應用程式佈局可在「實驗室」設定中啟用。請試試看! +修復關於遺失通知的問題,以及增量同步需要長時間的問題。 +完整的變更紀錄:https://github.com/vector-im/element-android/releases From a71cc768f557d75d4f8f73cb14de93a9fbe4df72 Mon Sep 17 00:00:00 2001 From: waclaw66 <waclaw66@seznam.cz> Date: Thu, 22 Sep 2022 08:43:07 +0000 Subject: [PATCH 031/187] Translated using Weblate (Czech) Currently translated at 100.0% (76 of 76 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/40104360.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 fastlane/metadata/android/cs-CZ/changelogs/40104360.txt diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40104360.txt b/fastlane/metadata/android/cs-CZ/changelogs/40104360.txt new file mode 100644 index 0000000000..fcadf9898c --- /dev/null +++ b/fastlane/metadata/android/cs-CZ/changelogs/40104360.txt @@ -0,0 +1,3 @@ +Nový vzhled aplikace lze povolit v Experimentálních funkcích. Prosíme, vyzkoušejte ho! +Oprava problémů s chybějícími oznámeními a dlouhou přírůstkovou synchronizací. +Úplný seznam změn: https://github.com/vector-im/element-android/releases From 13bca69ae4934142827cf13b88d4edae21f80c05 Mon Sep 17 00:00:00 2001 From: Linerly <linerly@protonmail.com> Date: Thu, 22 Sep 2022 09:07:24 +0000 Subject: [PATCH 032/187] Translated using Weblate (Indonesian) Currently translated at 100.0% (76 of 76 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/id/ --- fastlane/metadata/android/id/changelogs/40104360.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 fastlane/metadata/android/id/changelogs/40104360.txt diff --git a/fastlane/metadata/android/id/changelogs/40104360.txt b/fastlane/metadata/android/id/changelogs/40104360.txt new file mode 100644 index 0000000000..be626f6350 --- /dev/null +++ b/fastlane/metadata/android/id/changelogs/40104360.txt @@ -0,0 +1,3 @@ +Tata Letak Aplikasi Baru dapat diaktifkan di pengaturan Uji Coba. Cobalah! +Perbariki masalah tentang notifikasi hilang, dan penyinkronan inkremental panjang. +Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases From 65c89638ab7b6569c584e164a26d8e790f4c0ac3 Mon Sep 17 00:00:00 2001 From: ericdecanini <eddecanini@gmail.com> Date: Fri, 23 Sep 2022 09:24:20 -0400 Subject: [PATCH 033/187] Fixes lint error with comments --- .../matrix/android/sdk/api/session/pushers/HttpPusher.kt | 4 ++-- .../android/sdk/internal/session/pushers/JsonPusher.kt | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/HttpPusher.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/HttpPusher.kt index c4ec57a766..1258c5c02f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/HttpPusher.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/HttpPusher.kt @@ -59,12 +59,12 @@ data class HttpPusher( val url: String, /** - * Whether the pusher should actively create push notifications + * Whether the pusher should actively create push notifications. */ val enabled: Boolean, /** - * The device ID of the session that registered the pusher + * The device ID of the session that registered the pusher. */ val deviceId: String, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/JsonPusher.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/JsonPusher.kt index f009549b5a..c1cf3eb276 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/JsonPusher.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/JsonPusher.kt @@ -117,13 +117,13 @@ internal data class JsonPusher( val append: Boolean? = false, /** - * Whether the pusher should actively create push notifications + * Whether the pusher should actively create push notifications. */ @Json(name = "org.matrix.msc3881.enabled") - val enabled: Boolean = false, + val enabled: Boolean = true, /** - * The device_id of the session that registered the pusher + * The device_id of the session that registered the pusher. */ @Json(name = "org.matrix.msc3881.device_id") val deviceId: String? = null, From 52171ef748e2e088db25bd90ba01cdf1ccfb0ef9 Mon Sep 17 00:00:00 2001 From: ericdecanini <eddecanini@gmail.com> Date: Fri, 23 Sep 2022 09:36:53 -0400 Subject: [PATCH 034/187] Changes deviceDisplayName on pusher to use device model name --- .../java/im/vector/app/core/pushers/PushersManager.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt b/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt index 8be9b192fe..ac932bee72 100644 --- a/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt +++ b/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt @@ -66,14 +66,14 @@ class PushersManager @Inject constructor( pushKey, stringProvider.getString(R.string.pusher_app_id), profileTag = DEFAULT_PUSHER_FILE_TAG + "_" + abs(activeSessionHolder.getActiveSession().myUserId.hashCode()), - localeProvider.current().language, - appNameProvider.getAppName(), - activeSessionHolder.getActiveSession().sessionParams.deviceId ?: "MOBILE", - gateway, + lang = localeProvider.current().language, + appDisplayName = appNameProvider.getAppName(), + deviceDisplayName = android.os.Build.MODEL, + url = gateway, enabled = true, deviceId = activeSessionHolder.getActiveSession().sessionParams.deviceId ?: "MOBILE", append = false, - withEventIdOnly = true + withEventIdOnly = true, ) suspend fun registerEmailForPush(email: String) { From f724751c8602523ee257462d841f17027df1736d Mon Sep 17 00:00:00 2001 From: ericdecanini <eddecanini@gmail.com> Date: Fri, 23 Sep 2022 11:03:56 -0400 Subject: [PATCH 035/187] Adds new pusher fields to Notification Targets screen --- .../src/main/res/values/strings.xml | 10 +++--- .../features/settings/push/PushGatewayItem.kt | 4 +++ .../src/main/res/layout/item_pushgateway.xml | 34 +++++++++++++++++++ 3 files changed, 44 insertions(+), 4 deletions(-) diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index dec46159dd..c436044153 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -1697,13 +1697,15 @@ <string name="settings_push_rules_no_rules">No push rules defined</string> <string name="settings_push_gateway_no_pushers">No registered push gateways</string> - <string name="push_gateway_item_app_id">app_id:</string> - <string name="push_gateway_item_push_key">push_key:</string> - <string name="push_gateway_item_app_display_name">app_display_name:</string> - <string name="push_gateway_item_device_name">session_name:</string> + <string name="push_gateway_item_app_id">App ID:</string> + <string name="push_gateway_item_push_key">Push Key:</string> + <string name="push_gateway_item_app_display_name">App Display Name:</string> + <string name="push_gateway_item_device_name">Device Display Name:</string> + <string name="push_gateway_item_device_id">Device ID:</string> <string name="push_gateway_item_url">Url:</string> <string name="push_gateway_item_format">Format:</string> <string name="push_gateway_item_profile_tag">Profile tag:</string> + <string name="push_gateway_item_enabled">Enabled:</string> <string name="preference_voice_and_video">Voice & Video</string> <string name="preference_root_help_about">Help & About</string> diff --git a/vector/src/main/java/im/vector/app/features/settings/push/PushGatewayItem.kt b/vector/src/main/java/im/vector/app/features/settings/push/PushGatewayItem.kt index 5ef6e02330..4fa1e422a1 100644 --- a/vector/src/main/java/im/vector/app/features/settings/push/PushGatewayItem.kt +++ b/vector/src/main/java/im/vector/app/features/settings/push/PushGatewayItem.kt @@ -50,6 +50,8 @@ abstract class PushGatewayItem : VectorEpoxyModel<PushGatewayItem.Holder>(R.layo holder.format.setTextOrHide(pusher.data.format, hideWhenBlank = true, holder.formatTitle) holder.profileTag.setTextOrHide(pusher.profileTag, hideWhenBlank = true, holder.profileTagTitle) holder.deviceName.text = pusher.deviceDisplayName + holder.deviceId.text = pusher.deviceId ?: "null" + holder.enabled.text = pusher.enabled.toString() holder.removeButton.setOnClickListener { interactions.onRemovePushTapped(pusher) } @@ -59,10 +61,12 @@ abstract class PushGatewayItem : VectorEpoxyModel<PushGatewayItem.Holder>(R.layo val kind by bind<TextView>(R.id.pushGatewayKind) val pushKey by bind<TextView>(R.id.pushGatewayKeyValue) val deviceName by bind<TextView>(R.id.pushGatewayDeviceNameValue) + val deviceId by bind<TextView>(R.id.pushGatewayDeviceIdValue) val formatTitle by bind<View>(R.id.pushGatewayFormat) val format by bind<TextView>(R.id.pushGatewayFormatValue) val profileTagTitle by bind<TextView>(R.id.pushGatewayProfileTag) val profileTag by bind<TextView>(R.id.pushGatewayProfileTagValue) + val enabled by bind<TextView>(R.id.pushGatewayEnabledValue) val urlTitle by bind<View>(R.id.pushGatewayURL) val url by bind<TextView>(R.id.pushGatewayURLValue) val appName by bind<TextView>(R.id.pushGatewayAppNameValue) diff --git a/vector/src/main/res/layout/item_pushgateway.xml b/vector/src/main/res/layout/item_pushgateway.xml index afc40e5b7a..67a3a7d390 100644 --- a/vector/src/main/res/layout/item_pushgateway.xml +++ b/vector/src/main/res/layout/item_pushgateway.xml @@ -83,6 +83,23 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="16dp" + tools:text="Pixel 6" /> + + <TextView + android:id="@+id/pushGatewayDeviceId" + style="@style/Widget.Vector.TextView.Body" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="4dp" + android:text="@string/push_gateway_item_device_id" + android:textStyle="bold" /> + + <TextView + android:id="@+id/pushGatewayDeviceIdValue" + style="@style/Widget.Vector.TextView.Body" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="16dp" tools:text="EBMDOLFJD" /> <TextView @@ -135,6 +152,23 @@ android:layout_height="wrap_content" android:layout_marginBottom="16dp" /> + <TextView + android:id="@+id/pushGatewayEnabled" + style="@style/Widget.Vector.TextView.Body" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="4dp" + android:text="@string/push_gateway_item_enabled" + android:textStyle="bold" /> + + <TextView + android:id="@+id/pushGatewayEnabledValue" + style="@style/Widget.Vector.TextView.Body" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="16dp" + tools:text="true" /> + <Button android:id="@+id/pushGatewayDeleteButton" android:layout_width="wrap_content" From b356ac799a6f4fedbcfcbf0418c7ecb805dfb84c Mon Sep 17 00:00:00 2001 From: Vri <element.io@vrifox.cc> Date: Sat, 24 Sep 2022 14:54:39 +0000 Subject: [PATCH 036/187] Translated using Weblate (German) Currently translated at 100.0% (2419 of 2419 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/de/ --- .../src/main/res/values-de/strings.xml | 160 +++++++++++++----- 1 file changed, 115 insertions(+), 45 deletions(-) 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 e01fc898a3..5154809f72 100644 --- a/library/ui-strings/src/main/res/values-de/strings.xml +++ b/library/ui-strings/src/main/res/values-de/strings.xml @@ -269,7 +269,7 @@ <string name="send_bug_report">Problem melden</string> <string name="send_bug_report_description">Bitte beschreibe das Problem. Was hast du genau gemacht\? Was sollte passieren\? Was ist tatsächlich passiert\?</string> <string name="send_bug_report_placeholder">Problembeschreibung</string> - <string name="send_bug_report_logs_description">Um Probleme diagnostizieren zu können, werden Protokolle des Clients zusammen mit dem Fehlerbericht übermittelt. Dieser Fehlerbericht wird, wie die Protokolle und das Bildschirmfoto, nicht öffentlich sichtbar sein. Wenn du nur den oben eingegebenen Text senden möchtest, die nachfolgenden Haken entsprechend entfernen:</string> + <string name="send_bug_report_logs_description">Um Probleme diagnostizieren zu können, werden Protokolle der Anwendung zusammen mit dem Fehlerbericht übermittelt. Dieser Fehlerbericht wird, wie die Protokolle und das Bildschirmfoto, nicht öffentlich sichtbar sein. Wenn du nur den oben eingegebenen Text senden möchtest, die nachfolgenden Haken entsprechend entfernen:</string> <string name="send_bug_report_alert_message">Du scheinst dein Telefon frustriert zu schütteln. Möchtest du das Fenster zum Senden eines Fehlerberichts öffnen\?</string> <string name="send_bug_report_sent">Dein Fehlerbericht wurde erfolgreich übermittelt</string> <string name="send_bug_report_failed">Der Fehlerbericht konnte nicht übermittelt werden (%s)</string> @@ -356,7 +356,7 @@ <string name="settings_enable_all_notif">Benachrichtigungen für diesen Account</string> <string name="settings_enable_this_device">Benachrichtigungen für diese Sitzung</string> <string name="settings_messages_in_one_to_one">Direktnachrichten</string> - <string name="settings_messages_in_group_chat">Gruppenchats</string> + <string name="settings_messages_in_group_chat">Gruppenunterhaltungen</string> <string name="settings_invited_to_room">Einladungen</string> <string name="settings_call_invitations">Anrufe</string> <string name="settings_messages_sent_by_bot">Nachrichten von Bots</string> @@ -403,8 +403,8 @@ <string name="settings_unignore_user">Alle Nachrichten von %s anzeigen\?</string> <string name="settings_select_country">Wähle ein Land</string> <string name="room_settings_topic">Thema</string> - <string name="room_settings_room_read_history_rules_pref_title">Lesbarkeit des Chatverlaufs</string> - <string name="room_settings_room_read_history_rules_pref_dialog_title">Wer kann den Chatverlauf lesen?</string> + <string name="room_settings_room_read_history_rules_pref_title">Lesbarkeit des Verlaufs</string> + <string name="room_settings_room_read_history_rules_pref_dialog_title">Wer kann den Verlauf lesen\?</string> <string name="room_settings_read_history_entry_anyone">Alle</string> <string name="room_settings_read_history_entry_members_only_option_time_shared">Nur Mitglieder</string> <string name="room_settings_read_history_entry_members_only_invited">Nur Mitglieder (ab Einladung)</string> @@ -447,7 +447,7 @@ <string name="settings_start_on_boot">Starte beim Systemstart</string> <string name="settings_clear_media_cache">Medien-Cache leeren</string> <string name="settings_keep_media">Medien behalten</string> - <string name="settings_always_show_timestamps">Für alle Nachrichten Zeitstempel anzeigen</string> + <string name="settings_always_show_timestamps">Zeitstempel für alle Nachrichten</string> <string name="media_saving_period_3_days">3 Tage</string> <string name="media_saving_period_1_week">1 Woche</string> <string name="media_saving_period_1_month">1 Monat</string> @@ -500,7 +500,7 @@ <string name="start_video_call_prompt_msg">Sicher, dass du einen Videoanruf starten möchtest\?</string> <string name="room_participants_ban_prompt_msg">Die Verbannung einer Person entfernt sie aus diesem Raum und hindert sie am erneuten Beitritt.</string> <string name="room_settings_all_messages">Alle Nachrichten</string> - <string name="settings_inline_url_preview">URL-Vorschau im Chat</string> + <string name="settings_inline_url_preview">URL-Vorschau</string> <string name="settings_vibrate_on_mention">Vibriere beim Erwähnen eines Nutzers</string> <string name="create">Erstellen</string> <string name="group_details_home">Startseite</string> @@ -605,7 +605,7 @@ <string name="generic_label_and_value">%1$s: %2$s</string> <string name="x_plus">+%d</string> <string name="room_participants_action_remove">Aus Unterhaltung entfernen</string> - <string name="settings_inline_url_preview_summary">Linkvorschau im Chat aktivieren, falls dein Homeserver diese Funktion unterstützt.</string> + <string name="settings_inline_url_preview_summary">Link-Vorschau im Chat aktivieren, falls dein Heim-Server diese Funktion unterstützt.</string> <string name="settings_send_typing_notifs">Schreibbenachrichtigungen senden</string> <string name="settings_send_typing_notifs_summary">Lasse andere Benutzer wissen, dass du tippst.</string> <string name="settings_send_markdown">Markdown-Formatierung</string> @@ -729,10 +729,10 @@ <string name="keys_backup_setup_step3_generating_key_status">Wiederherstellungsschlüssel aus Passphrase generieren. Dies kann mehrere Sekunden brauchen.</string> <string name="keys_backup_setup_skip_msg">Du verlierst möglicherweise den Zugang zu deinen Nachrichten, wenn du dich abmeldest oder das Gerät verlierst.</string> <string name="keys_backup_restore_is_getting_backup_version">Rufe Backup-Version ab…</string> - <string name="keys_backup_restore_with_passphrase">Nutze deine Wiederherstellungspassphrase, um deinen verschlüsselten Chatverlauf lesen zu können</string> + <string name="keys_backup_restore_with_passphrase">Nutze deine Wiederherstellungs-Passphrase, um deinen verschlüsselten Nachrichtenverlauf lesen zu können</string> <string name="keys_backup_restore_use_recovery_key">nutze deinen Wiederherstellungsschlüssel</string> <string name="keys_backup_restore_with_passphrase_helper_with_link">Wenn du deine Wiederherstellungspassphrase nicht weist, kannst du %s.</string> - <string name="keys_backup_restore_with_recovery_key">Nutze deinen Wiederherstellungsschlüssel, um deinen verschlüsselten Chatverlauf lesen zu können</string> + <string name="keys_backup_restore_with_recovery_key">Nutze deinen Wiederherstellungsschlüssel, um deinen verschlüsselten Nachrichtenverlauf lesen zu können</string> <string name="keys_backup_restore_with_key_helper">Hast du deinen Wiederherstellungsschlüssel verloren\? Du kannst einen neuen in den Einstellungen einrichten.</string> <string name="keys_backup_passphrase_error_decrypt">Sicherung konnte mit dieser Passphrase nicht entschlüsselt werden. Bitte stelle sicher, dass du die korrekte Wiederherstellungspassphrase eingegeben hast.</string> <string name="keys_backup_recovery_code_empty_error_message">Gib deinen Wiederherstellungsschlüssel ein</string> @@ -757,7 +757,7 @@ <string name="keys_backup_settings_invalid_signature_from_verified_device">Die Sicherung hat eine ungültige Signatur von der verifizierten Sitzung %s</string> <string name="keys_backup_settings_invalid_signature_from_unverified_device">Die Sicherung hat eine ungültige Signatur von der nicht verifizierten Sitzung %s</string> <string name="keys_backup_settings_untrusted_backup">Um die Schlüsselsicherung für diese Sitzung zu verwenden, stelle sie jetzt mit deiner Passphrase oder deinem Wiederherstellungsschlüssel wieder her.</string> - <string name="keys_backup_settings_delete_confirm_message">Deine gesicherten Schlüssel vom Server löschen\? Du wirst deinen Wiederherstellungsschlüssel nicht mehr nutzen können, um deinen verschlüsselten Chatverlauf zu lesen.</string> + <string name="keys_backup_settings_delete_confirm_message">Deine gesicherten Schlüssel vom Server löschen\? Du wirst deinen Wiederherstellungsschlüssel nicht mehr nutzen können, um deinen verschlüsselten Nachrichtenverlauf zu lesen.</string> <string name="sign_out_bottom_sheet_warning_no_backup">Beim Abmelden gehen deine verschlüsselten Nachrichten verloren</string> <string name="sign_out_bottom_sheet_warning_backing_up">Schlüssel-Sicherung wird durchgeführt. Wenn du dich jetzt abmeldest, gehen deine verschlüsselten Nachrichten verloren.</string> <string name="sign_out_bottom_sheet_warning_backup_not_active">Schlüsselsicherung sollte bei allen Sitzungen aktiviert sein, um den Verlust verschlüsselter Nachrichten zu verhindern.</string> @@ -889,7 +889,7 @@ <string name="settings_other_third_party_notices">Sonstige Hinweise Dritter</string> <string name="navigate_to_room_when_already_in_the_room">Du siehst diesen Raum bereits!</string> <string name="settings_general_title">Allgemein</string> - <string name="settings_preferences">Einstellungen</string> + <string name="settings_preferences">Optionen</string> <string name="settings_security_and_privacy">Sicherheit und Privatsphäre</string> <string name="settings_push_rules">Push-Regeln</string> <string name="settings_push_rules_no_rules">Keine Push-Regeln definiert</string> @@ -1004,7 +1004,7 @@ <string name="a11y_open_drawer">Navigationsmenü öffnen</string> <string name="a11y_create_menu_open">Raumerstellungsmenü öffnen</string> <string name="a11y_create_menu_close">Schließe das Raumerstellungsmenü…</string> - <string name="a11y_create_direct_message">Starte einen neuen Privatchat</string> + <string name="a11y_create_direct_message">Erstelle eine neue Direktnachricht</string> <string name="a11y_create_room">Erstelle einen neuen Raum</string> <string name="a11y_close_keys_backup_banner">Schließe Key-Backup-Einblendung</string> <string name="a11y_jump_to_bottom">Zum Ende springen</string> @@ -1052,7 +1052,7 @@ <string name="help_long_click_on_room_for_more_options">Halte auf einem Raum um mehr Optionen anzuzeigen</string> <string name="room_join_rules_public">%1$s hat den Raum für jeden, der den Link hat, öffentlich gemacht.</string> <string name="timeline_unread_messages">Ungelesene Nachrichten</string> - <string name="login_splash_text1">Privat oder in Gruppen mit Leuten chatten</string> + <string name="login_splash_text1">Schreibe privat oder in Gruppen</string> <string name="login_splash_text2">Halte Gespräche mittels Verschlüsselung privat</string> <string name="login_splash_submit">Los geht\'s</string> <string name="login_server_title">Wähle einen Server</string> @@ -1081,13 +1081,13 @@ <string name="login_registration_disabled">Es tut uns leid. Dieser Server akzeptiert keine neuen Benutzerkonten.</string> <string name="login_registration_not_supported">Die Anwendung kann kein neues Benutzerkonto auf diesem Server erstellen. \n -\nMöchtest du dich über eine Web-Anwendung anmelden\?</string> +\nMöchtest du dich mit einer Web-Anwendung anmelden\?</string> <string name="login_login_with_email_error">Diese E-Mail-Adresse ist mit keinem Benutzerkonto verknüpft.</string> <string name="login_reset_password_on">Passwort auf %1$s zurücksetzen</string> <string name="login_reset_password_email_hint">E-Mail</string> <string name="login_reset_password_password_hint">Neues Passwort</string> <string name="login_reset_password_warning_title">Achtung!</string> - <string name="login_reset_password_warning_content">Eine Änderung deines Passworts wird alle Ende-zu-Ende-Schlüssel zurücksetzen. Dein verschlüsselter Chatverlauf wird dadurch unlesbar. Richte die Schlüsselsicherung ein oder exportiere deine Raumschlüssel aus einer anderen Sitzung, bevor du dein Passwort zurücksetzt.</string> + <string name="login_reset_password_warning_content">Eine Änderung deines Passworts wird alle Ende-zu-Ende-Schlüssel zurücksetzen. Dein verschlüsselter Verlauf wird dadurch unlesbar. Richte die Schlüsselsicherung ein oder exportiere deine Raumschlüssel aus einer anderen Sitzung, bevor du dein Passwort zurücksetzt.</string> <string name="login_reset_password_warning_submit">Fortfahren</string> <string name="login_reset_password_error_not_found">Diese E-Mail-Adresse ist mit keinem Benutzerkonto verknüpft</string> <string name="login_reset_password_mail_confirmation_title">Prüfe deinen Posteingang</string> @@ -1126,9 +1126,9 @@ <string name="login_splash_title">Es ist deine Konversation. Mache sie dir zu eigen.</string> <string name="login_server_url_form_modular_text">Premium-Hosting für Organisationen</string> <string name="login_server_url_form_modular_notice">Gib die Adresse des Modular Element oder Servers ein, den du verwenden möchtest</string> - <string name="login_mode_not_supported">Die Anwendung kann sich nicht bei diesem Homeserver anmelden. Der Homeserver unterstützt die folgenden Anmeldemöglichkeiten: %1$s. + <string name="login_mode_not_supported">Die Anwendung kann sich nicht bei diesem Heim-Server anmelden. Der Heim-Server unterstützt die folgenden Anmeldemöglichkeiten: %1$s. \n -\nMöchtest du dich mit einem Webclient anmelden\?</string> +\nMöchtest du dich mit einer Web-Anwendung anmelden\?</string> <string name="login_reset_password_notice">Dir wird eine Bestätigungsmail gesendet, um dein neues Passwort zu bestätigen.</string> <string name="login_reset_password_submit">Weiter</string> <string name="login_reset_password_success_notice_2">Du wurdest von allen Sitzungen abgemeldet und erhältst keine Push-Benachrichtigungen mehr. Um Benachrichtigungen wieder zu aktivieren, melde dich auf jedem Gerät erneut an.</string> @@ -1373,7 +1373,7 @@ <string name="error_failed_to_import_keys">Import der Schlüssel fehlgeschlagen</string> <string name="settings_notification_configuration">Benachrichtigungskonfiguration</string> <string name="settings_messages_at_room">Nachrichten mit \"@room\"</string> - <string name="settings_messages_in_e2e_group_chat">Verschlüsselte Gruppenchats</string> + <string name="settings_messages_in_e2e_group_chat">Verschlüsselte Gruppenunterhaltungen</string> <string name="command_description_plain">Sendet eine Nachricht als einfachen Text, ohne sie als Markdown zu interpretieren</string> <string name="auth_invalid_login_param_space_in_password">Inkorrekter Benutzername und/oder Passwort. Das eingegebene Passwort beginnt oder endet mit Leerzeichen, bitte kontrolliere es.</string> <string name="message_key">Nachrichtenschlüssel</string> @@ -1381,7 +1381,7 @@ <string name="bootstrap_crosssigning_print_it">Druck es aus und speichere es an einem sicheren Ort</string> <string name="bootstrap_crosssigning_save_cloud">Kopier es in deinen persönlichen Cloud-Speicher</string> <string name="encryption_not_enabled">Verschlüsselung ist nicht aktiviert</string> - <string name="settings_when_rooms_are_upgraded">Raumupgrades</string> + <string name="settings_when_rooms_are_upgraded">Raumaktualisierung</string> <string name="encryption_enabled">Verschlüsselung aktiviert</string> <string name="encryption_enabled_tile_description">Nachrichten in diesem Raum sind Ende-zu-Ende-verschlüsselt. Erfahre mehr und verifiziere Benutzer in deren Profil.</string> <string name="encryption_unknown_algorithm_tile_description">Die Verschlüsselung in diesem Raum wird nicht unterstützt</string> @@ -1392,7 +1392,7 @@ <string name="qr_code_scanned_verif_waiting_notice">Fast geschafft! Warte auf Bestätigung…</string> <string name="settings_messages_in_e2e_one_to_one">Verschlüsselte Direktnachrichten</string> <string name="room_message_placeholder">Nachricht…</string> - <string name="security_prompt_text">Verifiziere dich und andere, um eure Chats zu schützen</string> + <string name="security_prompt_text">Verifiziere dich und andere, um eure Unterhaltungen zu schützen</string> <string name="bootstrap_enter_recovery">Gib zum Fortfahren deinen %s ein</string> <string name="use_file">Datei benutzen</string> <string name="bootstrap_invalid_recovery_key">Dies ist kein gültiger Wiederherstellungsschlüssel</string> @@ -1412,12 +1412,12 @@ <string name="settings_security_prevent_screenshots_title">Bildschirmfotos der Anwendung verhindern</string> <string name="settings_security_prevent_screenshots_summary">Das Aktivieren dieser Einstellung setzt FLAG_SECURE in allen Aktivitäten. Starte die Anwendung neu, damit die Änderung wirksam wird.</string> <string name="change_password_summary">Neues Benutzerpasswort festlegen…</string> - <string name="use_other_session_content_description">Nutze die neueste Version von ${app_name} auf deinen anderen Geräten, ${app_name} Web, ${app_name} Desktop, ${app_name} iOS, ${app_name} für Android oder einen anderen cross-signing-fähigen Matrix-Client</string> + <string name="use_other_session_content_description">Nutze die neueste Version von ${app_name} auf deinen anderen Geräten, ${app_name} Web, ${app_name} Desktop, ${app_name} iOS, ${app_name} für Android oder eine andere Matrix-Anwendung, die Quersignierung unterstützt</string> <string name="app_desktop_web">${app_name} Web \n${app_name} Desktop</string> <string name="app_ios_android">${app_name} iOS \n${app_name} Android</string> - <string name="or_other_mx_capable_client">oder einen anderen cross-signing-fähigen Matrix Client</string> + <string name="or_other_mx_capable_client">oder eine andere Matrix-Anwendung, die Quersignierung unterstützt</string> <string name="use_latest_app">Nutze die neueste Version von ${app_name} auf deinen anderen Geräten:</string> <string name="command_description_discard_session">Erzwingt das Verwerfen der aktuell ausgehende Gruppensitzung in einem verschlüsseltem Raum</string> <string name="command_description_discard_session_not_handled">Wird nur in verschlüsselten Räumen unterstützt</string> @@ -1570,7 +1570,7 @@ <string name="identity_server_set_alternative_submit">Bestätigen</string> <string name="power_level_edit_title">Lege Rolle fest</string> <string name="power_level_title">Rolle</string> - <string name="a11y_open_chat">Öffne Chat</string> + <string name="a11y_open_chat">Unterhaltung öffnen</string> <string name="a11y_mute_microphone">Stelle Mikrophon stumm</string> <string name="a11y_unmute_microphone">Aktiviere Mikrophon</string> <string name="a11y_stop_camera">Stoppe Kamera</string> @@ -1704,7 +1704,7 @@ <string name="bad_passphrase_key_reset_all_action">Alle Wiederherstellungsoptionen vergessen oder verloren\? Alles zurücksetzen</string> <string name="direct_room_created_summary_item_by_you">Du bist beigetreten.</string> <string name="direct_room_created_summary_item">%s ist beigetreten.</string> - <string name="direct_room_encryption_enabled_tile_description">Nachrichten in diesem Raum sind Ende-zu-Ende-verschlüsselt.</string> + <string name="direct_room_encryption_enabled_tile_description">Nachrichten in dieser Unterhaltung sind Ende-zu-Ende-verschlüsselt.</string> <string name="direct_room_profile_section_more_leave">Verlassen</string> <string name="direct_room_profile_section_more_settings">Einstellungen</string> <string name="direct_room_profile_encrypted_subtitle">Nachrichten hier sind Ende-zu-Ende-verschlüsselt. @@ -1744,7 +1744,7 @@ <string name="room_member_open_or_create_dm">Direktnachricht</string> <string name="send_bug_report_include_key_share_history">Verlauf der Anfragen von Schlüsselfreigaben senden</string> <string name="no_more_results">Keine weiteren Ergebnisse</string> - <string name="start_chatting">Beginne ein Gespräch</string> + <string name="start_chatting">Beginne eine Unterhaltung</string> <string name="settings_discovery_consent_action_give_consent">Autorisieren</string> <string name="settings_discovery_consent_action_revoke">Meine Zustimmung widerrufen</string> <string name="settings_discovery_consent_notice_on">Du hast zugestimmt E-Mails und Telefonnummern an diesen Identitätsserver zu senden, um von anderen Nutzern entdeckt zu werden.</string> @@ -1774,7 +1774,7 @@ <string name="phone_book_perform_lookup">Suche nach Kontakten auf Matrix</string> <string name="room_settings_set_avatar">Raumbild einrichten</string> <string name="identity_server_user_consent_not_provided">Einverständnis wurde nicht abgegeben.</string> - <string name="user_code_info_text">Teile diesen Code mit Leuten, damit sie ihn scannen und mit dir chatten können.</string> + <string name="user_code_info_text">Teile diesen Code, damit andere ihn einlesen und mit dir schreiben können.</string> <string name="user_code_share">Meinen Code teilen</string> <string name="user_code_my_code">Mein Code</string> <string name="user_code_scan">Scanne einen QR-Code</string> @@ -1849,7 +1849,7 @@ <string name="settings_show_emoji_keyboard_summary">Knopf zum Nachrichteneditor hinzufügen, der die Emoji-Tastatur öffnet</string> <string name="settings_show_emoji_keyboard">Emoji-Tastatur anzeigen</string> <string name="settings_chat_effects_description">Nutze /confetti oder sende Nachrichten mit ❄️ oder 🎉</string> - <string name="settings_chat_effects_title">Chateffekte</string> + <string name="settings_chat_effects_title">Effekte im Verlauf</string> <string name="room_permissions_change_topic">Thema ändern</string> <string name="room_permissions_upgrade_the_room">Raum aktualisieren</string> <string name="room_permissions_notice">Rollen, die zum Ändern verschiedener Teile des Raums erforderlich sind, auswählen</string> @@ -1960,7 +1960,7 @@ <string name="invite_to_space_with_name_desc">Diese werden in der Lage sein, %s zu durchsuchen</string> <string name="invite_just_to_this_room_desc">Diese werden kein Teil von %s sein</string> <string name="share_space_link_message">Tritt meinem Space %1$s %2$s bei</string> - <string name="spaces_beta_welcome_to_spaces_desc">Mit Spaces kannst du Personen und Räume gruppieren.</string> + <string name="spaces_beta_welcome_to_spaces_desc">Spaces sind eine neue Möglichkeit, Räume und Personen zu gruppieren.</string> <string name="space_add_existing_rooms">Räume oder Spaces hinzufügen</string> <string name="skip_for_now">Vorübergehend überspringen</string> <string name="create_spaces_room_public_header">Über welche Themen möchtest du dich in %s unterhalten\?</string> @@ -2051,7 +2051,7 @@ <string name="private_space">Privater Space</string> <string name="public_space">Öffentlicher Space</string> <string name="call_transfer_unknown_person">Unbekannte Person</string> - <string name="give_feedback">Feedback geben</string> + <string name="give_feedback">Rückmeldung geben</string> <string name="feedback_failed">Fehler beim Senden vom Feedback (%s)</string> <string name="feedback_sent">Dein Feedback wurde erfolgreich versandt. Danke!</string> <string name="you_may_contact_me">Mich bei Fragen kontaktieren</string> @@ -2105,14 +2105,14 @@ <string name="sent_a_voice_message">Sprachnachricht</string> <string name="decide_who_can_find_and_join">Lege fest, wer diesen Raum finden und betreten kann.</string> <string name="tap_to_edit_spaces">Klicke, um die Spaces zu bearbeiten</string> - <string name="select_spaces">Spaces auswählen</string> + <string name="select_spaces">Spaces wählen</string> <string name="room_create_member_of_space_name_can_join">Mitglieder von %s können Räume finden, betrachten und betreten.</string> <string name="room_settings_room_access_private_invite_only_title">Privat (Zutritt nur mit Einladung)</string> <string name="settings_room_upgrades">Raumupgrades</string> <string name="settings_messages_by_bot">Nachrichten von Bots</string> <string name="settings_room_invitations">Raumeinladungen</string> - <string name="settings_encrypted_group_messages">Verschlüsselten Gruppenchats</string> - <string name="settings_group_messages">Gruppenchats</string> + <string name="settings_encrypted_group_messages">Verschlüsselte Gruppennachrichten</string> + <string name="settings_group_messages">Gruppennachrichten</string> <string name="settings_encrypted_direct_messages">Verschlüsselten Direktnachrichten</string> <string name="settings_messages_direct_messages">Direktnachrichten</string> <string name="settings_messages_containing_username">Mein Benutzername</string> @@ -2268,7 +2268,7 @@ <string name="preference_help_title">Hilfe und Unterstützung</string> <string name="preference_help">Hilfe</string> <string name="preference_root_legals">Rechtliches</string> - <string name="decide_which_spaces_can_access">Entscheide, welche Spaces Zugriff auf den Raum haben sollen. Die Mitglieder der Spaces können diesen Räumen beitreten.</string> + <string name="decide_which_spaces_can_access">Entscheide, welche Spaces Zugriff auf den Raum haben sollen. Die Mitglieder der Spaces können diesen Räumen betreten.</string> <string name="analytics_opt_in_content_link">hier</string> <string name="analytics_opt_in_title">Hilf mit, ${app_name} zu verbessern</string> <string name="action_enable">Aktivieren</string> @@ -2363,10 +2363,10 @@ <string name="ftue_auth_use_case_option_three">Communities</string> <string name="ftue_auth_use_case_option_two">Teams</string> <string name="ftue_auth_use_case_subtitle">Wir helfen dir, in Verbindung zu kommen</string> - <string name="ftue_auth_use_case_title">Mit wem wirst du am meisten chatten\?</string> + <string name="ftue_auth_use_case_title">Mit wem wirst du am meisten schreiben\?</string> <string name="action_thread_copy_link_to_thread">Link zu Thread kopieren</string> <string name="action_view_threads">Threads anzeigen</string> - <string name="message_bubbles">Nachrichtenblasen anzeigen</string> + <string name="message_bubbles">Nachrichtenblasen</string> <string name="location_timeline_failed_to_load_map">Laden der Karte fehlgeschlagen</string> <string name="a11y_static_map_image">Karte</string> <string name="labs_enable_thread_messages_desc">Hinweis: App wird neugestartet</string> @@ -2409,19 +2409,19 @@ <string name="threads_notice_migration_title">Threads nähern sich der Beta 🎉</string> <string name="action_disable">Deaktivieren</string> <string name="beta">BETA</string> - <string name="give_feedback_threads">Feedback geben</string> + <string name="give_feedback_threads">Rückmeldung geben</string> <string name="beta_title_bottom_sheet_action">BETA</string> <string name="threads_labs_enable_notice_title">Threads Beta</string> <string name="threads_beta_enable_notice_title">Threads Beta</string> <string name="call_start_screen_sharing">Bildschirm teilen</string> - <string name="action_try_it_out">Ausprobieren</string> + <string name="action_try_it_out">Probiere es aus</string> <string name="location_share_live_until">Live bis %1$s</string> <string name="unifiedpush_getdistributors_dialog_title">Wähle Deine Benachrichtigungsmethode</string> <string name="labs_enable_live_location_summary">Vorläufige Implementierung: Standorte bleiben im Nachrichtenverlauf von Räumen erhalten</string> <string name="push_gateway_item_profile_tag">Profil-Tag:</string> <string name="time_unit_hour_short">h</string> <string name="live_location_labs_promotion_switch_title">Standortfreigabe aktivieren</string> - <string name="live_location_labs_promotion_description">Bitte beachten: Dies ist eine Testfunktion mit einer vorübergehenden Implementierung. Das bedeutet, dass Du Deinen Standortverlauf nicht löschen kannst und dass fortgeschrittene Nutzer Deinen Standortverlauf auch dann noch sehen können, wenn Du Deinen Live-Standort nicht mehr mit diesem Raum teilst.</string> + <string name="live_location_labs_promotion_description">Bitte beachte: Dies ist eine experimentelle Funktion, die eine temporäre Implementierung nutzt. Das bedeutet, dass du deinen Standortverlauf nicht löschen kannst und erfahrene Nutzer ihn sehen können, selbst wenn du deinen Live-Standort nicht mehr mit diesem Raum teilst.</string> <string name="live_location_labs_promotion_title">Live-Standortfreigabe</string> <string name="settings_troubleshoot_test_current_gateway">Aktuelles Gateway: %s</string> <string name="settings_troubleshoot_test_current_gateway_title">Gateway</string> @@ -2464,7 +2464,7 @@ <string name="a11y_audio_playback_duration">%1$d Minuten %2$d Sekunden</string> <string name="a11y_audio_message_item">%1$s, %2$s, %3$s</string> <string name="settings_show_latest_profile_description">Die neuesten Profilinformationen (Avatar und Anzeigename) für alle Nachrichten anzeigen.</string> - <string name="settings_show_latest_profile">Aktuelle Benutzerinformationen anzeigen</string> + <string name="settings_show_latest_profile">Aktuelle Profilinformationen</string> <string name="ftue_personalize_complete_title">Sieht gut aus!</string> <string name="ftue_display_name_title">einen Anzeigenamen wählen</string> <string name="ftue_account_created_take_me_home">Zurück zum Home-Screen</string> @@ -2521,7 +2521,7 @@ <string name="ftue_account_created_personalize">Profil personalisieren</string> <string name="ftue_auth_carousel_workplace_body">${app_name} ist auch für den Arbeitsplatz geeignet. Die sichersten Organisationen der Welt vertrauen darauf.</string> <string name="send_feedback_threads_info">Threads sind noch in Arbeit, und es stehen neue, aufregende Funktionen an, wie z. B. verbesserte Benachrichtigungen. Wir würden uns sehr über Dein Feedback freuen!</string> - <string name="direct_room_encryption_enabled_tile_description_future">Nachrichten in diesem Chat werden Ende-zu-Ende-verschlüsselt.</string> + <string name="direct_room_encryption_enabled_tile_description_future">Nachrichten in dieser Unterhaltung werden Ende-zu-Ende-verschlüsselt.</string> <string name="ftue_auth_captcha_title">Bist du ein Mensch\?</string> <string name="ftue_auth_terms_subtitle">Bitte lies dir %ss Bedingungen und Richtlinien durch</string> <string name="ftue_auth_terms_title">Server-Richtlinien</string> @@ -2585,7 +2585,7 @@ <string name="create_room">Raum erstellen</string> <string name="room_list_filter_unreads">Ungelesene</string> <string name="room_list_filter_people">Personen</string> - <string name="send_your_first_msg_to_invite">Schreibe deine erste Nachricht, um %s zur Konversation einzuladen</string> + <string name="send_your_first_msg_to_invite">Schreibe deine erste Nachricht, um %s zur Unterhaltung einzuladen</string> <string name="device_manager_settings_active_sessions_show_all">Alle Sitzungen anzeigen (V2, in Arbeit)</string> <string name="device_manager_sessions_other_description">Für bestmögliche Sicherheit verifiziere deine Sitzungen und melde dich von allen ab, die du nicht erkennst oder nutzt.</string> <string name="device_manager_sessions_other_title">Andere Sitzungen</string> @@ -2619,7 +2619,7 @@ <string name="timeline_error_room_not_found">Entschuldigung, dieser Raum wurde nicht gefunden. \nBitte versuche es später erneut.%s</string> <string name="invites_title">Einladungen</string> - <string name="device_manager_other_sessions_description_unverified">Nicht verifiziert · Letzte Aktivität %1$s</string> + <string name="device_manager_other_sessions_description_unverified">Nicht verifiziert · Neueste Aktivität %1$s</string> <string name="device_manager_verification_status_unverified">Nicht verifizierte Sitzung</string> <string name="device_manager_unverified_sessions_title">Nicht verifizierte Sitzung</string> <string name="device_manager_header_section_security_recommendations_description">Verbessere deine Kontosicherheit, indem du diese Empfehlungen beherzigst.</string> @@ -2628,15 +2628,85 @@ <item quantity="one">Inaktiv seit %1$d+ Tag (%2$s)</item> <item quantity="other">Inaktiv seit %1$d+ Tagen (%2$s)</item> </plurals> - <string name="device_manager_other_sessions_description_verified">Verifiziert · Letzte Aktivität %1$s</string> + <string name="device_manager_other_sessions_description_verified">Verifiziert · Neueste Aktivität %1$s</string> <string name="device_manager_verification_status_verified">Verifizierte Sitzung</string> <string name="a11y_device_manager_device_type_unknown">Unbekannter Gerätetyp</string> <string name="invites_empty_title">Nichts Neues.</string> - <string name="space_list_empty_message">Spaces sind eine neue Art, Räume und Personen zu organisieren. Erstelle einen Space, um zu beginnen.</string> + <string name="space_list_empty_message">Spaces sind eine neue Möglichkeit, Räume und Personen zu gruppieren. Erstelle einen Space, um zu beginnen.</string> <string name="space_list_empty_title">Noch keine Spaces.</string> <string name="home_empty_no_unreads_message">Hier werden deine ungelesenen Nachrichten erscheinen, wenn du welche hast.</string> <string name="home_empty_no_unreads_title">Es gibt nichts Neues.</string> <string name="all_chats">Alle Unterhaltungen</string> <string name="change_space">Space wechseln</string> <string name="start_chat">Unterhaltung beginnen</string> -</resources> + <string name="device_manager_filter_bottom_sheet_title">Filter</string> + <string name="a11y_device_manager_filter">Filtern</string> + <string name="a11y_collapse_space_children">Subspaces von %s schließen</string> + <string name="a11y_expand_space_children">Subspaces von %s erweitern</string> + <string name="ftue_auth_create_account_username_entry_footer">Andere können dich als %s finden</string> + <string name="labs_enable_deferred_dm_summary">Erstelle Unterhaltungen mit der ersten Nachricht</string> + <string name="labs_enable_deferred_dm_title">Verzögerte Direktnachrichten</string> + <string name="home_layout_preferences_recents">Historie anzeigen</string> + <string name="onboarding_new_app_layout_button_try">Probiere es aus</string> + <string name="onboarding_new_app_layout_feedback_message">Tippe oben rechts, um eine Rückmeldung zu senden.</string> + <string name="onboarding_new_app_layout_feedback_title">Rückmeldung geben</string> + <string name="onboarding_new_app_layout_spaces_message">Greife auf deine Spaces (unten rechts) schneller und einfacher denn je zu.</string> + <string name="onboarding_new_app_layout_spaces_title">Auf Spaces zugreifen</string> + <string name="onboarding_new_app_layout_welcome_message">Um dein ${app_name} zu vereinfachen, sind Tabs nun optional. Verwalte sie mit dem Menü oben rechts.</string> + <string name="onboarding_new_app_layout_welcome_title">Willkommen in einer neuen Übersicht!</string> + <string name="home_empty_no_rooms_message">Die Komplettlösung für sichere Kommunikation unter Freunden, in Gruppen oder in Organisationen. Erstelle eine Unterhaltung oder trete einem bestehenden Raum bei, um loszulegen.</string> + <string name="home_empty_no_rooms_title">Willkommen bei ${app_name}, +\n%s.</string> + <string name="home_empty_space_no_rooms_message">Spaces sind eine neue Möglichkeit, Räume und Personen zu gruppieren. Füge einen bestehenden Raum hinzu oder erstelle einen neuen mit der Schaltfläche unten rechts.</string> + <string name="home_empty_space_no_rooms_title">%s +\nsieht ein bisschen leer aus.</string> + <string name="device_manager_session_details_device_ip_address">IP-Adresse</string> + <string name="device_manager_session_details_session_name">Sitzungsname</string> + <string name="device_manager_session_details_description">Anwendung, Gerät und Aktivitätsinformationen.</string> + <string name="device_manager_session_details_title">Sitzungsdetails</string> + <string name="device_manager_other_sessions_clear_filter">Filter zurücksetzen</string> + <string name="device_manager_other_sessions_no_inactive_sessions_found">Keine inaktiven Sitzungen gefunden.</string> + <string name="device_manager_other_sessions_no_unverified_sessions_found">Keine nicht verifizierten Sitzungen gefunden.</string> + <string name="device_manager_other_sessions_no_verified_sessions_found">Keine verifizierten Sitzungen gefunden.</string> + <plurals name="device_manager_other_sessions_recommendation_description_inactive"> + <item quantity="one">Erwäge, dich aus alten Sitzungen (%1$d Tag oder mehr) abzumelden, die du nicht mehr benutzt.</item> + <item quantity="other">Erwäge, dich aus alten Sitzungen (%1$d Tage oder mehr) abzumelden, die du nicht mehr benutzt.</item> + </plurals> + <string name="device_manager_other_sessions_recommendation_title_inactive">Inaktiv</string> + <string name="device_manager_other_sessions_recommendation_description_unverified">Für besonders sichere Kommunikation verifiziere deine Sitzungen oder melde dich von ihnen ab, falls du sie nicht mehr identifizieren kannst.</string> + <string name="device_manager_other_sessions_recommendation_title_unverified">Nicht verifiziert</string> + <string name="device_manager_other_sessions_recommendation_title_verified">Verifiziert</string> + <plurals name="device_manager_filter_option_inactive_description"> + <item quantity="one">Inaktiv seit %1$d Tag oder länger</item> + <item quantity="other">Inaktiv seit %1$d Tagen oder länger</item> + </plurals> + <string name="device_manager_filter_option_inactive">Inaktiv</string> + <string name="device_manager_filter_option_unverified_description">Nicht bereit für sichere Kommunikation</string> + <string name="device_manager_filter_option_unverified">Nicht verifiziert</string> + <string name="device_manager_filter_option_verified_description">Für sichere Kommunikation bereit</string> + <string name="device_manager_filter_option_verified">Verifiziert</string> + <string name="device_manager_filter_option_all_sessions">Alle Sitzungen</string> + <string name="device_manager_device_title">Gerät</string> + <string name="device_manager_session_title">Sitzung</string> + <string name="device_manager_current_session_title">Aktuelle Sitzung</string> + <plurals name="device_manager_inactive_sessions_description"> + <item quantity="one">Erwäge, dich aus alten (%1$d Tag oder mehr), nicht mehr verwendeten Sitzungen abzumelden.</item> + <item quantity="other">Erwäge, dich aus alten (%1$d Tage oder mehr), nicht mehr verwendeten Sitzungen abzumelden.</item> + </plurals> + <string name="device_manager_inactive_sessions_title">Inaktive Sitzungen</string> + <string name="device_manager_unverified_sessions_description">Nicht verifizierte Sitzungen verifizieren oder abmelden.</string> + <string name="device_manager_other_sessions_view_all">Alle anzeigen (%1$d)</string> + <string name="device_manager_verify_session">Sitzung verifizieren</string> + <string name="device_manager_verification_status_detail_other_session_verified">Diese Sitzung ist für sichere Kommunikation bereit.</string> + <string name="a11y_device_manager_device_type_desktop">Desktop</string> + <string name="invites_empty_message">Hier erscheinen deine neuen Anfragen und Einladungen.</string> + <string name="labs_enable_new_app_layout_summary">Ein vereinfachtes Element mit optionalen Tabs</string> + <string name="labs_enable_new_app_layout_title">Neues Layout aktivieren</string> + <string name="device_manager_session_details_session_last_activity">Neueste Aktivität</string> + <string name="device_manager_session_last_activity">Neueste Aktivität %1$s</string> + <string name="device_manager_verification_status_detail_current_session_unverified">Verifiziere deine aktuelle Sitzung für besonders sichere Kommunikation.</string> + <string name="device_manager_verification_status_detail_current_session_verified">Deine aktuelle Sitzung ist für sichere Kommunikation bereit.</string> + <string name="device_manager_view_details">Details anzeigen</string> + <string name="device_manager_verification_status_detail_other_session_unverified">Für bestmögliche Sicherheit und Zuverlässigkeit verifiziere diese Sitzungen oder melde dich von ihr ab.</string> + <string name="device_manager_other_sessions_recommendation_description_verified">Für bestmögliche Sicherheit melde dich von allen Sitzungen ab, die du nicht erkennst oder nutzt.</string> +</resources> \ No newline at end of file From 2a5a85af1f7aa00f384ebe159c00e1cd43cb576a Mon Sep 17 00:00:00 2001 From: Szimszon <github@oregpreshaz.eu> Date: Sat, 24 Sep 2022 15:26:06 +0000 Subject: [PATCH 037/187] Translated using Weblate (Hungarian) Currently translated at 100.0% (2419 of 2419 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/hu/ --- .../src/main/res/values-hu/strings.xml | 44 ++++++++++++++++++- 1 file changed, 43 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 3068556fe4..cac0a2eb5d 100644 --- a/library/ui-strings/src/main/res/values-hu/strings.xml +++ b/library/ui-strings/src/main/res/values-hu/strings.xml @@ -2668,4 +2668,46 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze <item quantity="other">%1$d+ napja inaktív (%2$s)</item> </plurals> <string name="invites_empty_message">Itt láthatók a meghívók és elvégzendő műveletek.</string> -</resources> + <string name="device_manager_session_details_device_ip_address">IP cím</string> + <string name="device_manager_session_details_session_last_activity">Utolsó tevékenység</string> + <string name="device_manager_session_details_session_name">Munkamenet neve</string> + <string name="device_manager_session_details_description">Alkalmazás, eszköz és aktivitás információ.</string> + <string name="device_manager_session_details_title">Munkamenet információk</string> + <string name="device_manager_other_sessions_clear_filter">Szűrő törlése</string> + <string name="device_manager_other_sessions_no_inactive_sessions_found">Nincs inaktív munkamenet.</string> + <string name="device_manager_other_sessions_no_unverified_sessions_found">Nincs ellenőrizetlen munkamenet.</string> + <string name="device_manager_other_sessions_no_verified_sessions_found">Nincs ellenőrzött munkamenet.</string> + <plurals name="device_manager_other_sessions_recommendation_description_inactive"> + <item quantity="one">Fontold meg, hogy kijelentkezel a régi munkamenetekből (%1$d napja vagy régebben használtál) amit már nem használsz.</item> + <item quantity="other">Fontold meg, hogy kijelentkezel a régi munkamenetekből (%1$d napja vagy régebben használtál) amit már nem használsz.</item> + </plurals> + <string name="device_manager_other_sessions_recommendation_title_inactive">Inaktív</string> + <string name="device_manager_other_sessions_recommendation_description_unverified">Erősítse meg a munkameneteit a még biztonságosabb csevegéshez vagy jelentkezzen ki ezekből, ha nem ismeri fel vagy már nem használja őket.</string> + <string name="device_manager_other_sessions_recommendation_title_unverified">Ellenőrizetlen</string> + <string name="device_manager_other_sessions_recommendation_description_verified">A legjobb biztonság érdekében jelentkezz ki minden olyan munkamenetből amit nem ismersz fel vagy régen használtál már.</string> + <string name="device_manager_other_sessions_recommendation_title_verified">Hitelesített</string> + <string name="a11y_device_manager_filter">Szűrés</string> + <plurals name="device_manager_filter_option_inactive_description"> + <item quantity="one">%1$d napja inaktív</item> + <item quantity="other">%1$d napja inaktív</item> + </plurals> + <string name="device_manager_filter_option_inactive">Inaktív</string> + <string name="device_manager_filter_option_unverified_description">Nem áll készen a biztonságos üzenetküldésre</string> + <string name="device_manager_filter_option_unverified">Ellenőrizetlen</string> + <string name="device_manager_filter_option_verified_description">Felkészülve a biztonságos üzenetküldésre</string> + <string name="device_manager_filter_option_verified">Hitelesített</string> + <string name="device_manager_filter_option_all_sessions">Minden munkamenet</string> + <string name="device_manager_filter_bottom_sheet_title">Szűrés</string> + <string name="device_manager_session_last_activity">Utolsó aktivitás %1$s</string> + <string name="device_manager_device_title">Eszköz</string> + <string name="device_manager_session_title">Munkamenet</string> + <string name="device_manager_current_session_title">Jelenlegi munkamenet</string> + <string name="device_manager_verification_status_detail_other_session_unverified">A jobb biztonság vagy megbízhatóság érdekében ellenőrizze vagy jelentkezzen ki ebből a munkamenetből.</string> + <string name="device_manager_verification_status_detail_current_session_unverified">Az aktuális munkamenet készen áll a biztonságos üzenetküldésre.</string> + <string name="device_manager_verification_status_detail_other_session_verified">Ez a munkamenet beállítva a biztonságos üzenetküldéshez.</string> + <string name="device_manager_verification_status_detail_current_session_verified">Az aktuális munkamenet készen áll a biztonságos üzenetküldésre.</string> + <string name="labs_enable_deferred_dm_summary">Közvetlen beszélgetés indítása csak az első üzenettel</string> + <string name="labs_enable_deferred_dm_title">Késleltetett közvetlen üzenetek engedélyezése</string> + <string name="labs_enable_new_app_layout_summary">Egyszerűsített Element opcionálisan lapokkal</string> + <string name="labs_enable_new_app_layout_title">Új kinézet engedélyezése</string> +</resources> \ No newline at end of file From 312111c35a3bdcdbb4c1dafdd68c7f34680b8b29 Mon Sep 17 00:00:00 2001 From: lvre <7uu3qrbvm@relay.firefox.com> Date: Fri, 23 Sep 2022 20:19:45 +0000 Subject: [PATCH 038/187] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2419 of 2419 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/pt_BR/ --- .../src/main/res/values-pt-rBR/strings.xml | 50 +++++++++++++++++-- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/library/ui-strings/src/main/res/values-pt-rBR/strings.xml b/library/ui-strings/src/main/res/values-pt-rBR/strings.xml index 817c7646df..23434766b6 100644 --- a/library/ui-strings/src/main/res/values-pt-rBR/strings.xml +++ b/library/ui-strings/src/main/res/values-pt-rBR/strings.xml @@ -473,7 +473,7 @@ <string name="start_video_call_prompt_msg">Você tem certeza que você quer começar uma chamada de vídeo\?</string> <string name="option_take_photo">Tirar foto</string> <string name="option_take_video">Tirar vídeo</string> - <string name="call">Chamar</string> + <string name="call">Chamada</string> <string name="room_participants_ban_prompt_msg">Banir usuária(o) vai removê-la(o) desta sala e preveni-la(o) de se juntar de novo.</string> <string name="room_settings_all_messages">Todas as mensagens</string> <string name="room_settings_add_homescreen_shortcut">Adicionar a tela de Início</string> @@ -2658,8 +2658,8 @@ <string name="device_manager_header_section_security_recommendations_description">Melhore a segurança de sua conta ao seguir estas recomendações.</string> <string name="device_manager_header_section_security_recommendations_title">Recomendações de segurança</string> <plurals name="device_manager_other_sessions_description_inactive"> - <item quantity="one">Inativa(o) por %1$d+ dia (%2$s)</item> - <item quantity="other">Inativa(o) por %1$d+ dias (%2$s)</item> + <item quantity="one">Inativa por %1$d+ dia (%2$s)</item> + <item quantity="other">Inativa por %1$d+ dias (%2$s)</item> </plurals> <string name="invites_empty_message">Isto é onde suas novas requisições e convites vão estar.</string> <string name="invites_empty_title">Nada novo.</string> @@ -2668,4 +2668,46 @@ <string name="a11y_collapse_space_children">Colapsar filhos de %s</string> <string name="a11y_expand_space_children">Expandir filhos de %s</string> <string name="change_space">Mudar Espaço</string> -</resources> + <string name="device_manager_other_sessions_recommendation_title_unverified">Não-verificadas</string> + <string name="device_manager_other_sessions_recommendation_title_verified">Verificadas</string> + <string name="device_manager_filter_option_unverified">Não-verificadas</string> + <string name="device_manager_filter_option_verified">Verificadas</string> + <string name="device_manager_other_sessions_recommendation_title_inactive">Inativas</string> + <plurals name="device_manager_filter_option_inactive_description"> + <item quantity="one">Inativas por %1$d dia ou mais longo</item> + <item quantity="other">Inativas por %1$d dias ou mais longo</item> + </plurals> + <string name="device_manager_filter_option_inactive">Inativas</string> + <string name="device_manager_session_details_device_ip_address">Endereço de IP</string> + <string name="device_manager_session_details_session_last_activity">Última atividade</string> + <string name="device_manager_session_details_session_name">Nome de sessão</string> + <string name="device_manager_session_details_description">Informação de aplicativo, dispositivo, e atividade.</string> + <string name="device_manager_session_details_title">Detalhes de sessão</string> + <string name="device_manager_other_sessions_clear_filter">Limpar Filtro</string> + <string name="device_manager_other_sessions_no_inactive_sessions_found">Nenhuma sessão inativa encontrada.</string> + <string name="device_manager_other_sessions_no_unverified_sessions_found">Nenhuma sessão não-verificada encontrada.</string> + <string name="device_manager_other_sessions_no_verified_sessions_found">Nenhuma sessão verificada encontrada.</string> + <plurals name="device_manager_other_sessions_recommendation_description_inactive"> + <item quantity="one">Considere fazer signout de sessões antigas (%1$d dia ou mais) que você não usa mais.</item> + <item quantity="other">Considere fazer signout de sessões antigas (%1$d dias ou mais) que você não usa mais.</item> + </plurals> + <string name="device_manager_other_sessions_recommendation_description_unverified">Verifique suas sessões para mensageria segura melhorada ou faça signout daquelas que você não reconhece ou usa mais.</string> + <string name="device_manager_other_sessions_recommendation_description_verified">Para melhor segurança, faça signout de qualquer sessão que você não reconhece ou usa mais.</string> + <string name="a11y_device_manager_filter">Filtrar</string> + <string name="device_manager_filter_option_verified_description">Pronta para mensageria segura</string> + <string name="device_manager_filter_option_unverified_description">Não pronta para mensageria segura</string> + <string name="device_manager_filter_option_all_sessions">Todas as sessões</string> + <string name="device_manager_filter_bottom_sheet_title">Filtrar</string> + <string name="device_manager_session_last_activity">Última atividade %1$s</string> + <string name="device_manager_device_title">Dispositivo</string> + <string name="device_manager_session_title">Sessão</string> + <string name="device_manager_current_session_title">Sessão Atual</string> + <string name="device_manager_verification_status_detail_other_session_unverified">Verifique ou faça signout desta sessão para melhor segurança e fiabilidade.</string> + <string name="device_manager_verification_status_detail_current_session_unverified">Verifique sua sessão atual para mensageria segura melhorada.</string> + <string name="device_manager_verification_status_detail_other_session_verified">Esta sessão está pronta para mensageria segura.</string> + <string name="device_manager_verification_status_detail_current_session_verified">Sua sessão atual está pronta para mensageria segura.</string> + <string name="labs_enable_deferred_dm_summary">Criar DM somente em primeira mensagem</string> + <string name="labs_enable_deferred_dm_title">Habilitar DMs diferidas</string> + <string name="labs_enable_new_app_layout_summary">Um Element simplificado com abas opcionais</string> + <string name="labs_enable_new_app_layout_title">Habilitar novo layout</string> +</resources> \ No newline at end of file From d86594099c2d5df1dbb6b945b0419f833dfbb694 Mon Sep 17 00:00:00 2001 From: Nui Harime <harime.nui@yandex.ru> Date: Sat, 24 Sep 2022 23:15:52 +0000 Subject: [PATCH 039/187] Translated using Weblate (Russian) Currently translated at 97.9% (2370 of 2419 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 | 39 +++++++++++++------ 1 file changed, 27 insertions(+), 12 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 e0ab799a57..38dd4a831e 100644 --- a/library/ui-strings/src/main/res/values-ru/strings.xml +++ b/library/ui-strings/src/main/res/values-ru/strings.xml @@ -907,7 +907,7 @@ <string name="event_redacted_by_user_reason">Событие удалено пользователем</string> <string name="event_redacted_by_admin_reason">Событие модерируется администратором комнаты</string> <string name="malformed_message">Некорректное событие, не могу отобразить</string> - <string name="create_new_room">Создать новую комнату</string> + <string name="create_new_room">Создать комнату</string> <string name="error_no_network">Нет сети. Пожалуйста, проверьте подключение к Интернету.</string> <string name="action_change">Изменить</string> <string name="change_room_directory_network">Изменить сеть</string> @@ -916,7 +916,7 @@ <string name="fab_menu_create_room">Комнаты</string> <string name="fab_menu_create_chat">Личные сообщения</string> <string name="create_room_action_create">СОЗДАТЬ</string> - <string name="create_room_name_hint">Имя</string> + <string name="create_room_name_hint">Название</string> <string name="create_room_public_title">Публичная</string> <string name="create_room_public_description">Каждый сможет присоединиться к этой комнате</string> <string name="keys_backup_unable_to_get_trust_info">Произошла ошибка при получении информации о доверии</string> @@ -958,8 +958,8 @@ <string name="room_filtering_footer_title">Не можете найти нужное\?</string> <string name="room_filtering_footer_create_new_room">Создать комнату</string> <string name="room_filtering_footer_create_new_direct_message">Отправить личное сообщение</string> - <string name="room_filtering_footer_open_room_directory">Просмотр каталога комнат</string> - <string name="room_directory_search_hint">Имя или ID (#example:matrix.org)</string> + <string name="room_filtering_footer_open_room_directory">Каталог комнат</string> + <string name="room_directory_search_hint">Название или ID (#example:matrix.org)</string> <string name="labs_swipe_to_reply_in_timeline">Жест смахивания для ответа в ленте сообщений</string> <string name="link_copied_to_clipboard">Ссылка скопирована в буфер обмена</string> <string name="creating_direct_room">Создаем комнату…</string> @@ -2116,7 +2116,7 @@ <string name="verification_scan_self_notice">Сканируйте код с помощью другого устройства или переключитесь и сканируйте с помощью этого устройства</string> <string name="create_space_alias_hint">Адрес пространства</string> <string name="error_file_too_big_simple">Файл слишком большой для загрузки.</string> - <string name="search_hint_room_name">Поиск по имени</string> + <string name="search_hint_room_name">Поиск по названию</string> <string name="send_file_step_compressing_video">Сжатие видео %d%%</string> <string name="send_file_step_compressing_image">Сжатие изображения…</string> <string name="give_feedback">Оставить отзыв</string> @@ -2374,11 +2374,11 @@ <string name="attachment_type_poll">Опрос</string> <string name="create_poll_title">Создать опрос</string> <string name="restart_the_application_to_apply_changes">Перезапустите приложение, чтобы изменения вступили в силу.</string> - <string name="labs_enable_latex_maths">Включить математику LaTeX</string> + <string name="labs_enable_latex_maths">Математика LaTeX</string> <string name="labs_auto_report_uisi_desc">Ваша система будет автоматически отправлять журналы при возникновении ошибки невозможности расшифровки</string> <string name="labs_auto_report_uisi">Автоматически сообщать об ошибках расшифровки.</string> <string name="encryption_misconfigured">Шифрование неправильно настроено</string> - <string name="room_member_override_nick_color">Изменить цвет отображаемого имени</string> + <string name="room_member_override_nick_color">Изменить цвет имени</string> <string name="room_profile_section_restore_security">Восстановить шифрование</string> <string name="contact_admin_to_restore_encryption">Обратитесь к администратору, чтобы восстановить шифрование до рабочего состояния.</string> <string name="encryption_has_been_misconfigured">Шифрование настроено неправильно.</string> @@ -2507,7 +2507,7 @@ <string name="live_location_sharing_notification_description">Идёт отправка местоположения</string> <string name="location_share_live_remaining_time">Осталось %1$s</string> <string name="live_location_bottom_sheet_last_updated_at">Обновлено %1$s назад</string> - <string name="labs_enable_live_location">Включить функцию \"Поделиться трансляцией местоположения\"</string> + <string name="labs_enable_live_location">Функция \"Поделиться трансляцией местоположения\"</string> <string name="live_location_sharing_notification_title">${app_name} Трансляция местоположения</string> <string name="location_share_live_until">Транслировать до %1$s</string> <string name="location_share_live_ended">Трансляция завершена</string> @@ -2666,7 +2666,7 @@ <string name="a11y_create_message">Создать беседу или комнату</string> <string name="device_manager_settings_active_sessions_show_all">Показать все сессии (V2, в разработке)</string> <string name="room_list_filter_people">Люди</string> - <string name="home_layout_preferences">Настройки оформления</string> + <string name="home_layout_preferences">Настройки вида</string> <string name="home_layout_preferences_filters">Фильтры</string> <string name="home_layout_preferences_recents">Недавние</string> <string name="room_list_filter_favourites">Избранные</string> @@ -2675,8 +2675,8 @@ <string name="home_layout_preferences_sort_name">А - Я</string> <string name="home_layout_preferences_sort_activity">Активности</string> <string name="home_layout_preferences_sort_by">Сортировать по</string> - <string name="explore_rooms">Обзор комнат</string> - <string name="start_chat">Начать беседу</string> + <string name="explore_rooms">Каталог комнат</string> + <string name="start_chat">Отправить ЛС</string> <string name="create_room">Создать комнату</string> <string name="device_manager_other_sessions_view_all">Посмотреть все (%1$d)</string> <string name="device_manager_header_section_security_recommendations_description">Повысьте безопасность учётной записи, следуя этим рекомендациям.</string> @@ -2711,5 +2711,20 @@ <string name="device_manager_verification_status_detail_current_session_verified">Текущая сессия готова к безопасному обмену сообщениями.</string> <string name="a11y_device_manager_device_type_web">Веб-браузер</string> <string name="space_list_empty_message">Пространства — это новый способ организации комнат и людей. Создайте пространство, чтобы начать.</string> - <string name="labs_enable_new_app_layout_title">Новое оформление</string> + <string name="labs_enable_new_app_layout_title">Новый вид</string> + <string name="home_empty_no_unreads_title">Нечего отображать.</string> + <string name="home_empty_no_unreads_message">Здесь будут отображаться непрочитанные сообщения, когда таковые будут.</string> + <string name="font_size_use_system">Присущий системе</string> + <string name="change_space">Смена пространства</string> + <string name="labs_enable_new_app_layout_summary">Упрощённый Element с дополнительными вкладками</string> + <string name="onboarding_new_app_layout_welcome_title">Добро пожаловать в новый вид!</string> + <string name="home_empty_space_no_rooms_title">%s +\nвыглядит слегка пустовато.</string> + <string name="onboarding_new_app_layout_button_try">Попробовать</string> + <string name="device_manager_session_details_description">Сведения о приложении, устройстве и активности.</string> + <string name="device_manager_verification_status_detail_current_session_unverified">Подтвердите текущую сессию для более безопасного обмена сообщениями.</string> + <string name="space_list_empty_title">Пока нет пространств.</string> + <string name="device_manager_other_sessions_recommendation_description_unverified">Подтвердите свои сессии для более безопасного обмена сообщениями или выйдите из тех, которые более не признаёте или не используете.</string> + <string name="device_manager_unverified_sessions_description">Подтвердите или выйдите из незаверенных сессий.</string> + <string name="device_manager_verification_status_detail_other_session_unverified">Подтвердите или выйдите из этой сессии для лучшей безопасности и надёжности.</string> </resources> \ No newline at end of file From e315db3c8eb910a9298cbe4a527282b413b0b648 Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk <igor_ck@outlook.com> Date: Fri, 23 Sep 2022 19:15:22 +0000 Subject: [PATCH 040/187] Translated using Weblate (Ukrainian) Currently translated at 100.0% (2419 of 2419 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 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 41626bd156..c4f1658f6b 100644 --- a/library/ui-strings/src/main/res/values-uk/strings.xml +++ b/library/ui-strings/src/main/res/values-uk/strings.xml @@ -818,7 +818,7 @@ <string name="room_widget_permission_avatar_url">URL-адреса аватара</string> <string name="room_widget_permission_display_name">Ваше показуване ім\'я</string> <string name="room_widget_revoke_access">Скасувати доступ для мене</string> - <string name="room_widget_open_in_browser">Відкрити в переглядачі</string> + <string name="room_widget_open_in_browser">Відкрити у браузері</string> <string name="room_widget_reload">Перезавантажити віджет</string> <string name="room_widget_failed_to_load">Не вдалося завантажити віджет. \n%s</string> @@ -1196,7 +1196,7 @@ <string name="use_file">Використати файл</string> <string name="verification_cannot_access_other_session">Скористатись парольною фразою відновлення або ключем</string> <string name="keys_backup_restore_with_passphrase">Скористатись відновлювальними парольною фразою або ключем</string> - <string name="use_other_session_content_description">Використовуйте найостаннішій ${app_name} на ваших інших пристроях, ${app_name} Web, ${app_name} для комп\'ютерів, ${app_name} iOS, ${app_name} для Android, або будь-який інший, здатний до перехресного підписування, Matrix-клієнт</string> + <string name="use_other_session_content_description">Використовуйте найостаннішій ${app_name} на ваших інших пристроях, ${app_name} браузері, ${app_name} комп\'ютерах, ${app_name} iOS, ${app_name} Android, або будь-який інший, здатний до перехресного підписування, Matrix-клієнт</string> <string name="use_latest_app">Використовуйте найостаннішій ${app_name} на ваших інших пристроях:</string> <string name="verification_use_passphrase">Якщо ви не можете доступитись до чинного сеансу</string> <string name="verification_open_other_to_verify">Використайте чинний сеанс, щоб звірити цей сеанс, таким чином надавши йому доступ до зашифрованих повідомлень.</string> @@ -2021,7 +2021,7 @@ <string name="failed_to_access_secure_storage">Не вдалося отримати доступ до безпечного сховища</string> <string name="app_ios_android">${app_name} iOS \n${app_name} Android</string> - <string name="app_desktop_web">${app_name} для переглядача + <string name="app_desktop_web">${app_name} для браузера \n${app_name} для ПК</string> <string name="error_saving_media_file">Не вдалося зберегти медіафайл</string> <string name="bootstrap_invalid_recovery_key">Це не дійсний ключ відновлення</string> @@ -2701,7 +2701,7 @@ <string name="a11y_open_settings">Відкрити налаштування</string> <string name="all_chats">Усі бесіди</string> <string name="device_manager_settings_active_sessions_show_all">Показати всі сеанси (V2, WIP)</string> - <string name="device_manager_sessions_other_description">Для найкращої безпеки перевірте свої сеанси та вийдіть з усіх сеансів, які ви більше не розпізнаєте або не використовуєте.</string> + <string name="device_manager_sessions_other_description">Звірте свої сеанси та вийдіть з усіх сеансів, які ви більше не розпізнаєте або не використовуєте для кращої безпеки.</string> <string name="device_manager_sessions_other_title">Інші сеанси</string> <string name="settings_sessions_list">Сеанси</string> <string name="a11y_open_spaces">Відкрити список кімнат</string> From d7ce983ef44c7bdff83a55097f2d73e13c178b10 Mon Sep 17 00:00:00 2001 From: phardyle <bradney_ccea@aleeas.com> Date: Sat, 24 Sep 2022 14:41:13 +0000 Subject: [PATCH 041/187] Translated using Weblate (Chinese (Simplified)) Currently translated at 98.8% (2392 of 2419 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/zh_Hans/ --- .../src/main/res/values-zh-rCN/strings.xml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) 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 69a2fc5eef..eba96e82c3 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 @@ -2607,4 +2607,21 @@ <string name="space_list_empty_message">空间是对房间和人进行分组的新方式。创建一个空间来开始吧。</string> <string name="labs_enable_new_app_layout_title">启用新布局</string> <string name="device_manager_session_details_device_ip_address">IP地址</string> + <string name="device_manager_other_sessions_recommendation_description_unverified">验证你的会话以增强消息传输的安全性,或从那些你不认识或不再使用的会话登出。</string> + <string name="device_manager_filter_option_unverified_description">尚未准备好安全收发消息</string> + <string name="device_manager_filter_option_verified_description">准备好安全收发消息</string> + <string name="device_manager_filter_option_verified">已验证</string> + <string name="device_manager_filter_option_all_sessions">全部会话</string> + <string name="device_manager_filter_bottom_sheet_title">筛选</string> + <string name="device_manager_session_last_activity">上次活跃%1$s</string> + <string name="device_manager_device_title">设备</string> + <string name="device_manager_session_title">会话</string> + <string name="device_manager_current_session_title">当前会话</string> + <string name="device_manager_verification_status_detail_current_session_unverified">验证你的会话以增强消息传输的安全性。</string> + <string name="onboarding_new_app_layout_spaces_message">访问你的空间(右下角)比以前更快、更容易。</string> + <string name="device_manager_verification_status_detail_other_session_verified">此会话已准备好安全地收发消息。</string> + <string name="device_manager_verification_status_detail_current_session_verified">你当前的会话已准备好安全地收发消息。</string> + <string name="labs_enable_deferred_dm_summary">仅在首条消息创建私聊消息</string> + <string name="labs_enable_deferred_dm_title">启用延迟的私聊消息</string> + <string name="labs_enable_new_app_layout_summary">简化的Element,带有可选的标签</string> </resources> \ No newline at end of file From 94b2026056e905cc4c76b98d9a0904c2c3514d90 Mon Sep 17 00:00:00 2001 From: Vri <element.io@vrifox.cc> Date: Sat, 24 Sep 2022 00:04:39 +0000 Subject: [PATCH 042/187] Translated using Weblate (German) Currently translated at 100.0% (76 of 76 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/40104360.txt | 2 +- fastlane/metadata/android/de-DE/short_description.txt | 2 +- fastlane/metadata/android/de-DE/title.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/fastlane/metadata/android/de-DE/changelogs/40104360.txt b/fastlane/metadata/android/de-DE/changelogs/40104360.txt index bf249af068..3c47fa7eb6 100644 --- a/fastlane/metadata/android/de-DE/changelogs/40104360.txt +++ b/fastlane/metadata/android/de-DE/changelogs/40104360.txt @@ -1,3 +1,3 @@ -Das neue App-Layout kann in den Labor-Einstellungen aktiviert werden. Probier es gerne aus! +Das neue App-Layout kann in den experimentellen Einstellungen aktiviert werden. Probier es gerne aus! Fehler bzgl. ausbleibender Benachrichtigungen und langwierigem inkrementellem Synchronisieren behoben. Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases diff --git a/fastlane/metadata/android/de-DE/short_description.txt b/fastlane/metadata/android/de-DE/short_description.txt index d27bd3ef12..de571645ee 100644 --- a/fastlane/metadata/android/de-DE/short_description.txt +++ b/fastlane/metadata/android/de-DE/short_description.txt @@ -1 +1 @@ -Gruppen-Messenger - verschlüsselte Kommunikation, Gruppenchat und Videoanrufe +Gruppen-Messenger – verschlüsselte Kommunikation, Gruppen und Videoanrufe diff --git a/fastlane/metadata/android/de-DE/title.txt b/fastlane/metadata/android/de-DE/title.txt index 6304f37925..edee751d06 100644 --- a/fastlane/metadata/android/de-DE/title.txt +++ b/fastlane/metadata/android/de-DE/title.txt @@ -1 +1 @@ -Element - Sicherer Messenger +Element – Sicher kommunizieren From ef344236cd0bf5c4b0698615241e3b2f602a51fb Mon Sep 17 00:00:00 2001 From: lvre <7uu3qrbvm@relay.firefox.com> Date: Fri, 23 Sep 2022 20:03:26 +0000 Subject: [PATCH 043/187] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (76 of 76 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/pt_BR/ --- fastlane/metadata/android/pt-BR/changelogs/40104360.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 fastlane/metadata/android/pt-BR/changelogs/40104360.txt diff --git a/fastlane/metadata/android/pt-BR/changelogs/40104360.txt b/fastlane/metadata/android/pt-BR/changelogs/40104360.txt new file mode 100644 index 0000000000..78a879ccb7 --- /dev/null +++ b/fastlane/metadata/android/pt-BR/changelogs/40104360.txt @@ -0,0 +1,3 @@ +Novo Layout de App poder ser habilitado nas configurações de Labs. Por favor dê uma chance! +Consertar problemas sobre notificação faltando, e sinc incremental longo. +Changelog completo: https://github.com/vector-im/element-android/releases From 30628217ae0b07017b2db2863048c10835a734c4 Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk <igor_ck@outlook.com> Date: Fri, 23 Sep 2022 19:14:02 +0000 Subject: [PATCH 044/187] Translated using Weblate (Ukrainian) Currently translated at 100.0% (76 of 76 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/uk/ --- fastlane/metadata/android/uk/full_description.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastlane/metadata/android/uk/full_description.txt b/fastlane/metadata/android/uk/full_description.txt index c046d8a40a..330ddde4ae 100644 --- a/fastlane/metadata/android/uk/full_description.txt +++ b/fastlane/metadata/android/uk/full_description.txt @@ -5,7 +5,7 @@ Element — це і безпечний месенджер, і застосуно - Повністю зашифровані повідомлення для надання можливості безпечнішого корпоративного спілкування, навіть для віддалених працівників - Децентралізований чат на основі відкритого коду Matrix - Безпечний обмін файлами із зашифрованими даними для керування проєктами -- Відеочати з передачею голосу через IP та показом екрану іншим +- Відеочати з передачею голосу через IP та показом екрана іншим - Проста інтеграція з вашими улюбленими інструментами для онлайн-співпраці, інструментами керування проєктами, послугами VoIP та іншими застосунками обміну повідомленнями для команд Element цілковито відрізняється від інших застосунків обміну повідомленнями та спільної роботи. Він працює на Matrix, відкритій мережі для безпечного обміну повідомленнями та децентралізованого зв'язку. Це дозволяє самостійне розгортання, щоб надати користувачам якнайбільше володіння та контролю над їх даними та повідомленнями. From 79e4a435dab4cbf58b7e6c6e12275e8879dbd3ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Be=C5=88o?= <benjiko99@gmail.com> Date: Sun, 24 Jul 2022 16:53:33 +0200 Subject: [PATCH 045/187] Add privacy setting to disable personalized learning by the keyboard --- changelog.d/6633.feature | 1 + library/ui-strings/src/main/res/values/strings.xml | 3 +++ .../app/features/home/room/detail/TimelineFragment.kt | 2 ++ .../home/room/detail/composer/ComposerEditText.kt | 9 +++++++++ .../im/vector/app/features/settings/VectorPreferences.kt | 7 +++++++ .../main/res/xml/vector_settings_security_privacy.xml | 6 ++++++ 6 files changed, 28 insertions(+) create mode 100644 changelog.d/6633.feature diff --git a/changelog.d/6633.feature b/changelog.d/6633.feature new file mode 100644 index 0000000000..b52e9d95bc --- /dev/null +++ b/changelog.d/6633.feature @@ -0,0 +1 @@ +Add privacy setting to disable personalized learning by the keyboard diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index d8f6222acf..4f1917f0df 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -2578,6 +2578,9 @@ <string name="settings_security_prevent_screenshots_title">Prevent screenshots of the application</string> <string name="settings_security_prevent_screenshots_summary">Enabling this setting adds the FLAG_SECURE to all Activities. Restart the application for the change to take effect.</string> + <string name="settings_security_incognito_keyboard_title">Incognito keyboard</string> + <string name="settings_security_incognito_keyboard_summary">Request that the keyboard should not update any personalized data such as typing history and dictionary based on what the user typed. Some keyboards may not respect this setting.</string> + <string name="error_saving_media_file">Could not save media file</string> <string name="change_password_summary">Set a new account password…</string> 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 bba607eeb4..0a8ae775a9 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 @@ -1536,6 +1536,8 @@ class TimelineFragment : observerUserTyping() + composerEditText.setUseIncognitoKeyboard(vectorPreferences.useIncognitoKeyboard()) + if (vectorPreferences.sendMessageWithEnter()) { // imeOptions="actionSend" only works with single line, so we remove multiline inputType composerEditText.inputType = composerEditText.inputType and EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE.inv() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/ComposerEditText.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/ComposerEditText.kt index c751053cdf..6f09d25869 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/ComposerEditText.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/ComposerEditText.kt @@ -79,6 +79,11 @@ class ComposerEditText @JvmOverloads constructor( return ic } + /** Set whether the keyboard should disable personalized learning. */ + fun setUseIncognitoKeyboard(useIncognitoKeyboard: Boolean) { + imeOptions = if (useIncognitoKeyboard) imeOptions or INCOGNITO_KEYBOARD_IME else imeOptions and INCOGNITO_KEYBOARD_IME.inv() + } + init { addTextChangedListener( object : SimpleTextWatcher() { @@ -116,4 +121,8 @@ class ComposerEditText @JvmOverloads constructor( } ) } + + companion object { + const val INCOGNITO_KEYBOARD_IME = EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING + } } 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 16d3210b45..b7812b9ebb 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 @@ -87,6 +87,7 @@ class VectorPreferences @Inject constructor( const val SETTINGS_INTEGRATION_MANAGER_UI_URL_KEY = "SETTINGS_INTEGRATION_MANAGER_UI_URL_KEY" const val SETTINGS_SECURE_MESSAGE_RECOVERY_PREFERENCE_KEY = "SETTINGS_SECURE_MESSAGE_RECOVERY_PREFERENCE_KEY" const val SETTINGS_PERSISTED_SPACE_BACKSTACK = "SETTINGS_PERSISTED_SPACE_BACKSTACK" + const val SETTINGS_SECURITY_INCOGNITO_KEYBOARD_PREFERENCE_KEY = "SETTINGS_SECURITY_INCOGNITO_KEYBOARD_PREFERENCE_KEY" const val SETTINGS_CRYPTOGRAPHY_HS_ADMIN_DISABLED_E2E_DEFAULT = "SETTINGS_CRYPTOGRAPHY_HS_ADMIN_DISABLED_E2E_DEFAULT" // const val SETTINGS_SECURE_BACKUP_RESET_PREFERENCE_KEY = "SETTINGS_SECURE_BACKUP_RESET_PREFERENCE_KEY" @@ -288,6 +289,7 @@ class VectorPreferences @Inject constructor( SETTINGS_USE_RAGE_SHAKE_KEY, SETTINGS_SECURITY_USE_FLAG_SECURE, + SETTINGS_SECURITY_INCOGNITO_KEYBOARD_PREFERENCE_KEY, ShortcutsHandler.SHARED_PREF_KEY, ) @@ -969,6 +971,11 @@ class VectorPreferences @Inject constructor( return defaultPrefs.getBoolean(SETTINGS_SECURITY_USE_FLAG_SECURE, false) } + /** Whether the keyboard should disable personalized learning. */ + fun useIncognitoKeyboard(): Boolean { + return defaultPrefs.getBoolean(SETTINGS_SECURITY_INCOGNITO_KEYBOARD_PREFERENCE_KEY, false) + } + /** * The user enable protecting app access with pin code. * Currently we use the pin code store to know if the pin is enabled, so this is not used diff --git a/vector/src/main/res/xml/vector_settings_security_privacy.xml b/vector/src/main/res/xml/vector_settings_security_privacy.xml index 1e8997e9c8..78e1ba42a8 100644 --- a/vector/src/main/res/xml/vector_settings_security_privacy.xml +++ b/vector/src/main/res/xml/vector_settings_security_privacy.xml @@ -141,6 +141,12 @@ android:summary="@string/settings_security_application_protection_summary" android:title="@string/settings_security_application_protection_title" /> + <im.vector.app.core.preference.VectorSwitchPreference + android:defaultValue="false" + android:key="SETTINGS_SECURITY_INCOGNITO_KEYBOARD_PREFERENCE_KEY" + android:summary="@string/settings_security_incognito_keyboard_summary" + android:title="@string/settings_security_incognito_keyboard_title" /> + <im.vector.app.core.preference.VectorSwitchPreference android:defaultValue="false" android:key="SETTINGS_SECURITY_USE_FLAG_SECURE" From d2f9ca4cbc170a509db61fe96e9a02c7e1a3888c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Be=C5=88o?= <benjiko99@gmail.com> Date: Sun, 25 Sep 2022 13:56:59 +0200 Subject: [PATCH 046/187] Improve summary text of Incognito Keyboard setting --- library/ui-strings/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 4f1917f0df..040510fba8 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -2579,7 +2579,7 @@ <string name="settings_security_prevent_screenshots_summary">Enabling this setting adds the FLAG_SECURE to all Activities. Restart the application for the change to take effect.</string> <string name="settings_security_incognito_keyboard_title">Incognito keyboard</string> - <string name="settings_security_incognito_keyboard_summary">Request that the keyboard should not update any personalized data such as typing history and dictionary based on what the user typed. Some keyboards may not respect this setting.</string> + <string name="settings_security_incognito_keyboard_summary">"Request that the keyboard should not update any personalized data such as typing history and dictionary based on what you've typed in conversations. Notice that some keyboards may not respect this setting."</string> <string name="error_saving_media_file">Could not save media file</string> <string name="change_password_summary">Set a new account password…</string> From e5cf431cc70d9c8e6e2a75748bcd64207f7ff135 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Be=C5=88o?= <benjiko99@gmail.com> Date: Sun, 25 Sep 2022 14:42:07 +0200 Subject: [PATCH 047/187] Apply imeOptions to ComposerEditText without overriding previously set options --- .../home/room/detail/TimelineFragment.kt | 7 +------ .../room/detail/composer/ComposerEditText.kt | 21 ++++++++++++++----- 2 files changed, 17 insertions(+), 11 deletions(-) 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 0a8ae775a9..de2f9891e1 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 @@ -1537,12 +1537,7 @@ class TimelineFragment : observerUserTyping() composerEditText.setUseIncognitoKeyboard(vectorPreferences.useIncognitoKeyboard()) - - if (vectorPreferences.sendMessageWithEnter()) { - // imeOptions="actionSend" only works with single line, so we remove multiline inputType - composerEditText.inputType = composerEditText.inputType and EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE.inv() - composerEditText.imeOptions = EditorInfo.IME_ACTION_SEND - } + composerEditText.setSendMessageWithEnter(vectorPreferences.sendMessageWithEnter()) composerEditText.setOnEditorActionListener { v, actionId, keyEvent -> val imeActionId = actionId and EditorInfo.IME_MASK_ACTION diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/ComposerEditText.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/ComposerEditText.kt index 6f09d25869..911261d46f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/ComposerEditText.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/ComposerEditText.kt @@ -81,7 +81,22 @@ class ComposerEditText @JvmOverloads constructor( /** Set whether the keyboard should disable personalized learning. */ fun setUseIncognitoKeyboard(useIncognitoKeyboard: Boolean) { - imeOptions = if (useIncognitoKeyboard) imeOptions or INCOGNITO_KEYBOARD_IME else imeOptions and INCOGNITO_KEYBOARD_IME.inv() + imeOptions = if (useIncognitoKeyboard) { + imeOptions or EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING + } else { + imeOptions and EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING.inv() + } + } + + /** Set whether enter should send the message or add a new line. */ + fun setSendMessageWithEnter(sendMessageWithEnter: Boolean) { + if (sendMessageWithEnter) { + inputType = inputType and EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE.inv() + imeOptions = imeOptions or EditorInfo.IME_ACTION_SEND + } else { + inputType = inputType or EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE + imeOptions = imeOptions and EditorInfo.IME_ACTION_SEND.inv() + } } init { @@ -121,8 +136,4 @@ class ComposerEditText @JvmOverloads constructor( } ) } - - companion object { - const val INCOGNITO_KEYBOARD_IME = EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING - } } From 079a2f53514f4270e530f980dd14058186ed8188 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Be=C5=88o?= <benjiko99@gmail.com> Date: Sun, 25 Sep 2022 14:56:20 +0200 Subject: [PATCH 048/187] Hide Incognito Keyboard setting on unsupported devices --- .../features/home/room/detail/TimelineFragment.kt | 4 +++- .../home/room/detail/composer/ComposerEditText.kt | 3 +++ .../VectorSettingsSecurityPrivacyFragment.kt | 12 ++++++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) 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 de2f9891e1..853d8918a4 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 @@ -1536,7 +1536,9 @@ class TimelineFragment : observerUserTyping() - composerEditText.setUseIncognitoKeyboard(vectorPreferences.useIncognitoKeyboard()) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + composerEditText.setUseIncognitoKeyboard(vectorPreferences.useIncognitoKeyboard()) + } composerEditText.setSendMessageWithEnter(vectorPreferences.sendMessageWithEnter()) composerEditText.setOnEditorActionListener { v, actionId, keyEvent -> diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/ComposerEditText.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/ComposerEditText.kt index 911261d46f..9e88882866 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/ComposerEditText.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/ComposerEditText.kt @@ -20,10 +20,12 @@ package im.vector.app.features.home.room.detail.composer import android.content.ClipData import android.content.Context import android.net.Uri +import android.os.Build import android.text.Editable import android.util.AttributeSet import android.view.inputmethod.EditorInfo import android.view.inputmethod.InputConnection +import androidx.annotation.RequiresApi import androidx.appcompat.widget.AppCompatEditText import androidx.core.view.OnReceiveContentListener import androidx.core.view.ViewCompat @@ -80,6 +82,7 @@ class ComposerEditText @JvmOverloads constructor( } /** Set whether the keyboard should disable personalized learning. */ + @RequiresApi(Build.VERSION_CODES.O) fun setUseIncognitoKeyboard(useIncognitoKeyboard: Boolean) { imeOptions = if (useIncognitoKeyboard) { imeOptions or EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt index 5cbdf114a5..87f5af67eb 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt @@ -20,6 +20,7 @@ package im.vector.app.features.settings import android.app.Activity import android.content.Intent import android.net.Uri +import android.os.Build import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -160,6 +161,10 @@ class VectorSettingsSecurityPrivacyFragment : findPreference<VectorSwitchPreference>("SETTINGS_USER_ANALYTICS_CONSENT_KEY")!! } + private val incognitoKeyboardPref by lazy { + findPreference<VectorSwitchPreference>(VectorPreferences.SETTINGS_SECURITY_INCOGNITO_KEYBOARD_PREFERENCE_KEY)!! + } + override fun onCreateRecyclerView(inflater: LayoutInflater, parent: ViewGroup, savedInstanceState: Bundle?): RecyclerView { return super.onCreateRecyclerView(inflater, parent, savedInstanceState).also { // Insert animation are really annoying the first time the list is shown @@ -275,6 +280,9 @@ class VectorSettingsSecurityPrivacyFragment : // Analytics setUpAnalytics() + // Incognito Keyboard + setUpIncognitoKeyboard() + // Pin code openPinCodeSettingsPref.setOnPreferenceClickListener { openPinCodePreferenceScreen() @@ -337,6 +345,10 @@ class VectorSettingsSecurityPrivacyFragment : } } + private fun setUpIncognitoKeyboard() { + incognitoKeyboardPref.isVisible = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O + } + // Todo this should be refactored and use same state as 4S section private fun refreshXSigningStatus() { val crossSigningKeys = session.cryptoService().crossSigningService().getMyCrossSigningKeys() From 1f28a2acae02d11c282a3fd2071fb720ea7bf5f6 Mon Sep 17 00:00:00 2001 From: ericdecanini <eddecanini@gmail.com> Date: Sun, 25 Sep 2022 10:45:59 -0400 Subject: [PATCH 049/187] Adds PushersMapperTest --- .../internal/session/pushers/AddPusherTask.kt | 1 + .../database/mapper/PushersMapperTest.kt | 64 +++++++++++++++++++ .../pushers/DefaultAddPusherTaskTest.kt | 6 +- .../sdk/test/fixtures/JsonPusherFixture.kt | 49 ++++++++++++++ .../sdk/test/fixtures/PusherEntityFixture.kt | 47 ++++++++++++++ 5 files changed, 164 insertions(+), 3 deletions(-) create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/mapper/PushersMapperTest.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/JsonPusherFixture.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/PusherEntityFixture.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherTask.kt index c284f006ca..3e145dc668 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherTask.kt @@ -38,6 +38,7 @@ internal class DefaultAddPusherTask @Inject constructor( private val requestExecutor: RequestExecutor, private val globalErrorReceiver: GlobalErrorReceiver ) : AddPusherTask { + override suspend fun execute(params: AddPusherTask.Params) { val pusher = params.pusher try { diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/mapper/PushersMapperTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/mapper/PushersMapperTest.kt new file mode 100644 index 0000000000..5bca9248d6 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/mapper/PushersMapperTest.kt @@ -0,0 +1,64 @@ +/* + * 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 org.matrix.android.sdk.internal.database.mapper + +import org.amshove.kluent.shouldBeEqualTo +import org.junit.Test +import org.matrix.android.sdk.test.fixtures.JsonPusherFixture.aJsonPusher +import org.matrix.android.sdk.test.fixtures.PusherEntityFixture.aPusherEntity + +class PushersMapperTest { + + @Test + fun `when mapping PusherEntity, then it is mapped into Pusher successfully`() { + val pusherEntity = aPusherEntity() + + val mappedPusher = PushersMapper.map(pusherEntity) + + mappedPusher.pushKey shouldBeEqualTo pusherEntity.pushKey + mappedPusher.kind shouldBeEqualTo pusherEntity.kind.orEmpty() + mappedPusher.appId shouldBeEqualTo pusherEntity.appId + mappedPusher.appDisplayName shouldBeEqualTo pusherEntity.appDisplayName + mappedPusher.deviceDisplayName shouldBeEqualTo pusherEntity.deviceDisplayName + mappedPusher.profileTag shouldBeEqualTo pusherEntity.profileTag + mappedPusher.lang shouldBeEqualTo pusherEntity.lang + mappedPusher.data.url shouldBeEqualTo pusherEntity.data?.url + mappedPusher.data.format shouldBeEqualTo pusherEntity.data?.format + mappedPusher.enabled shouldBeEqualTo pusherEntity.enabled + mappedPusher.deviceId shouldBeEqualTo pusherEntity.deviceId + mappedPusher.state shouldBeEqualTo pusherEntity.state + } + + @Test + fun `when mapping JsonPusher, then it is mapped into Pusher successfully`() { + val jsonPusher = aJsonPusher() + + val mappedPusherEntity = PushersMapper.map(jsonPusher) + + mappedPusherEntity.pushKey shouldBeEqualTo jsonPusher.pushKey + mappedPusherEntity.kind shouldBeEqualTo jsonPusher.kind + mappedPusherEntity.appId shouldBeEqualTo jsonPusher.appId + mappedPusherEntity.appDisplayName shouldBeEqualTo jsonPusher.appDisplayName + mappedPusherEntity.deviceDisplayName shouldBeEqualTo jsonPusher.deviceDisplayName + mappedPusherEntity.profileTag shouldBeEqualTo jsonPusher.profileTag + mappedPusherEntity.lang shouldBeEqualTo jsonPusher.lang + mappedPusherEntity.data?.url shouldBeEqualTo jsonPusher.data?.url + mappedPusherEntity.data?.format shouldBeEqualTo jsonPusher.data?.format + mappedPusherEntity.enabled shouldBeEqualTo jsonPusher.enabled + mappedPusherEntity.deviceId shouldBeEqualTo jsonPusher.deviceId + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultAddPusherTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultAddPusherTaskTest.kt index dac33069f3..a971973f56 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultAddPusherTaskTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultAddPusherTaskTest.kt @@ -71,7 +71,7 @@ class DefaultAddPusherTaskTest { } @Test - fun `given a persisted pusher when adding Pusher then updates api and mutates persisted result with Registered state`() { + fun `given a persisted pusher, when adding Pusher, then updates api and mutates persisted result with Registered state`() { val realmResult = PusherEntity(appDisplayName = null) monarchy.givenWhereReturns(result = realmResult) .givenEqualTo(PusherEntityFields.PUSH_KEY, A_JSON_PUSHER.pushKey) @@ -85,7 +85,7 @@ class DefaultAddPusherTaskTest { } @Test - fun `given a persisted push entity and SetPush API fails when adding Pusher then mutates persisted result with Failed registration state and rethrows`() { + fun `given a persisted push entity and SetPush API fails, when adding Pusher, then mutates persisted result with Failed registration state and rethrows`() { val realmResult = PusherEntity() monarchy.givenWhereReturns(result = realmResult) .givenEqualTo(PusherEntityFields.PUSH_KEY, A_JSON_PUSHER.pushKey) @@ -99,7 +99,7 @@ class DefaultAddPusherTaskTest { } @Test - fun `given no persisted push entity and SetPush API fails when adding Pusher then rethrows error`() { + fun `given no persisted push entity and SetPush API fails, when adding Pusher, then rethrows error`() { monarchy.givenWhereReturns<PusherEntity>(result = null) .givenEqualTo(PusherEntityFields.PUSH_KEY, A_JSON_PUSHER.pushKey) pushersAPI.givenSetPusherErrors(SocketException()) diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/JsonPusherFixture.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/JsonPusherFixture.kt new file mode 100644 index 0000000000..56b5dcc940 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/JsonPusherFixture.kt @@ -0,0 +1,49 @@ +/* + * 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 org.matrix.android.sdk.test.fixtures + +import org.matrix.android.sdk.internal.session.pushers.JsonPusher +import org.matrix.android.sdk.internal.session.pushers.JsonPusherData + +internal object JsonPusherFixture { + + fun aJsonPusher( + pushKey: String = "", + kind: String? = null, + appId: String = "", + appDisplayName: String? = null, + deviceDisplayName: String? = null, + profileTag: String? = null, + lang: String? = null, + data: JsonPusherData? = null, + append: Boolean? = false, + enabled: Boolean = true, + deviceId: String? = null, + ) = JsonPusher( + pushKey, + kind, + appId, + appDisplayName, + deviceDisplayName, + profileTag, + lang, + data, + append, + enabled, + deviceId, + ) +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/PusherEntityFixture.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/PusherEntityFixture.kt new file mode 100644 index 0000000000..9e3fc555be --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/PusherEntityFixture.kt @@ -0,0 +1,47 @@ +/* + * 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 org.matrix.android.sdk.test.fixtures + +import org.matrix.android.sdk.internal.database.model.PusherDataEntity +import org.matrix.android.sdk.internal.database.model.PusherEntity + +internal object PusherEntityFixture { + + fun aPusherEntity( + pushKey: String = "", + kind: String? = null, + appId: String = "", + appDisplayName: String? = null, + deviceDisplayName: String? = null, + profileTag: String? = null, + lang: String? = null, + data: PusherDataEntity? = null, + enabled: Boolean = true, + deviceId: String? = null, + ) = PusherEntity( + pushKey, + kind, + appId, + appDisplayName, + deviceDisplayName, + profileTag, + lang, + data, + enabled, + deviceId, + ) +} From 40c2e95a543e5f1cc8062f89c270398edf99a310 Mon Sep 17 00:00:00 2001 From: ericdecanini <eddecanini@gmail.com> Date: Sun, 25 Sep 2022 11:58:02 -0400 Subject: [PATCH 050/187] Adds PushersManagerTest --- .../main/java/im/vector/app/AppBuildConfig.kt | 26 ++++++ .../vector/app/core/pushers/PushersManager.kt | 7 +- .../app/core/resources/AppNameProvider.kt | 9 +- .../app/core/resources/LocaleProvider.kt | 9 +- .../view/MessageBubbleContentLayout.kt | 4 +- .../detail/timeline/view/MessageBubbleView.kt | 4 +- .../app/core/pushers/PushersManagerTest.kt | 85 +++++++++++++++++++ .../app/test/fakes/FakeAppNameProvider.kt | 28 ++++++ .../app/test/fakes/FakeLocaleProvider.kt | 29 +++++++ .../app/test/fakes/FakePushersService.kt | 32 +++++++ .../im/vector/app/test/fakes/FakeSession.kt | 2 + 11 files changed, 224 insertions(+), 11 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/AppBuildConfig.kt create mode 100644 vector/src/test/java/im/vector/app/core/pushers/PushersManagerTest.kt create mode 100644 vector/src/test/java/im/vector/app/test/fakes/FakeAppNameProvider.kt create mode 100644 vector/src/test/java/im/vector/app/test/fakes/FakeLocaleProvider.kt create mode 100644 vector/src/test/java/im/vector/app/test/fakes/FakePushersService.kt diff --git a/vector/src/main/java/im/vector/app/AppBuildConfig.kt b/vector/src/main/java/im/vector/app/AppBuildConfig.kt new file mode 100644 index 0000000000..54eb4f87ea --- /dev/null +++ b/vector/src/main/java/im/vector/app/AppBuildConfig.kt @@ -0,0 +1,26 @@ +/* + * 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 + +import android.os.Build + +object AppBuildConfig { + + fun getModel(): String { + return Build.MODEL + } +} diff --git a/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt b/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt index ac932bee72..67e7868df7 100644 --- a/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt +++ b/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt @@ -16,6 +16,7 @@ package im.vector.app.core.pushers +import im.vector.app.AppBuildConfig import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.resources.AppNameProvider @@ -26,7 +27,7 @@ import java.util.UUID import javax.inject.Inject import kotlin.math.abs -private const val DEFAULT_PUSHER_FILE_TAG = "mobile" +internal const val DEFAULT_PUSHER_FILE_TAG = "mobile" class PushersManager @Inject constructor( private val unifiedPushHelper: UnifiedPushHelper, @@ -64,11 +65,11 @@ class PushersManager @Inject constructor( gateway: String ) = HttpPusher( pushKey, - stringProvider.getString(R.string.pusher_app_id), + appId = stringProvider.getString(R.string.pusher_app_id), profileTag = DEFAULT_PUSHER_FILE_TAG + "_" + abs(activeSessionHolder.getActiveSession().myUserId.hashCode()), lang = localeProvider.current().language, appDisplayName = appNameProvider.getAppName(), - deviceDisplayName = android.os.Build.MODEL, + deviceDisplayName = AppBuildConfig.getModel(), url = gateway, enabled = true, deviceId = activeSessionHolder.getActiveSession().sessionParams.deviceId ?: "MOBILE", diff --git a/vector/src/main/java/im/vector/app/core/resources/AppNameProvider.kt b/vector/src/main/java/im/vector/app/core/resources/AppNameProvider.kt index 3b6a8b595c..a25862f3a8 100644 --- a/vector/src/main/java/im/vector/app/core/resources/AppNameProvider.kt +++ b/vector/src/main/java/im/vector/app/core/resources/AppNameProvider.kt @@ -21,9 +21,14 @@ import im.vector.app.core.utils.getApplicationLabel import timber.log.Timber import javax.inject.Inject -class AppNameProvider @Inject constructor(private val context: Context) { +interface AppNameProvider { - fun getAppName(): String { + fun getAppName(): String +} + +class DefaultAppNameProvider @Inject constructor(private val context: Context) : AppNameProvider { + + override fun getAppName(): String { return try { val appPackageName = context.applicationContext.packageName var appName = context.getApplicationLabel(appPackageName) diff --git a/vector/src/main/java/im/vector/app/core/resources/LocaleProvider.kt b/vector/src/main/java/im/vector/app/core/resources/LocaleProvider.kt index d91a09e6df..3bdbc64eb4 100644 --- a/vector/src/main/java/im/vector/app/core/resources/LocaleProvider.kt +++ b/vector/src/main/java/im/vector/app/core/resources/LocaleProvider.kt @@ -23,9 +23,14 @@ import androidx.core.os.ConfigurationCompat import java.util.Locale import javax.inject.Inject -class LocaleProvider @Inject constructor(private val resources: Resources) { +interface LocaleProvider { - fun current(): Locale { + fun current(): Locale +} + +class DefaultLocaleProvider @Inject constructor(private val resources: Resources) : LocaleProvider { + + override fun current(): Locale { return ConfigurationCompat.getLocales(resources.configuration).get(0) ?: Locale.getDefault() } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleContentLayout.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleContentLayout.kt index f11b1c6951..6d8fd87a52 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleContentLayout.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleContentLayout.kt @@ -27,7 +27,7 @@ import androidx.core.view.marginEnd import androidx.core.view.marginStart import androidx.core.view.marginTop import im.vector.app.R -import im.vector.app.core.resources.LocaleProvider +import im.vector.app.core.resources.DefaultLocaleProvider import im.vector.app.core.resources.getLayoutDirectionFromCurrentLocale class MessageBubbleContentLayout @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : @@ -53,7 +53,7 @@ class MessageBubbleContentLayout @JvmOverloads constructor(context: Context, att textViewStub.setOnInflateListener(null) messageTextView = inflated.findViewById(R.id.messageTextView) } - localeLayoutDirection = LocaleProvider(resources).getLayoutDirectionFromCurrentLocale() + localeLayoutDirection = DefaultLocaleProvider(resources).getLayoutDirectionFromCurrentLocale() } override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt index c9665a9125..6ac787b719 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt @@ -33,7 +33,7 @@ import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams import com.google.android.material.shape.MaterialShapeDrawable import im.vector.app.R -import im.vector.app.core.resources.LocaleProvider +import im.vector.app.core.resources.DefaultLocaleProvider import im.vector.app.core.resources.getLayoutDirectionFromCurrentLocale import im.vector.app.core.utils.DimensionConverter import im.vector.app.databinding.ViewMessageBubbleBinding @@ -67,7 +67,7 @@ class MessageBubbleView @JvmOverloads constructor( override fun onFinishInflate() { super.onFinishInflate() views = ViewMessageBubbleBinding.bind(this) - val currentLayoutDirection = LocaleProvider(resources).getLayoutDirectionFromCurrentLocale() + val currentLayoutDirection = DefaultLocaleProvider(resources).getLayoutDirectionFromCurrentLocale() val layoutDirectionToSet = if (isIncoming) { currentLayoutDirection } else { diff --git a/vector/src/test/java/im/vector/app/core/pushers/PushersManagerTest.kt b/vector/src/test/java/im/vector/app/core/pushers/PushersManagerTest.kt new file mode 100644 index 0000000000..e42f995ef6 --- /dev/null +++ b/vector/src/test/java/im/vector/app/core/pushers/PushersManagerTest.kt @@ -0,0 +1,85 @@ +/* + * 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.pushers + +import im.vector.app.AppBuildConfig +import im.vector.app.R +import im.vector.app.test.fakes.FakeActiveSessionHolder +import im.vector.app.test.fakes.FakeAppNameProvider +import im.vector.app.test.fakes.FakeLocaleProvider +import im.vector.app.test.fakes.FakePushersService +import im.vector.app.test.fakes.FakeSession +import im.vector.app.test.fakes.FakeStringProvider +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkObject +import org.amshove.kluent.shouldBeEqualTo +import org.junit.Before +import org.junit.Test +import java.util.Locale +import kotlin.math.abs + +class PushersManagerTest { + + private val pushersService = FakePushersService() + private val session = FakeSession(fakePushersService = pushersService) + private val activeSessionHolder = FakeActiveSessionHolder(session) + private val stringProvider = FakeStringProvider() + private val localeProvider = FakeLocaleProvider() + private val appNameProvider = FakeAppNameProvider() + + private val pushersManager = PushersManager( + mockk(), + activeSessionHolder.instance, + localeProvider, + stringProvider.instance, + appNameProvider, + ) + + @Before + fun setUp() { + mockkObject(AppBuildConfig) + } + + @Test + fun `when enqueueRegisterPusher, then HttpPusher created and enqueued`() { + val pushKey = "abc" + val gateway = "123" + val pusherAppId = "app-id" + val appName = "element" + val deviceDisplayName = "iPhone Lollipop" + stringProvider.given(R.string.pusher_app_id, pusherAppId) + localeProvider.givenCurrent(Locale.UK) + appNameProvider.givenAppName(appName) + every { AppBuildConfig.getModel() } returns deviceDisplayName + + pushersManager.enqueueRegisterPusher(pushKey, gateway) + + val httpPusher = pushersService.verifyEnqueueAddHttpPusher() + httpPusher.pushkey shouldBeEqualTo pushKey + httpPusher.appId shouldBeEqualTo pusherAppId + httpPusher.profileTag shouldBeEqualTo DEFAULT_PUSHER_FILE_TAG + "_" + abs(session.myUserId.hashCode()) + httpPusher.lang shouldBeEqualTo Locale.UK.language + httpPusher.appDisplayName shouldBeEqualTo appName + httpPusher.deviceDisplayName shouldBeEqualTo deviceDisplayName + httpPusher.enabled shouldBeEqualTo true + httpPusher.deviceId shouldBeEqualTo session.sessionParams.deviceId + httpPusher.append shouldBeEqualTo false + httpPusher.withEventIdOnly shouldBeEqualTo true + httpPusher.url shouldBeEqualTo gateway + } +} diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeAppNameProvider.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeAppNameProvider.kt new file mode 100644 index 0000000000..2e60a78853 --- /dev/null +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeAppNameProvider.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.test.fakes + +import im.vector.app.core.resources.AppNameProvider +import io.mockk.every +import io.mockk.mockk + +class FakeAppNameProvider : AppNameProvider by mockk() { + + fun givenAppName(appName: String) { + every { getAppName() } returns appName + } +} diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeLocaleProvider.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeLocaleProvider.kt new file mode 100644 index 0000000000..4d4e2b5bcb --- /dev/null +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeLocaleProvider.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.test.fakes + +import im.vector.app.core.resources.LocaleProvider +import io.mockk.every +import io.mockk.mockk +import java.util.Locale + +class FakeLocaleProvider : LocaleProvider by mockk() { + + fun givenCurrent(locale: Locale) { + every { current() } returns locale + } +} diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakePushersService.kt b/vector/src/test/java/im/vector/app/test/fakes/FakePushersService.kt new file mode 100644 index 0000000000..9e11b86871 --- /dev/null +++ b/vector/src/test/java/im/vector/app/test/fakes/FakePushersService.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.test.fakes + +import io.mockk.mockk +import io.mockk.slot +import io.mockk.verify +import org.matrix.android.sdk.api.session.pushers.HttpPusher +import org.matrix.android.sdk.api.session.pushers.PushersService + +class FakePushersService : PushersService by mockk(relaxed = true) { + + fun verifyEnqueueAddHttpPusher(): HttpPusher { + val httpPusherSlot = slot<HttpPusher>() + verify { enqueueAddHttpPusher(capture(httpPusherSlot)) } + return httpPusherSlot.captured + } +} diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt index 35d23e35e8..3a253c96ea 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt @@ -41,6 +41,7 @@ class FakeSession( val fakeHomeServerCapabilitiesService: FakeHomeServerCapabilitiesService = FakeHomeServerCapabilitiesService(), val fakeSharedSecretStorageService: FakeSharedSecretStorageService = FakeSharedSecretStorageService(), val fakeRoomService: FakeRoomService = FakeRoomService(), + val fakePushersService: FakePushersService = FakePushersService(), private val fakeEventService: FakeEventService = FakeEventService(), ) : Session by mockk(relaxed = true) { @@ -58,6 +59,7 @@ class FakeSession( override fun sharedSecretStorageService() = fakeSharedSecretStorageService override fun roomService() = fakeRoomService override fun eventService() = fakeEventService + override fun pushersService() = fakePushersService fun givenVectorStore(vectorSessionStore: VectorSessionStore) { coEvery { From 4cebfa13e8510499d425dd175260f722ddd5caf9 Mon Sep 17 00:00:00 2001 From: ericdecanini <eddecanini@gmail.com> Date: Mon, 26 Sep 2022 11:39:40 -0400 Subject: [PATCH 051/187] Adds missing bindings --- .../src/gplay/java/im/vector/app/di/FlavorModule.kt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/vector-app/src/gplay/java/im/vector/app/di/FlavorModule.kt b/vector-app/src/gplay/java/im/vector/app/di/FlavorModule.kt index 2fe72313ea..d70a55afb8 100644 --- a/vector-app/src/gplay/java/im/vector/app/di/FlavorModule.kt +++ b/vector-app/src/gplay/java/im/vector/app/di/FlavorModule.kt @@ -23,6 +23,10 @@ import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import im.vector.app.GoogleFlavorLegals import im.vector.app.core.pushers.FcmHelper +import im.vector.app.core.resources.AppNameProvider +import im.vector.app.core.resources.DefaultAppNameProvider +import im.vector.app.core.resources.DefaultLocaleProvider +import im.vector.app.core.resources.LocaleProvider import im.vector.app.core.services.GuardServiceStarter import im.vector.app.features.home.NightlyProxy import im.vector.app.features.settings.legals.FlavorLegals @@ -46,6 +50,12 @@ abstract class FlavorModule { @Binds abstract fun bindsFcmHelper(fcmHelper: GoogleFcmHelper): FcmHelper + @Binds + abstract fun bindsLocaleProvider(localeProvider: DefaultLocaleProvider): LocaleProvider + + @Binds + abstract fun bindsAppNameProvider(appNameProvider: DefaultAppNameProvider): AppNameProvider + @Binds abstract fun bindsFlavorLegals(legals: GoogleFlavorLegals): FlavorLegals } From dc1abb79781af776315cd7b673225ffd4f648a96 Mon Sep 17 00:00:00 2001 From: yostyle <y.pintas@gmail.com> Date: Fri, 23 Sep 2022 15:46:09 +0200 Subject: [PATCH 052/187] Add Activities of android permission controller in the whitelist --- .../features/lifecycle/VectorActivityLifecycleCallbacks.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/lifecycle/VectorActivityLifecycleCallbacks.kt b/vector/src/main/java/im/vector/app/features/lifecycle/VectorActivityLifecycleCallbacks.kt index c884843f5c..6fc1bcf6c7 100644 --- a/vector/src/main/java/im/vector/app/features/lifecycle/VectorActivityLifecycleCallbacks.kt +++ b/vector/src/main/java/im/vector/app/features/lifecycle/VectorActivityLifecycleCallbacks.kt @@ -33,6 +33,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import org.matrix.android.sdk.api.extensions.tryOrNull import timber.log.Timber class VectorActivityLifecycleCallbacks constructor(private val popupAlertManager: PopupAlertManager) : Application.ActivityLifecycleCallbacks { @@ -94,9 +95,13 @@ class VectorActivityLifecycleCallbacks constructor(private val popupAlertManager val context = activity.applicationContext val packageManager: PackageManager = context.packageManager - // Get all activities from app manifest + // Get all activities from element android and android permission controller app if (activitiesInfo.isEmpty()) { activitiesInfo = packageManager.getPackageInfo(context.packageName, PackageManager.GET_ACTIVITIES).activities + + activitiesInfo += tryOrNull { + packageManager.getPackageInfo("com.google.android.permissioncontroller", PackageManager.GET_ACTIVITIES).activities + } ?: emptyArray() } // Get all running activities on app task From bd64749b48098864d91e76aadc74ea71bff52a23 Mon Sep 17 00:00:00 2001 From: yostyle <y.pintas@gmail.com> Date: Fri, 23 Sep 2022 18:03:54 +0200 Subject: [PATCH 053/187] Add changelog --- changelog.d/7224.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7224.bugfix diff --git a/changelog.d/7224.bugfix b/changelog.d/7224.bugfix new file mode 100644 index 0000000000..e48925e9e6 --- /dev/null +++ b/changelog.d/7224.bugfix @@ -0,0 +1 @@ +Fix app restarts in loop on Android 13 on the first run of the app. From 8dc71e07edef7666f27489e419c093197082d989 Mon Sep 17 00:00:00 2001 From: yostyle <y.pintas@gmail.com> Date: Tue, 27 Sep 2022 00:24:10 +0200 Subject: [PATCH 054/187] Add activities from Android API 33. --- .../VectorActivityLifecycleCallbacks.kt | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/lifecycle/VectorActivityLifecycleCallbacks.kt b/vector/src/main/java/im/vector/app/features/lifecycle/VectorActivityLifecycleCallbacks.kt index 6fc1bcf6c7..5bdd92dcf4 100644 --- a/vector/src/main/java/im/vector/app/features/lifecycle/VectorActivityLifecycleCallbacks.kt +++ b/vector/src/main/java/im/vector/app/features/lifecycle/VectorActivityLifecycleCallbacks.kt @@ -59,6 +59,26 @@ class VectorActivityLifecycleCallbacks constructor(private val popupAlertManager override fun onActivityStopped(activity: Activity) {} override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { + if (activitiesInfo.isEmpty()) { + val context = activity.applicationContext + val packageManager: PackageManager = context.packageManager + + // Get all activities from element android + activitiesInfo = packageManager.getPackageInfo(context.packageName, PackageManager.GET_ACTIVITIES).activities + + // Get all activities from PermissionController module + // See https://source.android.com/docs/core/architecture/modular-system/permissioncontroller#package-format + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.S_V2) { + activitiesInfo += tryOrNull { + packageManager.getPackageInfo("com.google.android.permissioncontroller", PackageManager.GET_ACTIVITIES).activities + } ?: tryOrNull { + packageManager.getModuleInfo("com.google.android.permission", 1).packageName?.let { + packageManager.getPackageInfo(it, PackageManager.GET_ACTIVITIES or PackageManager.MATCH_APEX).activities + } + }.orEmpty() + } + } + // restart the app if the task contains an unknown activity coroutineScope.launch { val isTaskCorrupted = try { @@ -93,16 +113,6 @@ class VectorActivityLifecycleCallbacks constructor(private val popupAlertManager */ private suspend fun isTaskCorrupted(activity: Activity): Boolean = withContext(Dispatchers.Default) { val context = activity.applicationContext - val packageManager: PackageManager = context.packageManager - - // Get all activities from element android and android permission controller app - if (activitiesInfo.isEmpty()) { - activitiesInfo = packageManager.getPackageInfo(context.packageName, PackageManager.GET_ACTIVITIES).activities - - activitiesInfo += tryOrNull { - packageManager.getPackageInfo("com.google.android.permissioncontroller", PackageManager.GET_ACTIVITIES).activities - } ?: emptyArray() - } // Get all running activities on app task // and compare to activities declared in manifest @@ -127,7 +137,7 @@ class VectorActivityLifecycleCallbacks constructor(private val popupAlertManager runningTaskInfo.topActivity?.let { // Check whether the activity task affinity matches with app task affinity. // The activity is considered safe when its task affinity doesn't correspond to app task affinity. - if (packageManager.getActivityInfo(it, 0).taskAffinity == context.applicationInfo.taskAffinity) { + if (context.packageManager.getActivityInfo(it, 0).taskAffinity == context.applicationInfo.taskAffinity) { isPotentialMaliciousActivity(it) } else false } ?: false From 0d6cbbdb5dc4218a91e0bbdc0bfc304f784946e0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Sep 2022 16:41:13 +0000 Subject: [PATCH 055/187] Bump dependency-check-gradle from 7.2.0 to 7.2.1 Bumps dependency-check-gradle from 7.2.0 to 7.2.1. --- updated-dependencies: - dependency-name: org.owasp:dependency-check-gradle dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index a748594a8c..93e08192c6 100644 --- a/build.gradle +++ b/build.gradle @@ -29,7 +29,7 @@ buildscript { classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.4.0.2513' classpath 'com.google.android.gms:oss-licenses-plugin:0.10.5' classpath "com.likethesalad.android:stem-plugin:2.2.2" - classpath 'org.owasp:dependency-check-gradle:7.2.0' + classpath 'org.owasp:dependency-check-gradle:7.2.1' classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.7.10" classpath "org.jetbrains.kotlinx:kotlinx-knit:0.4.0" classpath 'com.jakewharton:butterknife-gradle-plugin:10.2.3' From 6eb5d9818094a81aae7d2ec7ae5721a58fe17b4b Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Mon, 12 Sep 2022 14:14:45 +0200 Subject: [PATCH 056/187] Add changelog entry --- changelog.d/7100.wip | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7100.wip diff --git a/changelog.d/7100.wip b/changelog.d/7100.wip new file mode 100644 index 0000000000..47e7a6f810 --- /dev/null +++ b/changelog.d/7100.wip @@ -0,0 +1 @@ +[Device Management] Learn more bottom sheets From 70a5093e3d82eae12af70bdeb7378c31efcb209a Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Mon, 12 Sep 2022 14:37:38 +0200 Subject: [PATCH 057/187] Adding string resources --- library/ui-strings/src/main/res/values/strings.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 4ff7aae750..2170ec6c5b 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3302,6 +3302,10 @@ <string name="device_manager_session_rename_edit_hint">Session name</string> <string name="device_manager_session_rename_description">Custom session names can help you recognize your devices more easily.</string> <string name="device_manager_session_rename_warning">Please be aware that session names are also visible to people you communicate with.</string> + <string name="device_manager_learn_more_sessions_inactive">Inactive sessions are sessions you have not used in some time. Removing inactive sessions can improve performance, and make it easier for you to identify if a new session is suspicious. In addition, inactive sessions continue to receive encryption keys and so may pose a security or privacy risk if an unauthorised person is able to access the session.</string> + <string name="device_manager_learn_more_sessions_unverified">Unverified sessions are sessions that have logged in with your credentials but not been verified via cross-verification from a verified session, or using your passphrase. You should make especially certain that you recognise these sessions as they could represent an unauthorised use of your account. If you do recognise them, you should verify them as this allows your contacts to see that the new login is really you.</string> + <string name="device_manager_learn_more_session_verified">This session has logged in with your credentials and then been verified, either using your secure passphrase or by cross-verifying from another verified session. This means it holds encryption keys for your previous messages, and confirms to other users you are communicating with that the session is really you.</string> + <string name="device_manager_learn_more_session_rename" tools:ignore="UnusedResources">Other users in direct messages and rooms that you join are able to view a full list of your sessions, including which are verified and unverified. This provides them with confidence that they are really speaking to you, but it also means they can see the session name you enter here.</string> <!-- Note to translators: %s will be replaces with selected space name --> <string name="home_empty_space_no_rooms_title">%s\nis looking a little empty.</string> From 1d11eae0cc69a98594fdbc9d96ae1177f547fa14 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Mon, 12 Sep 2022 14:54:26 +0200 Subject: [PATCH 058/187] Removing learn more link from security header --- .../values/stylable_sessions_list_header_view.xml | 1 + .../devices/v2/list/SessionsListHeaderView.kt | 13 +++++++++++-- .../main/res/layout/fragment_settings_devices.xml | 3 +++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/library/ui-styles/src/main/res/values/stylable_sessions_list_header_view.xml b/library/ui-styles/src/main/res/values/stylable_sessions_list_header_view.xml index d3b931e44a..098ec263fc 100644 --- a/library/ui-styles/src/main/res/values/stylable_sessions_list_header_view.xml +++ b/library/ui-styles/src/main/res/values/stylable_sessions_list_header_view.xml @@ -4,6 +4,7 @@ <declare-styleable name="SessionsListHeaderView"> <attr name="sessionsListHeaderTitle" format="string" /> <attr name="sessionsListHeaderDescription" format="string" /> + <attr name="sessionsListHeaderHasLearnMoreLink" format="boolean" /> </declare-styleable> </resources> diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionsListHeaderView.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionsListHeaderView.kt index ef8682df01..0264db08e1 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionsListHeaderView.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionsListHeaderView.kt @@ -65,14 +65,23 @@ class SessionsListHeaderView @JvmOverloads constructor( return } + val hasLearnMoreLink = typedArray.getBoolean(R.styleable.SessionsListHeaderView_sessionsListHeaderHasLearnMoreLink, true) + if(hasLearnMoreLink) { + setDescriptionWithLearnMore(description) + } else { + binding.sessionsListHeaderDescription.text = description + } + + binding.sessionsListHeaderDescription.isVisible = true + } + + private fun setDescriptionWithLearnMore(description: String) { val learnMore = context.getString(R.string.action_learn_more) val fullDescription = buildString { append(description) append(" ") append(learnMore) } - - binding.sessionsListHeaderDescription.isVisible = true binding.sessionsListHeaderDescription.setTextWithColoredPart( fullText = fullDescription, coloredPart = learnMore, diff --git a/vector/src/main/res/layout/fragment_settings_devices.xml b/vector/src/main/res/layout/fragment_settings_devices.xml index 8e2daa2239..4f4e14eaad 100644 --- a/vector/src/main/res/layout/fragment_settings_devices.xml +++ b/vector/src/main/res/layout/fragment_settings_devices.xml @@ -14,6 +14,7 @@ android:layout_height="wrap_content" app:sessionsListHeaderDescription="@string/device_manager_header_section_security_recommendations_description" app:sessionsListHeaderTitle="@string/device_manager_header_section_security_recommendations_title" + app:sessionsListHeaderHasLearnMoreLink="false" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> @@ -62,6 +63,7 @@ android:layout_height="wrap_content" app:sessionsListHeaderDescription="" app:sessionsListHeaderTitle="@string/device_manager_current_session_title" + app:sessionsListHeaderHasLearnMoreLink="false" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/deviceListSecurityRecommendationsDivider" /> @@ -92,6 +94,7 @@ android:layout_height="wrap_content" app:sessionsListHeaderDescription="@string/device_manager_sessions_other_description" app:sessionsListHeaderTitle="@string/device_manager_sessions_other_title" + app:sessionsListHeaderHasLearnMoreLink="true" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/deviceListDividerCurrentSession" /> From 648311e2b10b7fb3528053394e915d95a3e11c46 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Mon, 12 Sep 2022 15:54:15 +0200 Subject: [PATCH 059/187] Show basic bottom sheet when pressing learn more link from other sessions section --- .../src/main/res/values-ca/strings.xml | 4 +- .../src/main/res/values-cs/strings.xml | 2 +- .../src/main/res/values-de/strings.xml | 2 +- .../src/main/res/values-et/strings.xml | 2 +- .../src/main/res/values-fa/strings.xml | 4 +- .../src/main/res/values-fr/strings.xml | 2 +- .../src/main/res/values-hu/strings.xml | 2 +- .../src/main/res/values-in/strings.xml | 2 +- .../src/main/res/values-it/strings.xml | 2 +- .../src/main/res/values-nl/strings.xml | 2 +- .../src/main/res/values-pl/strings.xml | 2 +- .../src/main/res/values-pt-rBR/strings.xml | 2 +- .../src/main/res/values-ru/strings.xml | 2 +- .../src/main/res/values-sk/strings.xml | 2 +- .../src/main/res/values-uk/strings.xml | 2 +- .../src/main/res/values-zh-rCN/strings.xml | 2 +- .../src/main/res/values-zh-rTW/strings.xml | 2 +- .../src/main/res/values/strings.xml | 6 +- .../app/core/di/MavericksViewModelModule.kt | 6 ++ .../v2/VectorSettingsDevicesFragment.kt | 18 +++++- .../v2/more/SessionLearnMoreBottomSheet.kt | 62 +++++++++++++++++++ .../v2/more/SessionLearnMoreViewModel.kt | 43 +++++++++++++ .../v2/more/SessionLearnMoreViewState.kt | 29 +++++++++ .../bottom_sheet_session_learn_more.xml | 45 ++++++++++++++ 24 files changed, 222 insertions(+), 25 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/more/SessionLearnMoreBottomSheet.kt create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/more/SessionLearnMoreViewModel.kt create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/more/SessionLearnMoreViewState.kt create mode 100644 vector/src/main/res/layout/bottom_sheet_session_learn_more.xml diff --git a/library/ui-strings/src/main/res/values-ca/strings.xml b/library/ui-strings/src/main/res/values-ca/strings.xml index 863fa13fbb..daeebf6b30 100644 --- a/library/ui-strings/src/main/res/values-ca/strings.xml +++ b/library/ui-strings/src/main/res/values-ca/strings.xml @@ -2604,7 +2604,7 @@ <string name="explore_rooms">Explora sales</string> <string name="device_manager_sessions_other_description">Per estar més segur, verifica les teves sessions i tanca qualsevol sessió que no reconeguis o ja no utilitzis.</string> <string name="device_manager_sessions_other_title">Altres sessions</string> - <string name="settings_sessions_list">Sessions</string> + <string name="device_manager_sessions_list_title">Sessions</string> <string name="a11y_open_spaces">Obre la llista d\'espais</string> <string name="a11y_create_message">Crea un nou xat o sala</string> <string name="room_list_filter_people">Gent</string> @@ -2674,4 +2674,4 @@ <string name="device_manager_verification_status_detail_other_session_verified">Aquesta sessió està llesta per a missatges segurs.</string> <string name="device_manager_verification_status_detail_current_session_verified">La teva sessió actual està llesta per a missatges segurs.</string> <string name="device_manager_verification_status_detail_current_session_unverified">Verifica la teva sessió actual obtenir missatges segurs millorats.</string> -</resources> \ No newline at end of file +</resources> 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 79f8311159..7e6a012c65 100644 --- a/library/ui-strings/src/main/res/values-cs/strings.xml +++ b/library/ui-strings/src/main/res/values-cs/strings.xml @@ -2653,7 +2653,7 @@ <string name="device_manager_settings_active_sessions_show_all">Zobrazit všechny relace (V2, WIP)</string> <string name="device_manager_sessions_other_description">V zájmu co nejlepšího zabezpečení ověřujte své relace a odhlašujte se ze všech relací, které již nepoznáváte nebo nepoužíváte.</string> <string name="device_manager_sessions_other_title">Ostatní relace</string> - <string name="settings_sessions_list">Relace</string> + <string name="device_manager_sessions_list_title">Relace</string> <string name="a11y_open_spaces">Seznam otevřených prostorů</string> <string name="a11y_create_message">Vytvořit novou konverzaci nebo místnost</string> <string name="room_list_filter_people">Lidé</string> 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 e01fc898a3..0b6d8b9666 100644 --- a/library/ui-strings/src/main/res/values-de/strings.xml +++ b/library/ui-strings/src/main/res/values-de/strings.xml @@ -2589,7 +2589,7 @@ <string name="device_manager_settings_active_sessions_show_all">Alle Sitzungen anzeigen (V2, in Arbeit)</string> <string name="device_manager_sessions_other_description">Für bestmögliche Sicherheit verifiziere deine Sitzungen und melde dich von allen ab, die du nicht erkennst oder nutzt.</string> <string name="device_manager_sessions_other_title">Andere Sitzungen</string> - <string name="settings_sessions_list">Sitzungen</string> + <string name="device_manager_sessions_list_title">Sitzungen</string> <string name="a11y_open_spaces">Space-Liste öffnen</string> <string name="a11y_create_message">Beginne ein Gespräch oder erstelle einen Raum</string> <string name="room_list_filter_favourites">Favoriten</string> 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 9bd1dd23b7..51882a505a 100644 --- a/library/ui-strings/src/main/res/values-et/strings.xml +++ b/library/ui-strings/src/main/res/values-et/strings.xml @@ -2594,7 +2594,7 @@ <string name="device_manager_settings_active_sessions_show_all">Näita kõiki sessioone (V2, WIP)</string> <string name="device_manager_sessions_other_description">Parima turvalisuse nimel verifitseeri kõik oma sessioonid ning logi välja neist, mida sa enam ei kasuta.</string> <string name="device_manager_sessions_other_title">Muud sessioonid</string> - <string name="settings_sessions_list">Sessionid</string> + <string name="device_manager_sessions_list_title">Sessionid</string> <string name="a11y_open_spaces">Ava kogukondade loend</string> <string name="a11y_create_message">Alusta uut vestlust või loo uus jututuba</string> <string name="room_list_filter_people">Inimesed</string> 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 400a8121f9..1b8d3a4fb3 100644 --- a/library/ui-strings/src/main/res/values-fa/strings.xml +++ b/library/ui-strings/src/main/res/values-fa/strings.xml @@ -2603,7 +2603,7 @@ <string name="device_manager_settings_active_sessions_show_all">نمایش تمامی نشستها (ن۲، دحت)</string> <string name="device_manager_sessions_other_description">برای امنیت بیشتر، نشستهایتان را تأیید و از هر نشستی که تشخیصش نمیدهید یا دیگر استفاده نمیکنید خارج شوید.</string> <string name="device_manager_sessions_other_title">دیگر نشستها</string> - <string name="settings_sessions_list">نشستها</string> + <string name="device_manager_sessions_list_title">نشستها</string> <string name="a11y_open_spaces">گشودن سیاههٔ فضاها</string> <string name="a11y_create_message">ایجاد اتاق یا گفتوگویی جدید</string> <string name="room_list_filter_people">افراد</string> @@ -2700,4 +2700,4 @@ <string name="labs_enable_deferred_dm_summary">ایجاد پیام خصوصی فقط در نخستین پیام</string> <string name="labs_enable_new_app_layout_summary">المنتی ساده شده با زبانههای انتخابی</string> <string name="labs_enable_new_app_layout_title">به کار انداختن چینش جدید</string> -</resources> \ No newline at end of file +</resources> 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 5a19ccf2da..bbd82dd596 100644 --- a/library/ui-strings/src/main/res/values-fr/strings.xml +++ b/library/ui-strings/src/main/res/values-fr/strings.xml @@ -2603,7 +2603,7 @@ <string name="device_manager_settings_active_sessions_show_all">Afficher toutes les sessions (V2, en cours)</string> <string name="device_manager_sessions_other_description">Pour une meilleure sécurité, vérifiez vos sessions et déconnectez toutes les sessions que vous ne connaissez pas ou que vous n’utilisez plus.</string> <string name="device_manager_sessions_other_title">Autres sessions</string> - <string name="settings_sessions_list">Sessions</string> + <string name="device_manager_sessions_list_title">Sessions</string> <string name="a11y_open_spaces">Ouvrir la liste des espaces</string> <string name="a11y_create_message">Créer une nouvelle conversation ou salon</string> <string name="room_list_filter_people">Personnes</string> 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 3068556fe4..397c2d4662 100644 --- a/library/ui-strings/src/main/res/values-hu/strings.xml +++ b/library/ui-strings/src/main/res/values-hu/strings.xml @@ -2614,7 +2614,7 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze <string name="device_manager_settings_active_sessions_show_all">Minden munkamenet megjelenítése (V2, WIP)</string> <string name="device_manager_sessions_other_description">A legjobb biztonság érdekében ellenőrizd a munkameneteket, és jelentkezz ki minden olyan munkamenetből, melyet már nem ismersz fel vagy nem használsz.</string> <string name="device_manager_sessions_other_title">Más munkamenetek</string> - <string name="settings_sessions_list">Munkamenetek</string> + <string name="device_manager_sessions_list_title">Munkamenetek</string> <string name="a11y_open_spaces">Nyitott területek listája</string> <string name="a11y_create_message">Új beszélgetés vagy szoba létrehozása</string> <string name="room_list_filter_people">Emberek</string> 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 3b30950bd1..18dbbff4ad 100644 --- a/library/ui-strings/src/main/res/values-in/strings.xml +++ b/library/ui-strings/src/main/res/values-in/strings.xml @@ -2555,7 +2555,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.</string> <string name="device_manager_settings_active_sessions_show_all">Tampilkan Semua Sesi (V2, Dalam Pengembangan)</string> <string name="device_manager_sessions_other_description">Untuk keamanan terbaik, verifikasi sesi Anda dan keluarkan sesi apa pun yang Anda tidak kenal atau Anda tidak gunakan lagi.</string> <string name="device_manager_sessions_other_title">Sesi lainnya</string> - <string name="settings_sessions_list">Sesi</string> + <string name="device_manager_sessions_list_title">Sesi</string> <string name="a11y_open_spaces">Buka daftar space</string> <string name="a11y_create_message">Buat percakapan atau ruangan baru</string> <string name="room_list_filter_people">Orang</string> 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 b7b0fe91af..8ad2e62821 100644 --- a/library/ui-strings/src/main/res/values-it/strings.xml +++ b/library/ui-strings/src/main/res/values-it/strings.xml @@ -2594,7 +2594,7 @@ <string name="device_manager_settings_active_sessions_show_all">Mostra tutte le sessioni (V2, WIP)</string> <string name="device_manager_sessions_other_description">Per una maggiore sicurezza, verifica le tue sessioni e disconnetti quelle che non riconosci o che non usi più.</string> <string name="device_manager_sessions_other_title">Altre sessioni</string> - <string name="settings_sessions_list">Sessioni</string> + <string name="device_manager_sessions_list_title">Sessioni</string> <string name="a11y_open_spaces">Apri elenco spazi</string> <string name="a11y_create_message">Crea una nuova conversazione o stanza</string> <string name="room_list_filter_people">Persone</string> diff --git a/library/ui-strings/src/main/res/values-nl/strings.xml b/library/ui-strings/src/main/res/values-nl/strings.xml index ce122b0646..c5de4ae5f7 100644 --- a/library/ui-strings/src/main/res/values-nl/strings.xml +++ b/library/ui-strings/src/main/res/values-nl/strings.xml @@ -2602,7 +2602,7 @@ <string name="a11y_open_settings">Open instellingen</string> <string name="device_manager_sessions_other_description">Voor de beste beveiliging verifieert u uw sessies en meldt u zich af bij elke sessie die u niet meer herkent of gebruikt.</string> <string name="device_manager_sessions_other_title">Andere sessies</string> - <string name="settings_sessions_list">Sessies</string> + <string name="device_manager_sessions_list_title">Sessies</string> <string name="a11y_open_spaces">Lijst met publieke spaces</string> <string name="a11y_create_message">Nieuw gesprek of nieuwe kamer aanmaken</string> <string name="room_list_filter_people">Personen</string> 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 b7b73eb9e6..6deb3dd2e7 100644 --- a/library/ui-strings/src/main/res/values-pl/strings.xml +++ b/library/ui-strings/src/main/res/values-pl/strings.xml @@ -2699,7 +2699,7 @@ <string name="a11y_open_settings">Otwórz ustawienia</string> <string name="device_manager_sessions_other_description">Aby zapewnić najlepsze bezpieczeństwo, zweryfikuj swoje sesje i wyloguj się z każdej sesji, której już nie rozpoznajesz lub której już nie używasz.</string> <string name="device_manager_sessions_other_title">Inne sesje</string> - <string name="settings_sessions_list">Sesje</string> + <string name="device_manager_sessions_list_title">Sesje</string> <string name="a11y_open_spaces">Lista otwartych przestrzeni</string> <string name="a11y_create_message">Utwórz nową rozmowę lub pokój</string> <string name="room_list_filter_people">Ludzie</string> diff --git a/library/ui-strings/src/main/res/values-pt-rBR/strings.xml b/library/ui-strings/src/main/res/values-pt-rBR/strings.xml index 817c7646df..1c5291d6f3 100644 --- a/library/ui-strings/src/main/res/values-pt-rBR/strings.xml +++ b/library/ui-strings/src/main/res/values-pt-rBR/strings.xml @@ -2603,7 +2603,7 @@ <string name="device_manager_settings_active_sessions_show_all">Mostrar Todas Sessões (V2, WIP)</string> <string name="device_manager_sessions_other_description">Para a melhor segurança, verifique suas sessões e faça signout de qualquer sessão que você não reconhece ou usa mais.</string> <string name="device_manager_sessions_other_title">Outras sessões</string> - <string name="settings_sessions_list">Sessões</string> + <string name="device_manager_sessions_list_title">Sessões</string> <string name="a11y_open_spaces">Abrir lista de espaços</string> <string name="a11y_create_message">Criar uma nova conversa ou sala</string> <string name="room_list_filter_all">Todas</string> 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 7c9d073035..718e4cd125 100644 --- a/library/ui-strings/src/main/res/values-ru/strings.xml +++ b/library/ui-strings/src/main/res/values-ru/strings.xml @@ -2662,7 +2662,7 @@ <string name="all_chats">Все беседы</string> <string name="device_manager_sessions_other_description">Для лучшей безопасности заверьте свои сессии и выйдите из тех, которые более не признаёте или не используете.</string> <string name="device_manager_sessions_other_title">Другие сессии</string> - <string name="settings_sessions_list">Сессии</string> + <string name="device_manager_sessions_list_title">Сессии</string> <string name="a11y_create_message">Создать беседу или комнату</string> <string name="device_manager_settings_active_sessions_show_all">Показать все сессии (V2, в разработке)</string> <string name="room_list_filter_people">Люди</string> 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 9eb22e3ae3..b100db2717 100644 --- a/library/ui-strings/src/main/res/values-sk/strings.xml +++ b/library/ui-strings/src/main/res/values-sk/strings.xml @@ -2653,7 +2653,7 @@ <string name="device_manager_settings_active_sessions_show_all">Zobraziť všetky relácie (V2, WIP)</string> <string name="device_manager_sessions_other_description">V záujme čo najlepšieho zabezpečenia overte svoje relácie a odhláste sa z každej relácie, ktorú už nepoznáte alebo nepoužívate.</string> <string name="device_manager_sessions_other_title">Iné relácie</string> - <string name="settings_sessions_list">Relácie</string> + <string name="device_manager_sessions_list_title">Relácie</string> <string name="a11y_open_spaces">Otvoriť zoznam priestorov</string> <string name="a11y_create_message">Vytvoriť novú konverzáciu alebo miestnosť</string> <string name="room_list_filter_people">Ľudia</string> 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 3e511f8459..0de8e7191e 100644 --- a/library/ui-strings/src/main/res/values-uk/strings.xml +++ b/library/ui-strings/src/main/res/values-uk/strings.xml @@ -2703,7 +2703,7 @@ <string name="device_manager_settings_active_sessions_show_all">Показати всі сеанси (V2, WIP)</string> <string name="device_manager_sessions_other_description">Для найкращої безпеки перевірте свої сеанси та вийдіть з усіх сеансів, які ви більше не розпізнаєте або не використовуєте.</string> <string name="device_manager_sessions_other_title">Інші сеанси</string> - <string name="settings_sessions_list">Сеанси</string> + <string name="device_manager_sessions_list_title">Сеанси</string> <string name="a11y_open_spaces">Відкрити список кімнат</string> <string name="a11y_create_message">Створити нову розмову або кімнату</string> <string name="room_list_filter_people">Люди</string> 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 db1dab92e2..56a399482c 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 @@ -2553,7 +2553,7 @@ <string name="device_manager_settings_active_sessions_show_all">显示全部会话(V2, WIP)</string> <string name="device_manager_sessions_other_description">为获得最佳安全性,请验证你的会话,并从任何你不认识或不再使用的会话登出。</string> <string name="device_manager_sessions_other_title">其他会话</string> - <string name="settings_sessions_list">会话</string> + <string name="device_manager_sessions_list_title">会话</string> <string name="a11y_open_spaces">打开空间列表</string> <string name="a11y_create_message">创建新对话或房间</string> <string name="room_list_filter_people">人</string> 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 78caa2cc2e..cfeb6ca915 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 @@ -2553,7 +2553,7 @@ <string name="device_manager_settings_active_sessions_show_all">顯示所有工作階段 (V2, WIP)</string> <string name="device_manager_sessions_other_description">為了取得最佳安全性,請驗證您的工作階段並登出任何您無法識別或不再使用的工作階段。</string> <string name="device_manager_sessions_other_title">其他工作階段</string> - <string name="settings_sessions_list">工作階段</string> + <string name="device_manager_sessions_list_title">工作階段</string> <string name="a11y_open_spaces">開啟空間清單</string> <string name="a11y_create_message">建立新的對話或聊天室</string> <string name="room_list_filter_people">聯絡人</string> diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 2170ec6c5b..40653db278 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -2365,9 +2365,6 @@ <string name="settings_active_sessions_show_all">Show All Sessions</string> <string name="settings_active_sessions_manage">Manage Sessions</string> <string name="settings_active_sessions_signout_device">Sign out of this session</string> - <string name="settings_sessions_list">Sessions</string> - <string name="device_manager_sessions_other_title">Other sessions</string> - <string name="device_manager_sessions_other_description">For best security, verify your sessions and sign out from any session that you don’t recognize or use anymore.</string> <string name="settings_server_name">Server name</string> <string name="settings_server_version">Server version</string> @@ -3229,6 +3226,9 @@ <!-- Device Manager --> <string name="device_manager_settings_active_sessions_show_all">Show All Sessions (V2, WIP)</string> + <string name="device_manager_sessions_list_title">Sessions</string> + <string name="device_manager_sessions_other_title">Other sessions</string> + <string name="device_manager_sessions_other_description">For best security, verify your sessions and sign out from any session that you don’t recognize or use anymore.</string> <string name="a11y_device_manager_device_type_mobile">Mobile</string> <string name="a11y_device_manager_device_type_web">Web</string> <string name="a11y_device_manager_device_type_desktop">Desktop</string> diff --git a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt index 62e7140742..7a5b95f88e 100644 --- a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt @@ -90,6 +90,7 @@ import im.vector.app.features.settings.devices.DeviceVerificationInfoBottomSheet import im.vector.app.features.settings.devices.DevicesViewModel import im.vector.app.features.settings.devices.v2.details.SessionDetailsViewModel import im.vector.app.features.settings.devices.v2.othersessions.OtherSessionsViewModel +import im.vector.app.features.settings.devices.v2.more.SessionLearnMoreViewModel import im.vector.app.features.settings.devices.v2.overview.SessionOverviewViewModel import im.vector.app.features.settings.devices.v2.rename.RenameSessionViewModel import im.vector.app.features.settings.devtools.AccountDataViewModel @@ -659,4 +660,9 @@ interface MavericksViewModelModule { @IntoMap @MavericksViewModelKey(RenameSessionViewModel::class) fun renameSessionViewModelFactory(factory: RenameSessionViewModel.Factory): MavericksAssistedViewModelFactory<*, *> + + @Binds + @IntoMap + @MavericksViewModelKey(SessionLearnMoreViewModel::class) + fun sessionLearnMoreViewModelFactory(factory: SessionLearnMoreViewModel.Factory): MavericksAssistedViewModelFactory<*, *> } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt index 1e5c4d88e0..885a91b9d7 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt @@ -21,7 +21,6 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.core.view.isVisible import com.airbnb.mvrx.Success @@ -44,6 +43,7 @@ import im.vector.app.features.settings.devices.v2.list.SESSION_IS_MARKED_AS_INAC import im.vector.app.features.settings.devices.v2.list.SecurityRecommendationView import im.vector.app.features.settings.devices.v2.list.SecurityRecommendationViewState import im.vector.app.features.settings.devices.v2.list.SessionInfoViewState +import im.vector.app.features.settings.devices.v2.more.SessionLearnMoreBottomSheet import javax.inject.Inject /** @@ -76,7 +76,7 @@ class VectorSettingsDevicesFragment : private fun initToolbar() { (activity as? AppCompatActivity) ?.supportActionBar - ?.setTitle(R.string.settings_sessions_list) + ?.setTitle(R.string.device_manager_sessions_list_title) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -157,10 +157,22 @@ class VectorSettingsDevicesFragment : private fun initLearnMoreButtons() { views.deviceListHeaderOtherSessions.onLearnMoreClickListener = { - Toast.makeText(context, "Learn more other", Toast.LENGTH_LONG).show() + showLearnMoreInfoOtherSessions() } } + private fun showLearnMoreInfoOtherSessions() { + val args = SessionLearnMoreBottomSheet.Args( + title = getString(R.string.device_manager_sessions_other_title), + description = buildString { + append(getString(R.string.device_manager_learn_more_sessions_unverified)) + append("\n\n") + append(getString(R.string.device_manager_learn_more_sessions_inactive)) + } + ) + SessionLearnMoreBottomSheet.show(childFragmentManager, args) + } + private fun cleanUpLearnMoreButtonsListeners() { views.deviceListHeaderOtherSessions.onLearnMoreClickListener = null } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/more/SessionLearnMoreBottomSheet.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/more/SessionLearnMoreBottomSheet.kt new file mode 100644 index 0000000000..5846c8aa0b --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/more/SessionLearnMoreBottomSheet.kt @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.settings.devices.v2.more + +import android.os.Parcelable +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.fragment.app.FragmentManager +import com.airbnb.mvrx.fragmentViewModel +import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment +import im.vector.app.databinding.BottomSheetSessionLearnMoreBinding +import kotlinx.parcelize.Parcelize + +@AndroidEntryPoint +class SessionLearnMoreBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetSessionLearnMoreBinding>() { + + @Parcelize + data class Args( + val title: String, + val description: String, + ) : Parcelable + + private val viewModel: SessionLearnMoreViewModel by fragmentViewModel() + + override val showExpanded = true + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetSessionLearnMoreBinding { + return BottomSheetSessionLearnMoreBinding.inflate(inflater, container, false) + } + + override fun invalidate() = withState(viewModel) { viewState -> + super.invalidate() + views.bottomSheetSessionLearnMoreTitle.text = viewState.title + views.bottomSheetSessionLearnMoreDescription.text = viewState.description + } + + companion object { + + fun show(fragmentManager: FragmentManager, args: Args) { + val bottomSheet = SessionLearnMoreBottomSheet() + bottomSheet.isCancelable = true + bottomSheet.setArguments(args) + bottomSheet.show(fragmentManager, "SessionLearnMoreBottomSheet") + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/more/SessionLearnMoreViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/more/SessionLearnMoreViewModel.kt new file mode 100644 index 0000000000..09ca2df15d --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/more/SessionLearnMoreViewModel.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.settings.devices.v2.more + +import com.airbnb.mvrx.MavericksViewModelFactory +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import im.vector.app.core.di.MavericksAssistedViewModelFactory +import im.vector.app.core.di.hiltMavericksViewModelFactory +import im.vector.app.core.platform.EmptyAction +import im.vector.app.core.platform.EmptyViewEvents +import im.vector.app.core.platform.VectorViewModel + +class SessionLearnMoreViewModel @AssistedInject constructor( + @Assisted initialState: SessionLearnMoreViewState, +) : VectorViewModel<SessionLearnMoreViewState, EmptyAction, EmptyViewEvents>(initialState) { + + @AssistedFactory + interface Factory : MavericksAssistedViewModelFactory<SessionLearnMoreViewModel, SessionLearnMoreViewState> { + override fun create(initialState: SessionLearnMoreViewState): SessionLearnMoreViewModel + } + + companion object : MavericksViewModelFactory<SessionLearnMoreViewModel, SessionLearnMoreViewState> by hiltMavericksViewModelFactory() + + override fun handle(action: EmptyAction) { + // do nothing + } +} diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/more/SessionLearnMoreViewState.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/more/SessionLearnMoreViewState.kt new file mode 100644 index 0000000000..cade2ce861 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/more/SessionLearnMoreViewState.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.settings.devices.v2.more + +import com.airbnb.mvrx.MavericksState + +data class SessionLearnMoreViewState( + val title: String, + val description: String, +) : MavericksState { + constructor(args: SessionLearnMoreBottomSheet.Args) : this( + title = args.title, + description = args.description, + ) +} diff --git a/vector/src/main/res/layout/bottom_sheet_session_learn_more.xml b/vector/src/main/res/layout/bottom_sheet_session_learn_more.xml new file mode 100644 index 0000000000..bdfa4b3c1a --- /dev/null +++ b/vector/src/main/res/layout/bottom_sheet_session_learn_more.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/bottomSheetScrollView" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:animateLayoutChanges="true" + android:background="?colorSurface" + android:fadeScrollbars="false" + android:scrollbars="vertical"> + + <androidx.constraintlayout.widget.ConstraintLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingHorizontal="24dp" + android:paddingVertical="16dp"> + + <TextView + android:id="@+id/bottomSheetSessionLearnMoreTitle" + style="@style/TextAppearance.Vector.Subtitle.Medium.DevicesManagement" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:layout_constraintBottom_toTopOf="@id/bottomSheetSessionLearnMoreDescription" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + tools:text="Verified sessions" /> + + <TextView + android:id="@+id/bottomSheetSessionLearnMoreDescription" + style="@style/TextAppearance.Vector.Body" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/bottomSheetSessionLearnMoreTitle" + tools:text="Further context on verified sessions. What do those mean, and how do they differ from unverified ones." /> + + </androidx.constraintlayout.widget.ConstraintLayout> + +</androidx.core.widget.NestedScrollView> From 97cdda45d6692a6f0841f2bdcab18ab7ca8b918e Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Mon, 12 Sep 2022 17:14:31 +0200 Subject: [PATCH 060/187] Adding close button --- library/ui-strings/src/main/res/values/strings.xml | 1 + .../devices/v2/more/SessionLearnMoreBottomSheet.kt | 13 +++++++++++++ .../res/layout/bottom_sheet_session_learn_more.xml | 14 +++++++++++++- 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 40653db278..8ae869639a 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -406,6 +406,7 @@ <string name="action_reset">Reset</string> <string name="action_learn_more">Learn more</string> <string name="action_next">Next</string> + <string name="action_got_it">Got it</string> <string name="copied_to_clipboard">Copied to clipboard</string> diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/more/SessionLearnMoreBottomSheet.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/more/SessionLearnMoreBottomSheet.kt index 5846c8aa0b..22ca06eb1e 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/more/SessionLearnMoreBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/more/SessionLearnMoreBottomSheet.kt @@ -16,8 +16,10 @@ package im.vector.app.features.settings.devices.v2.more +import android.os.Bundle import android.os.Parcelable import android.view.LayoutInflater +import android.view.View import android.view.ViewGroup import androidx.fragment.app.FragmentManager import com.airbnb.mvrx.fragmentViewModel @@ -44,6 +46,17 @@ class SessionLearnMoreBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSh return BottomSheetSessionLearnMoreBinding.inflate(inflater, container, false) } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + initCloseButton() + } + + private fun initCloseButton() { + views.bottomSheetSessionLearnMoreCloseButton.debouncedClicks { + dismiss() + } + } + override fun invalidate() = withState(viewModel) { viewState -> super.invalidate() views.bottomSheetSessionLearnMoreTitle.text = viewState.title diff --git a/vector/src/main/res/layout/bottom_sheet_session_learn_more.xml b/vector/src/main/res/layout/bottom_sheet_session_learn_more.xml index bdfa4b3c1a..72e1bbdf23 100644 --- a/vector/src/main/res/layout/bottom_sheet_session_learn_more.xml +++ b/vector/src/main/res/layout/bottom_sheet_session_learn_more.xml @@ -34,12 +34,24 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="16dp" - app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintBottom_toTopOf="@id/bottomSheetSessionLearnMoreCloseButton" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/bottomSheetSessionLearnMoreTitle" tools:text="Further context on verified sessions. What do those mean, and how do they differ from unverified ones." /> + <Button + android:id="@+id/bottomSheetSessionLearnMoreCloseButton" + style="@style/Widget.Vector.Button.CallToAction" + android:layout_width="0dp" + android:layout_height="56dp" + android:layout_marginTop="24dp" + android:text="@string/action_got_it" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/bottomSheetSessionLearnMoreDescription" /> + </androidx.constraintlayout.widget.ConstraintLayout> </androidx.core.widget.NestedScrollView> From 400b1b0697de0c8c7700bbe16e86ab84482a030f Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Mon, 12 Sep 2022 17:29:16 +0200 Subject: [PATCH 061/187] Adding handle at top --- .../bottom_sheet_session_learn_more.xml | 96 ++++++++++--------- 1 file changed, 49 insertions(+), 47 deletions(-) diff --git a/vector/src/main/res/layout/bottom_sheet_session_learn_more.xml b/vector/src/main/res/layout/bottom_sheet_session_learn_more.xml index 72e1bbdf23..466ab5af49 100644 --- a/vector/src/main/res/layout/bottom_sheet_session_learn_more.xml +++ b/vector/src/main/res/layout/bottom_sheet_session_learn_more.xml @@ -1,57 +1,59 @@ <?xml version="1.0" encoding="utf-8"?> -<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android" +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" - android:id="@+id/bottomSheetScrollView" android:layout_width="match_parent" - android:layout_height="match_parent" - android:animateLayoutChanges="true" + android:layout_height="wrap_content" android:background="?colorSurface" - android:fadeScrollbars="false" - android:scrollbars="vertical"> + android:paddingHorizontal="24dp" + android:paddingTop="8dp" + android:paddingBottom="16dp"> - <androidx.constraintlayout.widget.ConstraintLayout - android:layout_width="match_parent" + <View + android:id="@+id/bottomSheetSessionLearnMoreHandle" + android:layout_width="36dp" + android:layout_height="6dp" + android:background="@drawable/ic_bottom_sheet_handle" + app:layout_constraintBottom_toTopOf="@id/bottomSheetSessionLearnMoreTitle" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <TextView + android:id="@+id/bottomSheetSessionLearnMoreTitle" + style="@style/TextAppearance.Vector.Subtitle.Medium.DevicesManagement" + android:layout_width="wrap_content" android:layout_height="wrap_content" - android:paddingHorizontal="24dp" - android:paddingVertical="16dp"> + android:layout_marginTop="20dp" + app:layout_constraintBottom_toTopOf="@id/bottomSheetSessionLearnMoreDescription" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/bottomSheetSessionLearnMoreHandle" + tools:text="Verified sessions" /> - <TextView - android:id="@+id/bottomSheetSessionLearnMoreTitle" - style="@style/TextAppearance.Vector.Subtitle.Medium.DevicesManagement" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - app:layout_constraintBottom_toTopOf="@id/bottomSheetSessionLearnMoreDescription" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintHorizontal_bias="0" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" - tools:text="Verified sessions" /> + <TextView + android:id="@+id/bottomSheetSessionLearnMoreDescription" + style="@style/TextAppearance.Vector.Body" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + app:layout_constraintBottom_toTopOf="@id/bottomSheetSessionLearnMoreCloseButton" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/bottomSheetSessionLearnMoreTitle" + tools:text="Further context on verified sessions. What do those mean, and how do they differ from unverified ones." /> - <TextView - android:id="@+id/bottomSheetSessionLearnMoreDescription" - style="@style/TextAppearance.Vector.Body" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="16dp" - app:layout_constraintBottom_toTopOf="@id/bottomSheetSessionLearnMoreCloseButton" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/bottomSheetSessionLearnMoreTitle" - tools:text="Further context on verified sessions. What do those mean, and how do they differ from unverified ones." /> + <Button + android:id="@+id/bottomSheetSessionLearnMoreCloseButton" + style="@style/Widget.Vector.Button.CallToAction" + android:layout_width="0dp" + android:layout_height="56dp" + android:layout_marginTop="24dp" + android:text="@string/action_got_it" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/bottomSheetSessionLearnMoreDescription" /> - <Button - android:id="@+id/bottomSheetSessionLearnMoreCloseButton" - style="@style/Widget.Vector.Button.CallToAction" - android:layout_width="0dp" - android:layout_height="56dp" - android:layout_marginTop="24dp" - android:text="@string/action_got_it" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/bottomSheetSessionLearnMoreDescription" /> - - </androidx.constraintlayout.widget.ConstraintLayout> - -</androidx.core.widget.NestedScrollView> +</androidx.constraintlayout.widget.ConstraintLayout> From 51532687b9ebd5c9274ec448e699443c8aaa8aaa Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Mon, 12 Sep 2022 17:47:29 +0200 Subject: [PATCH 062/187] Learn more in session overview screen --- .../v2/VectorSettingsDevicesFragment.kt | 2 +- .../v2/overview/SessionOverviewFragment.kt | 24 ++++++++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt index 885a91b9d7..2a90cf6c19 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt @@ -168,7 +168,7 @@ class VectorSettingsDevicesFragment : append(getString(R.string.device_manager_learn_more_sessions_unverified)) append("\n\n") append(getString(R.string.device_manager_learn_more_sessions_inactive)) - } + }, ) SessionLearnMoreBottomSheet.show(childFragmentManager, args) } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt index 4af4913183..46121340b5 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt @@ -22,7 +22,6 @@ import android.view.LayoutInflater import android.view.MenuItem import android.view.View import android.view.ViewGroup -import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.core.view.isVisible import com.airbnb.mvrx.Success @@ -44,6 +43,8 @@ import im.vector.app.features.settings.devices.v2.list.SessionInfoViewState import im.vector.app.features.workers.signout.SignOutUiWorker import org.matrix.android.sdk.api.auth.data.LoginFlowTypes import org.matrix.android.sdk.api.extensions.orFalse +import im.vector.app.features.settings.devices.v2.more.SessionLearnMoreBottomSheet +import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel import javax.inject.Inject /** @@ -204,6 +205,9 @@ class SessionOverviewFragment : isLastSeenDetailsVisible = true, ) views.sessionOverviewInfo.render(infoViewState, dateFormatter, drawableProvider, colorProvider) + views.sessionOverviewInfo.onLearnMoreClickListener = { + showLearnMoreInfoVerificationStatus(viewState.deviceFullInfo.roomEncryptionTrustLevel == RoomEncryptionTrustLevel.Trusted) + } } else { views.sessionOverviewInfo.isVisible = false } @@ -249,4 +253,22 @@ class SessionOverviewFragment : reAuthActivityResultLauncher.launch(intent) } } + + private fun showLearnMoreInfoVerificationStatus(isVerified: Boolean) { + val titleResId = if (isVerified) { + R.string.device_manager_verification_status_verified + } else { + R.string.device_manager_verification_status_unverified + } + val descriptionResId = if (isVerified) { + R.string.device_manager_learn_more_session_verified + } else { + R.string.device_manager_learn_more_sessions_unverified + } + val args = SessionLearnMoreBottomSheet.Args( + title = getString(titleResId), + description = getString(descriptionResId), + ) + SessionLearnMoreBottomSheet.show(childFragmentManager, args) + } } From 87e1b53c6e5f83fa3250d367d02923719f20510c Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Wed, 14 Sep 2022 10:34:04 +0200 Subject: [PATCH 063/187] Fix coding style --- .../features/settings/devices/v2/list/SessionsListHeaderView.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionsListHeaderView.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionsListHeaderView.kt index 0264db08e1..0660e7d642 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionsListHeaderView.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionsListHeaderView.kt @@ -66,7 +66,7 @@ class SessionsListHeaderView @JvmOverloads constructor( } val hasLearnMoreLink = typedArray.getBoolean(R.styleable.SessionsListHeaderView_sessionsListHeaderHasLearnMoreLink, true) - if(hasLearnMoreLink) { + if (hasLearnMoreLink) { setDescriptionWithLearnMore(description) } else { binding.sessionsListHeaderDescription.text = description From d1bc7d591fde2f012cf9afde9a066d9f7218d2ec Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Fri, 23 Sep 2022 17:27:04 +0200 Subject: [PATCH 064/187] Revert changes to translations --- library/ui-strings/src/main/res/values-ca/strings.xml | 2 +- library/ui-strings/src/main/res/values-cs/strings.xml | 2 +- library/ui-strings/src/main/res/values-de/strings.xml | 2 +- library/ui-strings/src/main/res/values-et/strings.xml | 2 +- library/ui-strings/src/main/res/values-fa/strings.xml | 2 +- library/ui-strings/src/main/res/values-fr/strings.xml | 2 +- library/ui-strings/src/main/res/values-hu/strings.xml | 2 +- library/ui-strings/src/main/res/values-in/strings.xml | 2 +- library/ui-strings/src/main/res/values-it/strings.xml | 2 +- library/ui-strings/src/main/res/values-nl/strings.xml | 2 +- library/ui-strings/src/main/res/values-pl/strings.xml | 2 +- library/ui-strings/src/main/res/values-pt-rBR/strings.xml | 2 +- library/ui-strings/src/main/res/values-ru/strings.xml | 2 +- library/ui-strings/src/main/res/values-sk/strings.xml | 2 +- library/ui-strings/src/main/res/values-uk/strings.xml | 2 +- library/ui-strings/src/main/res/values-zh-rCN/strings.xml | 2 +- library/ui-strings/src/main/res/values-zh-rTW/strings.xml | 2 +- library/ui-strings/src/main/res/values/strings.xml | 3 +-- .../settings/devices/v2/VectorSettingsDevicesFragment.kt | 2 +- 19 files changed, 19 insertions(+), 20 deletions(-) diff --git a/library/ui-strings/src/main/res/values-ca/strings.xml b/library/ui-strings/src/main/res/values-ca/strings.xml index daeebf6b30..5af1f7b379 100644 --- a/library/ui-strings/src/main/res/values-ca/strings.xml +++ b/library/ui-strings/src/main/res/values-ca/strings.xml @@ -2604,7 +2604,7 @@ <string name="explore_rooms">Explora sales</string> <string name="device_manager_sessions_other_description">Per estar més segur, verifica les teves sessions i tanca qualsevol sessió que no reconeguis o ja no utilitzis.</string> <string name="device_manager_sessions_other_title">Altres sessions</string> - <string name="device_manager_sessions_list_title">Sessions</string> + <string name="settings_sessions_list">Sessions</string> <string name="a11y_open_spaces">Obre la llista d\'espais</string> <string name="a11y_create_message">Crea un nou xat o sala</string> <string name="room_list_filter_people">Gent</string> 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 7e6a012c65..79f8311159 100644 --- a/library/ui-strings/src/main/res/values-cs/strings.xml +++ b/library/ui-strings/src/main/res/values-cs/strings.xml @@ -2653,7 +2653,7 @@ <string name="device_manager_settings_active_sessions_show_all">Zobrazit všechny relace (V2, WIP)</string> <string name="device_manager_sessions_other_description">V zájmu co nejlepšího zabezpečení ověřujte své relace a odhlašujte se ze všech relací, které již nepoznáváte nebo nepoužíváte.</string> <string name="device_manager_sessions_other_title">Ostatní relace</string> - <string name="device_manager_sessions_list_title">Relace</string> + <string name="settings_sessions_list">Relace</string> <string name="a11y_open_spaces">Seznam otevřených prostorů</string> <string name="a11y_create_message">Vytvořit novou konverzaci nebo místnost</string> <string name="room_list_filter_people">Lidé</string> 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 0b6d8b9666..e01fc898a3 100644 --- a/library/ui-strings/src/main/res/values-de/strings.xml +++ b/library/ui-strings/src/main/res/values-de/strings.xml @@ -2589,7 +2589,7 @@ <string name="device_manager_settings_active_sessions_show_all">Alle Sitzungen anzeigen (V2, in Arbeit)</string> <string name="device_manager_sessions_other_description">Für bestmögliche Sicherheit verifiziere deine Sitzungen und melde dich von allen ab, die du nicht erkennst oder nutzt.</string> <string name="device_manager_sessions_other_title">Andere Sitzungen</string> - <string name="device_manager_sessions_list_title">Sitzungen</string> + <string name="settings_sessions_list">Sitzungen</string> <string name="a11y_open_spaces">Space-Liste öffnen</string> <string name="a11y_create_message">Beginne ein Gespräch oder erstelle einen Raum</string> <string name="room_list_filter_favourites">Favoriten</string> 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 51882a505a..9bd1dd23b7 100644 --- a/library/ui-strings/src/main/res/values-et/strings.xml +++ b/library/ui-strings/src/main/res/values-et/strings.xml @@ -2594,7 +2594,7 @@ <string name="device_manager_settings_active_sessions_show_all">Näita kõiki sessioone (V2, WIP)</string> <string name="device_manager_sessions_other_description">Parima turvalisuse nimel verifitseeri kõik oma sessioonid ning logi välja neist, mida sa enam ei kasuta.</string> <string name="device_manager_sessions_other_title">Muud sessioonid</string> - <string name="device_manager_sessions_list_title">Sessionid</string> + <string name="settings_sessions_list">Sessionid</string> <string name="a11y_open_spaces">Ava kogukondade loend</string> <string name="a11y_create_message">Alusta uut vestlust või loo uus jututuba</string> <string name="room_list_filter_people">Inimesed</string> 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 1b8d3a4fb3..3a95348f0a 100644 --- a/library/ui-strings/src/main/res/values-fa/strings.xml +++ b/library/ui-strings/src/main/res/values-fa/strings.xml @@ -2603,7 +2603,7 @@ <string name="device_manager_settings_active_sessions_show_all">نمایش تمامی نشستها (ن۲، دحت)</string> <string name="device_manager_sessions_other_description">برای امنیت بیشتر، نشستهایتان را تأیید و از هر نشستی که تشخیصش نمیدهید یا دیگر استفاده نمیکنید خارج شوید.</string> <string name="device_manager_sessions_other_title">دیگر نشستها</string> - <string name="device_manager_sessions_list_title">نشستها</string> + <string name="settings_sessions_list">نشستها</string> <string name="a11y_open_spaces">گشودن سیاههٔ فضاها</string> <string name="a11y_create_message">ایجاد اتاق یا گفتوگویی جدید</string> <string name="room_list_filter_people">افراد</string> 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 bbd82dd596..5a19ccf2da 100644 --- a/library/ui-strings/src/main/res/values-fr/strings.xml +++ b/library/ui-strings/src/main/res/values-fr/strings.xml @@ -2603,7 +2603,7 @@ <string name="device_manager_settings_active_sessions_show_all">Afficher toutes les sessions (V2, en cours)</string> <string name="device_manager_sessions_other_description">Pour une meilleure sécurité, vérifiez vos sessions et déconnectez toutes les sessions que vous ne connaissez pas ou que vous n’utilisez plus.</string> <string name="device_manager_sessions_other_title">Autres sessions</string> - <string name="device_manager_sessions_list_title">Sessions</string> + <string name="settings_sessions_list">Sessions</string> <string name="a11y_open_spaces">Ouvrir la liste des espaces</string> <string name="a11y_create_message">Créer une nouvelle conversation ou salon</string> <string name="room_list_filter_people">Personnes</string> 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 397c2d4662..3068556fe4 100644 --- a/library/ui-strings/src/main/res/values-hu/strings.xml +++ b/library/ui-strings/src/main/res/values-hu/strings.xml @@ -2614,7 +2614,7 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze <string name="device_manager_settings_active_sessions_show_all">Minden munkamenet megjelenítése (V2, WIP)</string> <string name="device_manager_sessions_other_description">A legjobb biztonság érdekében ellenőrizd a munkameneteket, és jelentkezz ki minden olyan munkamenetből, melyet már nem ismersz fel vagy nem használsz.</string> <string name="device_manager_sessions_other_title">Más munkamenetek</string> - <string name="device_manager_sessions_list_title">Munkamenetek</string> + <string name="settings_sessions_list">Munkamenetek</string> <string name="a11y_open_spaces">Nyitott területek listája</string> <string name="a11y_create_message">Új beszélgetés vagy szoba létrehozása</string> <string name="room_list_filter_people">Emberek</string> 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 18dbbff4ad..3b30950bd1 100644 --- a/library/ui-strings/src/main/res/values-in/strings.xml +++ b/library/ui-strings/src/main/res/values-in/strings.xml @@ -2555,7 +2555,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.</string> <string name="device_manager_settings_active_sessions_show_all">Tampilkan Semua Sesi (V2, Dalam Pengembangan)</string> <string name="device_manager_sessions_other_description">Untuk keamanan terbaik, verifikasi sesi Anda dan keluarkan sesi apa pun yang Anda tidak kenal atau Anda tidak gunakan lagi.</string> <string name="device_manager_sessions_other_title">Sesi lainnya</string> - <string name="device_manager_sessions_list_title">Sesi</string> + <string name="settings_sessions_list">Sesi</string> <string name="a11y_open_spaces">Buka daftar space</string> <string name="a11y_create_message">Buat percakapan atau ruangan baru</string> <string name="room_list_filter_people">Orang</string> 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 8ad2e62821..b7b0fe91af 100644 --- a/library/ui-strings/src/main/res/values-it/strings.xml +++ b/library/ui-strings/src/main/res/values-it/strings.xml @@ -2594,7 +2594,7 @@ <string name="device_manager_settings_active_sessions_show_all">Mostra tutte le sessioni (V2, WIP)</string> <string name="device_manager_sessions_other_description">Per una maggiore sicurezza, verifica le tue sessioni e disconnetti quelle che non riconosci o che non usi più.</string> <string name="device_manager_sessions_other_title">Altre sessioni</string> - <string name="device_manager_sessions_list_title">Sessioni</string> + <string name="settings_sessions_list">Sessioni</string> <string name="a11y_open_spaces">Apri elenco spazi</string> <string name="a11y_create_message">Crea una nuova conversazione o stanza</string> <string name="room_list_filter_people">Persone</string> diff --git a/library/ui-strings/src/main/res/values-nl/strings.xml b/library/ui-strings/src/main/res/values-nl/strings.xml index c5de4ae5f7..ce122b0646 100644 --- a/library/ui-strings/src/main/res/values-nl/strings.xml +++ b/library/ui-strings/src/main/res/values-nl/strings.xml @@ -2602,7 +2602,7 @@ <string name="a11y_open_settings">Open instellingen</string> <string name="device_manager_sessions_other_description">Voor de beste beveiliging verifieert u uw sessies en meldt u zich af bij elke sessie die u niet meer herkent of gebruikt.</string> <string name="device_manager_sessions_other_title">Andere sessies</string> - <string name="device_manager_sessions_list_title">Sessies</string> + <string name="settings_sessions_list">Sessies</string> <string name="a11y_open_spaces">Lijst met publieke spaces</string> <string name="a11y_create_message">Nieuw gesprek of nieuwe kamer aanmaken</string> <string name="room_list_filter_people">Personen</string> 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 6deb3dd2e7..b7b73eb9e6 100644 --- a/library/ui-strings/src/main/res/values-pl/strings.xml +++ b/library/ui-strings/src/main/res/values-pl/strings.xml @@ -2699,7 +2699,7 @@ <string name="a11y_open_settings">Otwórz ustawienia</string> <string name="device_manager_sessions_other_description">Aby zapewnić najlepsze bezpieczeństwo, zweryfikuj swoje sesje i wyloguj się z każdej sesji, której już nie rozpoznajesz lub której już nie używasz.</string> <string name="device_manager_sessions_other_title">Inne sesje</string> - <string name="device_manager_sessions_list_title">Sesje</string> + <string name="settings_sessions_list">Sesje</string> <string name="a11y_open_spaces">Lista otwartych przestrzeni</string> <string name="a11y_create_message">Utwórz nową rozmowę lub pokój</string> <string name="room_list_filter_people">Ludzie</string> diff --git a/library/ui-strings/src/main/res/values-pt-rBR/strings.xml b/library/ui-strings/src/main/res/values-pt-rBR/strings.xml index 1c5291d6f3..817c7646df 100644 --- a/library/ui-strings/src/main/res/values-pt-rBR/strings.xml +++ b/library/ui-strings/src/main/res/values-pt-rBR/strings.xml @@ -2603,7 +2603,7 @@ <string name="device_manager_settings_active_sessions_show_all">Mostrar Todas Sessões (V2, WIP)</string> <string name="device_manager_sessions_other_description">Para a melhor segurança, verifique suas sessões e faça signout de qualquer sessão que você não reconhece ou usa mais.</string> <string name="device_manager_sessions_other_title">Outras sessões</string> - <string name="device_manager_sessions_list_title">Sessões</string> + <string name="settings_sessions_list">Sessões</string> <string name="a11y_open_spaces">Abrir lista de espaços</string> <string name="a11y_create_message">Criar uma nova conversa ou sala</string> <string name="room_list_filter_all">Todas</string> 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 718e4cd125..7c9d073035 100644 --- a/library/ui-strings/src/main/res/values-ru/strings.xml +++ b/library/ui-strings/src/main/res/values-ru/strings.xml @@ -2662,7 +2662,7 @@ <string name="all_chats">Все беседы</string> <string name="device_manager_sessions_other_description">Для лучшей безопасности заверьте свои сессии и выйдите из тех, которые более не признаёте или не используете.</string> <string name="device_manager_sessions_other_title">Другие сессии</string> - <string name="device_manager_sessions_list_title">Сессии</string> + <string name="settings_sessions_list">Сессии</string> <string name="a11y_create_message">Создать беседу или комнату</string> <string name="device_manager_settings_active_sessions_show_all">Показать все сессии (V2, в разработке)</string> <string name="room_list_filter_people">Люди</string> 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 b100db2717..9eb22e3ae3 100644 --- a/library/ui-strings/src/main/res/values-sk/strings.xml +++ b/library/ui-strings/src/main/res/values-sk/strings.xml @@ -2653,7 +2653,7 @@ <string name="device_manager_settings_active_sessions_show_all">Zobraziť všetky relácie (V2, WIP)</string> <string name="device_manager_sessions_other_description">V záujme čo najlepšieho zabezpečenia overte svoje relácie a odhláste sa z každej relácie, ktorú už nepoznáte alebo nepoužívate.</string> <string name="device_manager_sessions_other_title">Iné relácie</string> - <string name="device_manager_sessions_list_title">Relácie</string> + <string name="settings_sessions_list">Relácie</string> <string name="a11y_open_spaces">Otvoriť zoznam priestorov</string> <string name="a11y_create_message">Vytvoriť novú konverzáciu alebo miestnosť</string> <string name="room_list_filter_people">Ľudia</string> 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 0de8e7191e..3e511f8459 100644 --- a/library/ui-strings/src/main/res/values-uk/strings.xml +++ b/library/ui-strings/src/main/res/values-uk/strings.xml @@ -2703,7 +2703,7 @@ <string name="device_manager_settings_active_sessions_show_all">Показати всі сеанси (V2, WIP)</string> <string name="device_manager_sessions_other_description">Для найкращої безпеки перевірте свої сеанси та вийдіть з усіх сеансів, які ви більше не розпізнаєте або не використовуєте.</string> <string name="device_manager_sessions_other_title">Інші сеанси</string> - <string name="device_manager_sessions_list_title">Сеанси</string> + <string name="settings_sessions_list">Сеанси</string> <string name="a11y_open_spaces">Відкрити список кімнат</string> <string name="a11y_create_message">Створити нову розмову або кімнату</string> <string name="room_list_filter_people">Люди</string> 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 56a399482c..db1dab92e2 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 @@ -2553,7 +2553,7 @@ <string name="device_manager_settings_active_sessions_show_all">显示全部会话(V2, WIP)</string> <string name="device_manager_sessions_other_description">为获得最佳安全性,请验证你的会话,并从任何你不认识或不再使用的会话登出。</string> <string name="device_manager_sessions_other_title">其他会话</string> - <string name="device_manager_sessions_list_title">会话</string> + <string name="settings_sessions_list">会话</string> <string name="a11y_open_spaces">打开空间列表</string> <string name="a11y_create_message">创建新对话或房间</string> <string name="room_list_filter_people">人</string> 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 cfeb6ca915..78caa2cc2e 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 @@ -2553,7 +2553,7 @@ <string name="device_manager_settings_active_sessions_show_all">顯示所有工作階段 (V2, WIP)</string> <string name="device_manager_sessions_other_description">為了取得最佳安全性,請驗證您的工作階段並登出任何您無法識別或不再使用的工作階段。</string> <string name="device_manager_sessions_other_title">其他工作階段</string> - <string name="device_manager_sessions_list_title">工作階段</string> + <string name="settings_sessions_list">工作階段</string> <string name="a11y_open_spaces">開啟空間清單</string> <string name="a11y_create_message">建立新的對話或聊天室</string> <string name="room_list_filter_people">聯絡人</string> diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 8ae869639a..3b93619ea8 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -2366,7 +2366,7 @@ <string name="settings_active_sessions_show_all">Show All Sessions</string> <string name="settings_active_sessions_manage">Manage Sessions</string> <string name="settings_active_sessions_signout_device">Sign out of this session</string> - + <string name="settings_sessions_list">Sessions</string> <string name="settings_server_name">Server name</string> <string name="settings_server_version">Server version</string> <string name="settings_server_upload_size_title">Server file upload limit</string> @@ -3227,7 +3227,6 @@ <!-- Device Manager --> <string name="device_manager_settings_active_sessions_show_all">Show All Sessions (V2, WIP)</string> - <string name="device_manager_sessions_list_title">Sessions</string> <string name="device_manager_sessions_other_title">Other sessions</string> <string name="device_manager_sessions_other_description">For best security, verify your sessions and sign out from any session that you don’t recognize or use anymore.</string> <string name="a11y_device_manager_device_type_mobile">Mobile</string> diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt index 2a90cf6c19..0ed3517fed 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt @@ -76,7 +76,7 @@ class VectorSettingsDevicesFragment : private fun initToolbar() { (activity as? AppCompatActivity) ?.supportActionBar - ?.setTitle(R.string.device_manager_sessions_list_title) + ?.setTitle(R.string.settings_sessions_list) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { From e986d7805e92a83d786abefaa76e077bbc0c5ce5 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Fri, 23 Sep 2022 17:35:29 +0200 Subject: [PATCH 065/187] Fixing after rebase issues --- .../java/im/vector/app/core/di/MavericksViewModelModule.kt | 2 +- .../settings/devices/v2/overview/SessionOverviewFragment.kt | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt index 7a5b95f88e..38b62e1511 100644 --- a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt @@ -89,8 +89,8 @@ import im.vector.app.features.settings.crosssigning.CrossSigningSettingsViewMode import im.vector.app.features.settings.devices.DeviceVerificationInfoBottomSheetViewModel import im.vector.app.features.settings.devices.DevicesViewModel import im.vector.app.features.settings.devices.v2.details.SessionDetailsViewModel -import im.vector.app.features.settings.devices.v2.othersessions.OtherSessionsViewModel import im.vector.app.features.settings.devices.v2.more.SessionLearnMoreViewModel +import im.vector.app.features.settings.devices.v2.othersessions.OtherSessionsViewModel import im.vector.app.features.settings.devices.v2.overview.SessionOverviewViewModel import im.vector.app.features.settings.devices.v2.rename.RenameSessionViewModel import im.vector.app.features.settings.devtools.AccountDataViewModel diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt index 46121340b5..776f6ab926 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt @@ -22,6 +22,7 @@ import android.view.LayoutInflater import android.view.MenuItem import android.view.View import android.view.ViewGroup +import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.core.view.isVisible import com.airbnb.mvrx.Success @@ -206,7 +207,7 @@ class SessionOverviewFragment : ) views.sessionOverviewInfo.render(infoViewState, dateFormatter, drawableProvider, colorProvider) views.sessionOverviewInfo.onLearnMoreClickListener = { - showLearnMoreInfoVerificationStatus(viewState.deviceFullInfo.roomEncryptionTrustLevel == RoomEncryptionTrustLevel.Trusted) + showLearnMoreInfoVerificationStatus(deviceFullInfo.roomEncryptionTrustLevel == RoomEncryptionTrustLevel.Trusted) } } else { views.sessionOverviewInfo.isVisible = false From 28b30e4e4f716251656c268eb9759d9da271b899 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Tue, 27 Sep 2022 17:28:29 +0200 Subject: [PATCH 066/187] Fix after rebase issue --- .../settings/devices/v2/overview/SessionOverviewFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt index 776f6ab926..14b4c5ff08 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt @@ -207,7 +207,7 @@ class SessionOverviewFragment : ) views.sessionOverviewInfo.render(infoViewState, dateFormatter, drawableProvider, colorProvider) views.sessionOverviewInfo.onLearnMoreClickListener = { - showLearnMoreInfoVerificationStatus(deviceFullInfo.roomEncryptionTrustLevel == RoomEncryptionTrustLevel.Trusted) + showLearnMoreInfoVerificationStatus(deviceInfo.roomEncryptionTrustLevel == RoomEncryptionTrustLevel.Trusted) } } else { views.sessionOverviewInfo.isVisible = false From 5704dd4f439ad8888388d1d96a35236456c49e74 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Wed, 28 Sep 2022 09:41:05 +0200 Subject: [PATCH 067/187] Fix wrong import order --- .../settings/devices/v2/overview/SessionOverviewFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt index 14b4c5ff08..2b216b8436 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt @@ -41,10 +41,10 @@ import im.vector.app.databinding.FragmentSessionOverviewBinding import im.vector.app.features.auth.ReAuthActivity import im.vector.app.features.crypto.recover.SetupMode import im.vector.app.features.settings.devices.v2.list.SessionInfoViewState +import im.vector.app.features.settings.devices.v2.more.SessionLearnMoreBottomSheet import im.vector.app.features.workers.signout.SignOutUiWorker import org.matrix.android.sdk.api.auth.data.LoginFlowTypes import org.matrix.android.sdk.api.extensions.orFalse -import im.vector.app.features.settings.devices.v2.more.SessionLearnMoreBottomSheet import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel import javax.inject.Inject From acba3f2aa4cb8532385b5af52fc963ec4e6b8956 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Wed, 28 Sep 2022 10:14:44 +0200 Subject: [PATCH 068/187] Updating learn more strings --- library/ui-strings/src/main/res/values/strings.xml | 8 ++++---- .../settings/devices/v2/VectorSettingsDevicesFragment.kt | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 3b93619ea8..a3c2e610be 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3302,10 +3302,10 @@ <string name="device_manager_session_rename_edit_hint">Session name</string> <string name="device_manager_session_rename_description">Custom session names can help you recognize your devices more easily.</string> <string name="device_manager_session_rename_warning">Please be aware that session names are also visible to people you communicate with.</string> - <string name="device_manager_learn_more_sessions_inactive">Inactive sessions are sessions you have not used in some time. Removing inactive sessions can improve performance, and make it easier for you to identify if a new session is suspicious. In addition, inactive sessions continue to receive encryption keys and so may pose a security or privacy risk if an unauthorised person is able to access the session.</string> - <string name="device_manager_learn_more_sessions_unverified">Unverified sessions are sessions that have logged in with your credentials but not been verified via cross-verification from a verified session, or using your passphrase. You should make especially certain that you recognise these sessions as they could represent an unauthorised use of your account. If you do recognise them, you should verify them as this allows your contacts to see that the new login is really you.</string> - <string name="device_manager_learn_more_session_verified">This session has logged in with your credentials and then been verified, either using your secure passphrase or by cross-verifying from another verified session. This means it holds encryption keys for your previous messages, and confirms to other users you are communicating with that the session is really you.</string> - <string name="device_manager_learn_more_session_rename" tools:ignore="UnusedResources">Other users in direct messages and rooms that you join are able to view a full list of your sessions, including which are verified and unverified. This provides them with confidence that they are really speaking to you, but it also means they can see the session name you enter here.</string> + <string name="device_manager_learn_more_sessions_inactive">Inactive sessions are sessions you have not used in some time, but they continue to receive encryption keys.\n\nRemoving inactive sessions improves security and performance, and makes it easier for you to identify if a new session is suspicious.</string> + <string name="device_manager_learn_more_sessions_unverified">Unverified sessions are sessions that have logged in with your credentials but not been cross-verified.\n\nYou should make especially certain that you recognise these sessions as they could represent an unauthorised use of your account.</string> + <string name="device_manager_learn_more_session_verified">This session has logged in with your credentials and then been verified, either using your secure passphrase or by cross-verifying.\n\nThis means it holds encryption keys for your previous messages, and confirms to other users you are communicating with that the session is really you.</string> + <string name="device_manager_learn_more_session_rename">Other users in direct messages and rooms that you join are able to view a full list of your sessions.\n\nThis provides them with confidence that they are really speaking to you, but it also means they can see the session name you enter here.</string> <!-- Note to translators: %s will be replaces with selected space name --> <string name="home_empty_space_no_rooms_title">%s\nis looking a little empty.</string> diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt index 0ed3517fed..118227a41b 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt @@ -166,7 +166,7 @@ class VectorSettingsDevicesFragment : title = getString(R.string.device_manager_sessions_other_title), description = buildString { append(getString(R.string.device_manager_learn_more_sessions_unverified)) - append("\n\n") + append("\n\n\n") append(getString(R.string.device_manager_learn_more_sessions_inactive)) }, ) From c356c8090b199c02746157b2ab9d8c9f5ffef39b Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Wed, 28 Sep 2022 13:57:18 +0200 Subject: [PATCH 069/187] Removing other sessions section learn more --- .../v2/VectorSettingsDevicesFragment.kt | 20 --------- .../res/layout/fragment_settings_devices.xml | 44 +++++++++---------- 2 files changed, 22 insertions(+), 42 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt index 118227a41b..0fdbd40178 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt @@ -43,7 +43,6 @@ import im.vector.app.features.settings.devices.v2.list.SESSION_IS_MARKED_AS_INAC import im.vector.app.features.settings.devices.v2.list.SecurityRecommendationView import im.vector.app.features.settings.devices.v2.list.SecurityRecommendationViewState import im.vector.app.features.settings.devices.v2.list.SessionInfoViewState -import im.vector.app.features.settings.devices.v2.more.SessionLearnMoreBottomSheet import javax.inject.Inject /** @@ -82,7 +81,6 @@ class VectorSettingsDevicesFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - initLearnMoreButtons() initWaitingView() initOtherSessionsView() initSecurityRecommendationsView() @@ -155,24 +153,6 @@ class VectorSettingsDevicesFragment : super.onDestroyView() } - private fun initLearnMoreButtons() { - views.deviceListHeaderOtherSessions.onLearnMoreClickListener = { - showLearnMoreInfoOtherSessions() - } - } - - private fun showLearnMoreInfoOtherSessions() { - val args = SessionLearnMoreBottomSheet.Args( - title = getString(R.string.device_manager_sessions_other_title), - description = buildString { - append(getString(R.string.device_manager_learn_more_sessions_unverified)) - append("\n\n\n") - append(getString(R.string.device_manager_learn_more_sessions_inactive)) - }, - ) - SessionLearnMoreBottomSheet.show(childFragmentManager, args) - } - private fun cleanUpLearnMoreButtonsListeners() { views.deviceListHeaderOtherSessions.onLearnMoreClickListener = null } diff --git a/vector/src/main/res/layout/fragment_settings_devices.xml b/vector/src/main/res/layout/fragment_settings_devices.xml index 4f4e14eaad..951e9eba05 100644 --- a/vector/src/main/res/layout/fragment_settings_devices.xml +++ b/vector/src/main/res/layout/fragment_settings_devices.xml @@ -12,12 +12,12 @@ android:id="@+id/deviceListHeaderSectionSecurityRecommendations" android:layout_width="0dp" android:layout_height="wrap_content" - app:sessionsListHeaderDescription="@string/device_manager_header_section_security_recommendations_description" - app:sessionsListHeaderTitle="@string/device_manager_header_section_security_recommendations_title" - app:sessionsListHeaderHasLearnMoreLink="false" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" /> + app:layout_constraintTop_toTopOf="parent" + app:sessionsListHeaderDescription="@string/device_manager_header_section_security_recommendations_description" + app:sessionsListHeaderHasLearnMoreLink="false" + app:sessionsListHeaderTitle="@string/device_manager_header_section_security_recommendations_title" /> <im.vector.app.features.settings.devices.v2.list.SecurityRecommendationView android:id="@+id/deviceListUnverifiedSessionsRecommendation" @@ -25,13 +25,13 @@ android:layout_height="wrap_content" android:layout_marginHorizontal="16dp" android:layout_marginVertical="16dp" - app:recommendationTitle="@string/device_manager_unverified_sessions_title" - app:recommendationDescription="@string/device_manager_unverified_sessions_description" - app:recommendationImageResource="@drawable/ic_shield_warning_no_border" - app:recommendationImageBackgroundTint="@color/shield_color_warning_background" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/deviceListHeaderSectionSecurityRecommendations"/> + app:layout_constraintTop_toBottomOf="@id/deviceListHeaderSectionSecurityRecommendations" + app:recommendationDescription="@string/device_manager_unverified_sessions_description" + app:recommendationImageBackgroundTint="@color/shield_color_warning_background" + app:recommendationImageResource="@drawable/ic_shield_warning_no_border" + app:recommendationTitle="@string/device_manager_unverified_sessions_title" /> <im.vector.app.features.settings.devices.v2.list.SecurityRecommendationView android:id="@+id/deviceListInactiveSessionsRecommendation" @@ -39,13 +39,13 @@ android:layout_height="wrap_content" android:layout_marginHorizontal="16dp" android:layout_marginVertical="16dp" - app:recommendationTitle="@string/device_manager_inactive_sessions_title" - app:recommendationDescription="@plurals/device_manager_inactive_sessions_description" - app:recommendationImageResource="@drawable/ic_inactive_sessions" - app:recommendationImageBackgroundTint="?vctr_system" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/deviceListUnverifiedSessionsRecommendation"/> + app:layout_constraintTop_toBottomOf="@id/deviceListUnverifiedSessionsRecommendation" + app:recommendationDescription="@plurals/device_manager_inactive_sessions_description" + app:recommendationImageBackgroundTint="?vctr_system" + app:recommendationImageResource="@drawable/ic_inactive_sessions" + app:recommendationTitle="@string/device_manager_inactive_sessions_title" /> <View android:id="@+id/deviceListSecurityRecommendationsDivider" @@ -61,12 +61,12 @@ android:id="@+id/deviceListHeaderCurrentSession" android:layout_width="0dp" android:layout_height="wrap_content" - app:sessionsListHeaderDescription="" - app:sessionsListHeaderTitle="@string/device_manager_current_session_title" - app:sessionsListHeaderHasLearnMoreLink="false" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/deviceListSecurityRecommendationsDivider" /> + app:layout_constraintTop_toBottomOf="@id/deviceListSecurityRecommendationsDivider" + app:sessionsListHeaderDescription="" + app:sessionsListHeaderHasLearnMoreLink="false" + app:sessionsListHeaderTitle="@string/device_manager_current_session_title" /> <im.vector.app.features.settings.devices.v2.list.SessionInfoView android:id="@+id/deviceListCurrentSession" @@ -92,12 +92,12 @@ android:id="@+id/deviceListHeaderOtherSessions" android:layout_width="0dp" android:layout_height="wrap_content" - app:sessionsListHeaderDescription="@string/device_manager_sessions_other_description" - app:sessionsListHeaderTitle="@string/device_manager_sessions_other_title" - app:sessionsListHeaderHasLearnMoreLink="true" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/deviceListDividerCurrentSession" /> + app:layout_constraintTop_toBottomOf="@id/deviceListDividerCurrentSession" + app:sessionsListHeaderDescription="@string/device_manager_sessions_other_description" + app:sessionsListHeaderHasLearnMoreLink="false" + app:sessionsListHeaderTitle="@string/device_manager_sessions_other_title" /> <im.vector.app.features.settings.devices.v2.list.OtherSessionsView android:id="@+id/deviceListOtherSessions" From 20a24162f466a5ffe60314752e92605dc343268f Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Wed, 28 Sep 2022 14:00:45 +0200 Subject: [PATCH 070/187] Updating texts + other sessions list screen learn more --- .../src/main/res/values/strings.xml | 6 +++- .../v2/othersessions/OtherSessionsFragment.kt | 31 ++++++++++++++++++- .../v2/overview/SessionOverviewFragment.kt | 2 +- .../res/layout/fragment_other_sessions.xml | 7 +++-- 4 files changed, 40 insertions(+), 6 deletions(-) diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index a3c2e610be..7925ac02c4 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3302,9 +3302,13 @@ <string name="device_manager_session_rename_edit_hint">Session name</string> <string name="device_manager_session_rename_description">Custom session names can help you recognize your devices more easily.</string> <string name="device_manager_session_rename_warning">Please be aware that session names are also visible to people you communicate with.</string> + <string name="device_manager_learn_more_sessions_inactive_title">Inactive sessions</string> <string name="device_manager_learn_more_sessions_inactive">Inactive sessions are sessions you have not used in some time, but they continue to receive encryption keys.\n\nRemoving inactive sessions improves security and performance, and makes it easier for you to identify if a new session is suspicious.</string> + <string name="device_manager_learn_more_sessions_unverified_title">Unverified sessions</string> <string name="device_manager_learn_more_sessions_unverified">Unverified sessions are sessions that have logged in with your credentials but not been cross-verified.\n\nYou should make especially certain that you recognise these sessions as they could represent an unauthorised use of your account.</string> - <string name="device_manager_learn_more_session_verified">This session has logged in with your credentials and then been verified, either using your secure passphrase or by cross-verifying.\n\nThis means it holds encryption keys for your previous messages, and confirms to other users you are communicating with that the session is really you.</string> + <string name="device_manager_learn_more_sessions_verified_title">Verified sessions</string> + <string name="device_manager_learn_more_sessions_verified">Verified sessions have logged in with your credentials and then been verified, either using your secure passphrase or by cross-verifying.\n\nThis means they hold encryption keys for your previous messages, and confirm to other users you are communicating with that these sessions are really you.</string> + <string name="device_manager_learn_more_session_rename_title">Renaming sessions</string> <string name="device_manager_learn_more_session_rename">Other users in direct messages and rooms that you join are able to view a full list of your sessions.\n\nThis provides them with confidence that they are really speaking to you, but it also means they can see the session name you enter here.</string> <!-- Note to translators: %s will be replaces with selected space name --> diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt index 5734b04089..610776e22e 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt @@ -20,6 +20,7 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.annotation.StringRes import androidx.core.view.isVisible import com.airbnb.mvrx.Success import com.airbnb.mvrx.args @@ -37,6 +38,7 @@ import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterBott import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType import im.vector.app.features.settings.devices.v2.list.OtherSessionsView import im.vector.app.features.settings.devices.v2.list.SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS +import im.vector.app.features.settings.devices.v2.more.SessionLearnMoreBottomSheet import im.vector.app.features.themes.ThemeUtils import javax.inject.Inject @@ -121,6 +123,7 @@ class OtherSessionsFragment : ) ) views.otherSessionsNotFoundTextView.text = getString(R.string.device_manager_other_sessions_no_verified_sessions_found) + updateSecurityLearnMoreButton(R.string.device_manager_learn_more_sessions_verified_title, R.string.device_manager_learn_more_sessions_verified) } DeviceManagerFilterType.UNVERIFIED -> { views.otherSessionsSecurityRecommendationView.render( @@ -132,6 +135,10 @@ class OtherSessionsFragment : ) ) views.otherSessionsNotFoundTextView.text = getString(R.string.device_manager_other_sessions_no_unverified_sessions_found) + updateSecurityLearnMoreButton( + R.string.device_manager_learn_more_sessions_unverified_title, + R.string.device_manager_learn_more_sessions_unverified + ) } DeviceManagerFilterType.INACTIVE -> { views.otherSessionsSecurityRecommendationView.render( @@ -147,8 +154,10 @@ class OtherSessionsFragment : ) ) views.otherSessionsNotFoundTextView.text = getString(R.string.device_manager_other_sessions_no_inactive_sessions_found) + updateSecurityLearnMoreButton(R.string.device_manager_learn_more_sessions_inactive_title, R.string.device_manager_learn_more_sessions_inactive) + } + DeviceManagerFilterType.ALL_SESSIONS -> { /* NOOP. View is not visible */ } - DeviceManagerFilterType.ALL_SESSIONS -> { /* NOOP. View is not visible */ } } if (devices.isNullOrEmpty()) { @@ -161,6 +170,26 @@ class OtherSessionsFragment : } } + private fun updateSecurityLearnMoreButton( + @StringRes titleResId: Int, + @StringRes descriptionResId: Int, + ) { + views.otherSessionsSecurityRecommendationView.onLearnMoreClickListener = { + showLearnMoreInfo(titleResId, getString(descriptionResId)) + } + } + + private fun showLearnMoreInfo( + @StringRes titleResId: Int, + description: String, + ) { + val args = SessionLearnMoreBottomSheet.Args( + title = getString(titleResId), + description = description, + ) + SessionLearnMoreBottomSheet.show(childFragmentManager, args) + } + override fun onOtherSessionClicked(deviceId: String) { viewNavigator.navigateToSessionOverview( context = requireActivity(), diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt index 2b216b8436..8c3b907070 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt @@ -262,7 +262,7 @@ class SessionOverviewFragment : R.string.device_manager_verification_status_unverified } val descriptionResId = if (isVerified) { - R.string.device_manager_learn_more_session_verified + R.string.device_manager_learn_more_sessions_verified } else { R.string.device_manager_learn_more_sessions_unverified } diff --git a/vector/src/main/res/layout/fragment_other_sessions.xml b/vector/src/main/res/layout/fragment_other_sessions.xml index 037f85ad28..e25b8b185f 100644 --- a/vector/src/main/res/layout/fragment_other_sessions.xml +++ b/vector/src/main/res/layout/fragment_other_sessions.xml @@ -53,11 +53,12 @@ android:id="@+id/deviceListHeaderOtherSessions" android:layout_width="0dp" android:layout_height="wrap_content" - app:sessionsListHeaderDescription="@string/device_manager_sessions_other_description" - app:sessionsListHeaderTitle="" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/appBarLayout" /> + app:layout_constraintTop_toBottomOf="@id/appBarLayout" + app:sessionsListHeaderDescription="@string/device_manager_sessions_other_description" + app:sessionsListHeaderHasLearnMoreLink="false" + app:sessionsListHeaderTitle="" /> <im.vector.app.features.settings.devices.v2.othersessions.OtherSessionsSecurityRecommendationView android:id="@+id/otherSessionsSecurityRecommendationView" From 28dee84aee99ca1047a8d17efe8abe88298afa35 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Wed, 28 Sep 2022 14:01:09 +0200 Subject: [PATCH 071/187] Rename sessions screen learn more --- .../devices/v2/rename/RenameSessionFragment.kt | 17 +++++++++++++++++ .../main/res/layout/fragment_session_rename.xml | 3 ++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/rename/RenameSessionFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/rename/RenameSessionFragment.kt index df92bee100..2f671492e3 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/rename/RenameSessionFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/rename/RenameSessionFragment.kt @@ -24,9 +24,11 @@ import androidx.core.widget.doOnTextChanged import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.R import im.vector.app.core.extensions.showKeyboard import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentSessionRenameBinding +import im.vector.app.features.settings.devices.v2.more.SessionLearnMoreBottomSheet import javax.inject.Inject /** @@ -51,6 +53,7 @@ class RenameSessionFragment : initEditText() initSaveButton() initWithLastEditedName() + initInfoView() } private fun initToolbar() { @@ -75,6 +78,20 @@ class RenameSessionFragment : viewModel.handle(RenameSessionAction.InitWithLastEditedName) } + private fun initInfoView() { + views.renameSessionInfo.onLearnMoreClickListener = { + showLearnMoreInfo() + } + } + + private fun showLearnMoreInfo() { + val args = SessionLearnMoreBottomSheet.Args( + title = getString(R.string.device_manager_learn_more_session_rename_title), + description = getString(R.string.device_manager_learn_more_session_rename), + ) + SessionLearnMoreBottomSheet.show(childFragmentManager, args) + } + private fun observeViewEvents() { viewModel.observeViewEvents { when (it) { diff --git a/vector/src/main/res/layout/fragment_session_rename.xml b/vector/src/main/res/layout/fragment_session_rename.xml index 12b8af00f0..cbca907353 100644 --- a/vector/src/main/res/layout/fragment_session_rename.xml +++ b/vector/src/main/res/layout/fragment_session_rename.xml @@ -60,6 +60,7 @@ app:layout_constraintTop_toBottomOf="@id/renameSessionInputLayout" /> <im.vector.app.features.settings.devices.v2.SessionWarningInfoView + android:id="@+id/renameSessionInfo" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginHorizontal="@dimen/layout_horizontal_margin" @@ -68,6 +69,6 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/renameSessionDescription" app:sessionsWarningInfoDescription="@string/device_manager_session_rename_warning" - app:sessionsWarningInfoHasLearnMore="false" /> + app:sessionsWarningInfoHasLearnMore="true" /> </androidx.constraintlayout.widget.ConstraintLayout> From 48439f90713e42d6ba06d37b602b832cd645fe71 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Wed, 28 Sep 2022 15:05:38 +0200 Subject: [PATCH 072/187] Reverting remove of end of line from translations --- library/ui-strings/src/main/res/values-ca/strings.xml | 2 +- library/ui-strings/src/main/res/values-fa/strings.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/ui-strings/src/main/res/values-ca/strings.xml b/library/ui-strings/src/main/res/values-ca/strings.xml index 5af1f7b379..863fa13fbb 100644 --- a/library/ui-strings/src/main/res/values-ca/strings.xml +++ b/library/ui-strings/src/main/res/values-ca/strings.xml @@ -2674,4 +2674,4 @@ <string name="device_manager_verification_status_detail_other_session_verified">Aquesta sessió està llesta per a missatges segurs.</string> <string name="device_manager_verification_status_detail_current_session_verified">La teva sessió actual està llesta per a missatges segurs.</string> <string name="device_manager_verification_status_detail_current_session_unverified">Verifica la teva sessió actual obtenir missatges segurs millorats.</string> -</resources> +</resources> \ No newline at end of file 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 3a95348f0a..400a8121f9 100644 --- a/library/ui-strings/src/main/res/values-fa/strings.xml +++ b/library/ui-strings/src/main/res/values-fa/strings.xml @@ -2700,4 +2700,4 @@ <string name="labs_enable_deferred_dm_summary">ایجاد پیام خصوصی فقط در نخستین پیام</string> <string name="labs_enable_new_app_layout_summary">المنتی ساده شده با زبانههای انتخابی</string> <string name="labs_enable_new_app_layout_title">به کار انداختن چینش جدید</string> -</resources> +</resources> \ No newline at end of file From bd6b441b2a285ad51a58e21f7b721723a9c6adf9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Sep 2022 23:13:01 +0000 Subject: [PATCH 073/187] Bump epoxy from 4.6.2 to 5.0.0 Bumps `epoxy` from 4.6.2 to 5.0.0. Updates `epoxy` from 4.6.2 to 5.0.0 - [Release notes](https://github.com/airbnb/epoxy/releases) - [Changelog](https://github.com/airbnb/epoxy/blob/master/CHANGELOG.md) - [Commits](https://github.com/airbnb/epoxy/compare/4.6.2...5.0.0) Updates `epoxy-glide-preloading` from 4.6.2 to 5.0.0 - [Release notes](https://github.com/airbnb/epoxy/releases) - [Changelog](https://github.com/airbnb/epoxy/blob/master/CHANGELOG.md) - [Commits](https://github.com/airbnb/epoxy/compare/4.6.2...5.0.0) Updates `epoxy-processor` from 4.6.2 to 5.0.0 - [Release notes](https://github.com/airbnb/epoxy/releases) - [Changelog](https://github.com/airbnb/epoxy/blob/master/CHANGELOG.md) - [Commits](https://github.com/airbnb/epoxy/compare/4.6.2...5.0.0) Updates `epoxy-paging` from 4.6.2 to 5.0.0 - [Release notes](https://github.com/airbnb/epoxy/releases) - [Changelog](https://github.com/airbnb/epoxy/blob/master/CHANGELOG.md) - [Commits](https://github.com/airbnb/epoxy/compare/4.6.2...5.0.0) --- updated-dependencies: - dependency-name: com.airbnb.android:epoxy dependency-type: direct:production update-type: version-update:semver-major - dependency-name: com.airbnb.android:epoxy-glide-preloading dependency-type: direct:production update-type: version-update:semver-major - dependency-name: com.airbnb.android:epoxy-processor dependency-type: direct:production update-type: version-update:semver-major - dependency-name: com.airbnb.android:epoxy-paging dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index f4165ad692..677e3d8a36 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -20,7 +20,7 @@ def moshi = "1.14.0" def lifecycle = "2.5.1" def flowBinding = "1.2.0" def flipper = "0.164.0" -def epoxy = "4.6.2" +def epoxy = "5.0.0" def mavericks = "2.7.0" def glide = "4.13.2" def bigImageViewer = "1.8.1" From 169170d39838ef3712add232f4eb513048feafbc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Sep 2022 23:15:14 +0000 Subject: [PATCH 074/187] Bump kotlin-gradle-plugin from 1.7.10 to 1.7.20 Bumps [kotlin-gradle-plugin](https://github.com/JetBrains/kotlin) from 1.7.10 to 1.7.20. - [Release notes](https://github.com/JetBrains/kotlin/releases) - [Changelog](https://github.com/JetBrains/kotlin/blob/v1.7.20/ChangeLog.md) - [Commits](https://github.com/JetBrains/kotlin/compare/v1.7.10...v1.7.20) --- 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] <support@github.com> --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index f4165ad692..390aa110bf 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -9,7 +9,7 @@ ext.versions = [ def gradle = "7.2.2" // Ref: https://kotlinlang.org/releases.html -def kotlin = "1.7.10" +def kotlin = "1.7.20" def kotlinCoroutines = "1.6.4" def dagger = "2.43.2" def appDistribution = "16.0.0-beta04" From 1db669d1c28eee662abda6f7bba6e094de99f9cd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Sep 2022 23:21:58 +0000 Subject: [PATCH 075/187] Bump kotlin-reflect from 1.7.10 to 1.7.20 Bumps [kotlin-reflect](https://github.com/JetBrains/kotlin) from 1.7.10 to 1.7.20. - [Release notes](https://github.com/JetBrains/kotlin/releases) - [Changelog](https://github.com/JetBrains/kotlin/blob/v1.7.20/ChangeLog.md) - [Commits](https://github.com/JetBrains/kotlin/compare/v1.7.10...v1.7.20) --- updated-dependencies: - dependency-name: org.jetbrains.kotlin:kotlin-reflect dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> --- 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 a4bc105a1d..7dcd6a648e 100644 --- a/vector-app/build.gradle +++ b/vector-app/build.gradle @@ -410,7 +410,7 @@ dependencies { androidTestImplementation libs.mockk.mockkAndroid androidTestUtil libs.androidx.orchestrator androidTestImplementation libs.androidx.fragmentTesting - androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:1.7.10" + androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:1.7.20" debugImplementation libs.androidx.fragmentTesting debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1' } diff --git a/vector/build.gradle b/vector/build.gradle index ff0d907212..f7f8a659a3 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -317,5 +317,5 @@ dependencies { androidTestImplementation libs.mockk.mockkAndroid androidTestUtil libs.androidx.orchestrator debugImplementation libs.androidx.fragmentTesting - androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:1.7.10" + androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:1.7.20" } From f5cb980020cee44a0d482af1c873703926ab50fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= <jorgem@element.io> Date: Fri, 30 Sep 2022 09:39:01 +0200 Subject: [PATCH 076/187] Fix dependencies and code issues --- dependencies_groups.gradle | 2 ++ .../java/org/billcarsonfr/jsonviewer/JSonViewerFragment.kt | 2 +- vector/build.gradle | 7 ------- .../attachments/preview/AttachmentsPreviewFragment.kt | 4 ++-- .../app/features/home/room/detail/TimelineFragment.kt | 4 ++-- .../vector/app/features/home/room/list/RoomListFragment.kt | 2 +- .../features/home/room/list/home/HomeRoomListFragment.kt | 2 +- .../features/pin/lockscreen/biometrics/BiometricHelper.kt | 2 +- .../VectorSettingsNotificationsTroubleshootFragment.kt | 2 +- .../app/features/spaces/manage/SpaceAddRoomFragment.kt | 2 +- 10 files changed, 12 insertions(+), 17 deletions(-) diff --git a/dependencies_groups.gradle b/dependencies_groups.gradle index a97d80bc7f..61ab038b6e 100644 --- a/dependencies_groups.gradle +++ b/dependencies_groups.gradle @@ -47,6 +47,7 @@ ext.groups = [ ], mavenCentral: [ regex: [ + 'com\\.google\\.auto\\.*', ], group: [ 'app.cash.paparazzi', @@ -101,6 +102,7 @@ ext.groups = [ 'com.googlecode.json-simple', 'com.googlecode.libphonenumber', 'com.ibm.icu', + 'com.intellij', 'com.jakewharton.android.repackaged', 'com.jakewharton.timber', 'com.kgurgul.flipper', diff --git a/library/external/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerFragment.kt b/library/external/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerFragment.kt index fbf6f88bc3..719ce29045 100644 --- a/library/external/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerFragment.kt +++ b/library/external/jsonviewer/src/main/java/org/billcarsonfr/jsonviewer/JSonViewerFragment.kt @@ -62,7 +62,7 @@ class JSonViewerFragment : Fragment(), MavericksView { } recyclerView = inflate.findViewById(R.id.jvRecyclerView) recyclerView.layoutManager = - LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) + LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false) recyclerView.setController(epoxyController) epoxyController.setStyle(args?.styleProvider) registerForContextMenu(recyclerView) diff --git a/vector/build.gradle b/vector/build.gradle index ff0d907212..c88f81857a 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -17,13 +17,6 @@ static def gitRevision() { return cmd.execute().text.trim() } -project.android.buildTypes.all { buildType -> - buildType.javaCompileOptions.annotationProcessorOptions.arguments = - [ - validateEpoxyModelUsage: String.valueOf(buildType.name == 'debug') - ] -} - initScreenshotTests(project) android { 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 20b155d11e..e7ab8c9804 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 @@ -207,13 +207,13 @@ class AttachmentsPreviewFragment : attachmentMiniaturePreviewController.callback = this views.attachmentPreviewerMiniatureList.let { - it.layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) + it.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false) it.setHasFixedSize(true) it.adapter = attachmentMiniaturePreviewController.adapter } views.attachmentPreviewerBigList.let { - it.layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) + it.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false) it.attachSnapHelperWithListener( PagerSnapHelper(), SnapOnScrollListener.Behavior.NOTIFY_ON_SCROLL_STATE_IDLE, 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 bba607eeb4..7bbdc431b9 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 @@ -1434,8 +1434,8 @@ class TimelineFragment : timelineEventController.timeline = timelineViewModel.timeline views.timelineRecyclerView.trackItemsVisibilityChange() - layoutManager = object : LinearLayoutManager(context, RecyclerView.VERTICAL, true) { - override fun onLayoutCompleted(state: RecyclerView.State?) { + layoutManager = object : LinearLayoutManager(requireContext(), RecyclerView.VERTICAL, true) { + override fun onLayoutCompleted(state: RecyclerView.State) { super.onLayoutCompleted(state) updateJumpToReadMarkerViewVisibility() jumpToBottomViewVisibilityManager.maybeShowJumpToBottomViewVisibilityWithDelay() diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt index 9591048725..970b1c34e1 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt @@ -285,7 +285,7 @@ class RoomListFragment : } private fun setupRecyclerView() { - val layoutManager = LinearLayoutManager(context) + val layoutManager = LinearLayoutManager(requireContext()) stateRestorer = LayoutManagerStateRestorer(layoutManager).register() views.roomListView.layoutManager = layoutManager views.roomListView.itemAnimator = RoomListAnimator() diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt index 5677f3e4a8..d8c71e3e17 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt @@ -137,7 +137,7 @@ class HomeRoomListFragment : private fun setupRecyclerView() { views.stateView.state = StateView.State.Content - val layoutManager = LinearLayoutManager(context) + val layoutManager = LinearLayoutManager(requireContext()) firstItemObserver = FirstItemUpdatedObserver(layoutManager) { layoutManager.scrollToPosition(0) } diff --git a/vector/src/main/java/im/vector/app/features/pin/lockscreen/biometrics/BiometricHelper.kt b/vector/src/main/java/im/vector/app/features/pin/lockscreen/biometrics/BiometricHelper.kt index 026ee159ed..bf2075d3a8 100644 --- a/vector/src/main/java/im/vector/app/features/pin/lockscreen/biometrics/BiometricHelper.kt +++ b/vector/src/main/java/im/vector/app/features/pin/lockscreen/biometrics/BiometricHelper.kt @@ -20,7 +20,7 @@ import android.content.Context import android.os.Build import androidx.annotation.MainThread import androidx.annotation.VisibleForTesting -import androidx.annotation.VisibleForTesting.PRIVATE +import androidx.annotation.VisibleForTesting.Companion.PRIVATE import androidx.biometric.BiometricManager import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_WEAK diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationsTroubleshootFragment.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationsTroubleshootFragment.kt index 137f1c8722..9fc55d14aa 100644 --- a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationsTroubleshootFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationsTroubleshootFragment.kt @@ -65,7 +65,7 @@ class VectorSettingsNotificationsTroubleshootFragment : override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - val layoutManager = LinearLayoutManager(context) + val layoutManager = LinearLayoutManager(requireContext()) views.troubleshootTestRecyclerView.layoutManager = layoutManager val dividerItemDecoration = DividerItemDecoration(view.context, layoutManager.orientation) diff --git a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomFragment.kt index d0115d561a..edc18a8816 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/manage/SpaceAddRoomFragment.kt @@ -214,7 +214,7 @@ class SpaceAddRoomFragment : roomEpoxyController.submitList(it) } listenItemCount(viewModel.roomCountFlow) { roomEpoxyController.totalSize = it } - views.roomList.layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) + views.roomList.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false) views.roomList.setHasFixedSize(true) } From 570bc57cc684bbc453c0675fdc651da8a0e66eea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= <jorgem@element.io> Date: Fri, 30 Sep 2022 09:44:23 +0200 Subject: [PATCH 077/187] Restore `validateEpoxyModelUsage` annotation processing arg --- vector/build.gradle | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/vector/build.gradle b/vector/build.gradle index c88f81857a..ff0d907212 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -17,6 +17,13 @@ static def gitRevision() { return cmd.execute().text.trim() } +project.android.buildTypes.all { buildType -> + buildType.javaCompileOptions.annotationProcessorOptions.arguments = + [ + validateEpoxyModelUsage: String.valueOf(buildType.name == 'debug') + ] +} + initScreenshotTests(project) android { From 9cb70516941e8e69e3416ae3cd8181ba7244bc8e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 Sep 2022 23:09:33 +0000 Subject: [PATCH 078/187] Bump glide from 4.13.2 to 4.14.1 Bumps `glide` from 4.13.2 to 4.14.1. Updates `glide` from 4.13.2 to 4.14.1 - [Release notes](https://github.com/bumptech/glide/releases) - [Commits](https://github.com/bumptech/glide/compare/v4.13.2...v4.14.1) Updates `compiler` from 4.13.2 to 4.14.1 - [Release notes](https://github.com/bumptech/glide/releases) - [Commits](https://github.com/bumptech/glide/compare/v4.13.2...v4.14.1) --- updated-dependencies: - dependency-name: com.github.bumptech.glide:glide dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: com.github.bumptech.glide:compiler dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index f4165ad692..86dd382aef 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -22,7 +22,7 @@ def flowBinding = "1.2.0" def flipper = "0.164.0" def epoxy = "4.6.2" def mavericks = "2.7.0" -def glide = "4.13.2" +def glide = "4.14.1" def bigImageViewer = "1.8.1" def jjwt = "0.11.5" // Temporary version to unblock #6929. Once 0.16.0 is released we should use it, and revert From fef2c8519a215d580df10c63015de667a3c356b8 Mon Sep 17 00:00:00 2001 From: Vri <element@vrifox.cc> Date: Wed, 28 Sep 2022 15:20:13 +0000 Subject: [PATCH 079/187] Translated using Weblate (German) Currently translated at 100.0% (2419 of 2419 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/de/ --- .../src/main/res/values-de/strings.xml | 162 +++++++++--------- 1 file changed, 81 insertions(+), 81 deletions(-) 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 5154809f72..a7c5d7530e 100644 --- a/library/ui-strings/src/main/res/values-de/strings.xml +++ b/library/ui-strings/src/main/res/values-de/strings.xml @@ -103,7 +103,7 @@ <string name="notice_room_avatar_changed_by_you">Du hast das Bild des Raumes geändert</string> <string name="notice_room_name_changed_by_you">Du hast den Raumnamen zu %1$s geändert</string> <string name="notice_placed_video_call_by_you">Du hast einen Videoanruf gestartet.</string> - <string name="notice_placed_voice_call_by_you">Du hast einen Audioanruf gestartet.</string> + <string name="notice_placed_voice_call_by_you">Du hast einen Sprachanruf gestartet.</string> <string name="notice_answered_call_by_you">Du hast den Anruf angenommen.</string> <string name="notice_ended_call_by_you">Du hast den Anruf beendet.</string> <string name="notice_made_future_room_visibility_by_you">Du hast den zukünftigen Nachrichtenverlauf für %1$s sichtbar gemacht</string> @@ -278,7 +278,7 @@ <string name="join_room">Raum betreten</string> <string name="username">Benutzername</string> <string name="logout">Abmelden</string> - <string name="hs_url">Heimserver-Adresse</string> + <string name="hs_url">Heim-Server-Adresse</string> <string name="search">Suchen</string> <string name="start_voice_call">Sprachanruf starten</string> <string name="start_video_call">Videoanruf starten</string> @@ -308,7 +308,7 @@ <string name="call_error_user_not_responding">Die Gegenseite hat den Anruf nicht angenommen.</string> <string name="permissions_rationale_popup_title">Information</string> <string name="permissions_rationale_msg_record_audio">${app_name} benötigt die Berechtigung, auf dein Mikrofon zugreifen zu können, um (Sprach-)Anrufe tätigen zu können.</string> - <string name="permissions_rationale_msg_camera_and_audio">${app_name} benötigt die Berechtigung, auf Kamera und Mikrofon zu zugreifen, um Video-Anrufe durchzuführen. + <string name="permissions_rationale_msg_camera_and_audio">${app_name} benötigt die Berechtigung, auf Kamera und Mikrofon zuzugreifen, um Videoanrufe durchzuführen. \n \nBitte erlaube den Zugriff im nächsten Dialog, um den Anruf durchzuführen.</string> <string name="yes">Ja</string> @@ -351,8 +351,8 @@ <string name="settings_display_name">Anzeigename</string> <string name="settings_add_email_address">E-Mail-Adresse hinzufügen</string> <string name="settings_add_phone_number">Telefonnummer hinzufügen</string> - <string name="settings_app_info_link_summary">Appinfo in den Systemeinstellungen öffnen.</string> - <string name="settings_app_info_link_title">App-Info</string> + <string name="settings_app_info_link_summary">Anwendungsinformationen in den Systemeinstellungen anzeigen.</string> + <string name="settings_app_info_link_title">Anwendungsinformationen</string> <string name="settings_enable_all_notif">Benachrichtigungen für diesen Account</string> <string name="settings_enable_this_device">Benachrichtigungen für diese Sitzung</string> <string name="settings_messages_in_one_to_one">Direktnachrichten</string> @@ -366,7 +366,7 @@ <string name="settings_version">Version</string> <string name="settings_olm_version">OLM-Version</string> <string name="settings_app_term_conditions">Nutzungsbedingungen</string> - <string name="settings_third_party_notices">Nutzungshinweise von Drittanbietern</string> + <string name="settings_third_party_notices">Drittanbieter-Lizenzen</string> <string name="settings_copyright">Urheberrechtserklärung</string> <string name="settings_privacy_policy">Datenschutzerklärung</string> <string name="settings_clear_cache">Cache leeren</string> @@ -390,8 +390,8 @@ <string name="devices_details_last_seen_format">%1$s @ %2$s</string> <string name="devices_delete_dialog_title">Authentifizierung</string> <string name="settings_logged_in">Angemeldet als</string> - <string name="settings_home_server">Heimserver</string> - <string name="settings_identity_server">Identitätsserver</string> + <string name="settings_home_server">Heim-Server</string> + <string name="settings_identity_server">Identitäts-Server</string> <string name="account_email_validation_message">Bitte prüfe deinen E-Mail-Posteingang und klicke auf den in der E-Mail enthaltenen Link. Klicke anschließend auf Fortsetzen.</string> <string name="account_email_already_used_error">Diese E-Mail-Adresse wird bereits verwendet.</string> <string name="account_phone_number_already_used_error">Diese Telefonnummer wird bereits verwendet.</string> @@ -412,8 +412,8 @@ <string name="room_settings_banned_users_title">Verbannte Benutzer</string> <string name="room_settings_category_advanced_title">Erweitert</string> <string name="room_settings_room_internal_id">Interne ID dieses Raumes</string> - <string name="room_settings_labs_pref_title">Experimentelle Einstellungen</string> - <string name="room_settings_labs_warning_message">Dies sind experimentelle Funktionen, die in unerwarteter Weise Fehler verursachen können. Mit Vorsicht zu verwenden.</string> + <string name="room_settings_labs_pref_title">Labor</string> + <string name="room_settings_labs_warning_message">Dies sind experimentelle Funktionen, die in unerwarteter Weise Fehler verursachen können. Verwende sie mit Vorsicht.</string> <string name="room_settings_set_main_address">Als Hauptadresse setzen</string> <string name="room_settings_unset_main_address">Als Hauptadresse aufheben</string> <string name="encryption_information_decryption_error">Entschlüsselungsfehler</string> @@ -551,9 +551,9 @@ <string name="dialog_user_consent_content">Um %1$s weiter zu verwenden, musst die Geschäftsbedingungen begutachten und ihnen zustimmen.</string> <string name="dialog_user_consent_submit">Jetzt prüfen</string> <string name="deactivate_account_title">Konto deaktivieren</string> - <string name="deactivate_account_content">Dies wird dein Konto permanent unbenutzbar machen. Du wirst dich nicht anmelden können und keiner wird denselben Nutzernamen erneut registrieren können. Du verlässt automatisch alle Räume, in denen du bist, und deine Kontoangaben werden vom Identitätsserver gelöscht. <b>Diese Aktion ist unumkehrbar</b>. + <string name="deactivate_account_content">Dies wird dein Konto permanent unbenutzbar machen. Du wirst dich nicht anmelden können und keiner wird denselben Nutzernamen erneut registrieren können. Du verlässt automatisch alle Räume, in denen du bist, und deine Kontoangaben werden vom Identitäts-Server gelöscht. <b>Diese Aktion ist unumkehrbar</b>. \n -\nDie Deaktivierung deines Konto wird standardmäßig <b>keine deiner gesendeten Nachrichten löschen</b>. Wenn du möchtest, dass auch deine Nachrichten gelöscht werden, wähle zusätzlich die Option unten. +\nDie Deaktivierung deines Kontos wird standardmäßig <b>keine deiner gesendeten Nachrichten löschen</b>. Wenn du möchtest, dass auch deine Nachrichten gelöscht werden, wähle zusätzlich die Option unten. \n \nDie Sichtbarkeit deiner Nachrichten ist ähnlich wie bei E-Mails: Wenn deine Nachrichten gelöscht werden, bedeutet dies, dass von dir verschickte Nachrichten nicht mit neuen oder unregistrierten Nutzer geteilt werden. Aber registrierte Nutzer, die bereits Zugang zu diesen Nachrichten haben, behalten weiterhin Zugriff auf ihre Kopie.</string> <string name="deactivate_account_delete_checkbox">Bitte alle Nachrichten, die ich gesendet habe, löschen, wenn mein Konto deaktiviert wird (Warnung: Unterhaltungen werden für zukünftige Nutzer unvollständig erscheinen)</string> @@ -614,7 +614,7 @@ <string name="settings_show_read_receipts_summary">Klicke auf die Lesebestätigungen für eine detailliertere Liste.</string> <string name="settings_show_join_leave_messages_summary">Einladungen, Entfernungen und Verbannungen bleiben sichtbar.</string> <string name="settings_password">Passwort</string> - <string name="settings_labs_native_camera_summary">Starte die System-Kamera anstelle der angepassten Kamera.</string> + <string name="settings_labs_native_camera_summary">Starte die Kamera des Systems anstelle der selbstdefinierten.</string> <string name="command_problem_with_parameters">Das Kommando \"%s\" braucht mehr Parameter oder einige Parameter sind inkorrekt.</string> <string name="markdown_has_been_enabled">Markdown wurde aktiviert.</string> <string name="markdown_has_been_disabled">Markdown wurde deaktiviert.</string> @@ -934,7 +934,7 @@ <string name="settings_background_fdroid_sync_mode_disabled">Keine Hintergrundsynchronisation</string> <string name="settings_discovery_category">Auffindbarkeit</string> <string name="widget_integration_review_terms">Um fortzufahren, musst du die Nutzungsbedingungen akzeptieren.</string> - <string name="identity_server_not_defined">Du verwendest keinen Identitätsserver</string> + <string name="identity_server_not_defined">Du verwendest keinen Identitäts-Server</string> <string name="error_user_already_logged_in">Du versuchst anscheinend, eine Verbindung zu einem anderen Homeserver herzustellen. Möchtest du dich abmelden\?</string> <string name="push_gateway_item_push_key">Push-Key:</string> <string name="push_gateway_item_app_display_name">App-Anzeigename:</string> @@ -942,13 +942,13 @@ <string name="terms_of_service">Nutzungsbedingungen</string> <string name="terms_description_for_identity_server">Für andere auffindbar sein</string> <string name="terms_description_for_integration_manager">Verwende Bots, Bridges, Widgets und Sticker-Pakete</string> - <string name="identity_server">Identitätsserver</string> - <string name="disconnect_identity_server">Verbindung zum Identitätsserver trennen</string> - <string name="add_identity_server">Identitätsserver konfigurieren</string> - <string name="change_identity_server">Identitätsserver ändern</string> + <string name="identity_server">Identitäts-Server</string> + <string name="disconnect_identity_server">Verbindung zum Identitäts-Server trennen</string> + <string name="add_identity_server">Identitäts-Server konfigurieren</string> + <string name="change_identity_server">Identitäts-Server ändern</string> <string name="settings_discovery_emails_title">Auffindbare E-Mail-Adressen</string> <string name="settings_discovery_no_mails">Erkennungsoptionen werden angezeigt, sobald du eine E-Mail hinzugefügt hast.</string> - <string name="settings_discovery_enter_identity_server">Gib einen neuen Identitätsserver ein</string> + <string name="settings_discovery_enter_identity_server">Gib eine Identitäts-Server-Adresse ein</string> <string name="settings_discovery_bad_identity_server">Konnte keine Verbindung zum Homeserver herstellen</string> <string name="login_error_no_homeserver_found">Dies ist keine Adresse eines Matrixservers</string> <string name="login_error_homeserver_not_found">Kann Homeserver nicht unter dieser URL erreichen. Bitte überprüfen</string> @@ -986,15 +986,15 @@ <string name="push_gateway_item_device_name">Sitzungsname:</string> <string name="push_gateway_item_format">Format:</string> <string name="settings_discovery_identity_server_info">Du nutzt aktuell %1$s um vorhandene Kontakte zu finden und um von dir bekannten Kontakten gefunden zu werden.</string> - <string name="settings_discovery_identity_server_info_none">Du benutzt aktuell keinen Identitätsserver. Um zu entdecken und um von dir bekannten Kontakten entdeckt zu werden, richte unten einen ein.</string> + <string name="settings_discovery_identity_server_info_none">Aktuell nutzt du keinen Identitäts-Server. Richte einen ein, um andere zu finden und selbst auffindbar zu sein.</string> <string name="settings_discovery_msisdn_title">Auffindbare Telefonnummern</string> - <string name="settings_discovery_please_enter_server">Bitte gib die Adresse des Identitätsservers ein</string> - <string name="settings_discovery_no_terms_title">Identitätsserver hat keine Nutzungsbedingungen</string> - <string name="settings_discovery_no_terms">Der Identitätsserver den du ausgewählt hast, hat keine Nutzungsbedingungen. Fahre nur fort, wenn du dem Besitzer des Dienstes vertraust</string> + <string name="settings_discovery_please_enter_server">Bitte gib die Adresse des Identitäts-Servers ein</string> + <string name="settings_discovery_no_terms_title">Identitäts-Server hat keine Nutzungsbedingungen</string> + <string name="settings_discovery_no_terms">Der Identitäts-Server, den du ausgewählt hast, hat keine Nutzungsbedingungen. Fahre nur fort, wenn du den Betreibenden des Dienstes vertraust</string> <string name="settings_text_message_sent">Eine Textnachricht wurde an %s gesendet. Bitte gib den Verifizierungscode ein, den sie enthält.</string> <string name="labs_allow_extended_logging">Aktiviere ausführliche Logs.</string> - <string name="labs_allow_extended_logging_summary">Ausführliche Logs werden der Entwicklung der App dadurch helfen, dass mehr Informationen übertragen werden, wenn du einen Fehlerbericht sendest. Auch wenn dies aktiviert ist, werden keine Nachrichteninhalte oder andere privaten Daten aufgezeichnet.</string> - <string name="error_terms_not_accepted">Bitte erneut versuchen, nachdem du die Nutzungsbedingungen deines Heimservers akzeptiert hast.</string> + <string name="labs_allow_extended_logging_summary">Ausführliche Protokolle werden bei der Entwicklung der App helfen. Auch wenn dies aktiviert ist, werden keine Nachrichteninhalte oder andere privaten Daten aufgezeichnet.</string> + <string name="error_terms_not_accepted">Bitte erneut versuchen, nachdem du die Nutzungsbedingungen deines Heim-Servers akzeptiert hast.</string> <string name="room_widget_permission_webview_shared_info_title">Bei Benutzung könnten Cookies gesetzt werden und es könnten Daten mit %s geteilt werden:</string> <string name="room_widget_permission_shared_info_title">Bei Benutzung könnten Daten mit %s geteilt werden:</string> <string name="settings_discovery_no_msisdn">Optionen zum Finden werden erscheinen, sobald du eine Telefonnummer hinzugefügt hast.</string> @@ -1063,9 +1063,9 @@ <string name="login_server_other_title">Andere</string> <string name="login_server_other_text">Benutzerdefinierte und erweiterte Einstellungen</string> <string name="login_continue">Fortfahren</string> - <string name="settings_discovery_disconnect_identity_server_info">Eine Trennung von deinem Identitätsserver würde bedeuten, dass du weder von anderen Nutzern gefunden werden, noch diese per E-Mail oder Telefonnummer einladen kannst.</string> - <string name="settings_discovery_disconnect_with_bound_pid">Du teilst deine E-Mail-Adressen oder Telefonnummern momentan auf dem Identitätsserver %1$s. Du wirst dich erneut mit %2$s verbinden müssen, um mit dem Teilen aufzuhören.</string> - <string name="settings_agree_to_terms">Stimme den Nutzungsbedingungen des Identitätsservers (%s) zu, um zu erlauben per E-Mail oder Telefonnummer gefunden zu werden.</string> + <string name="settings_discovery_disconnect_identity_server_info">Eine Trennung von deinem Identitäts-Server würde bedeuten, dass du weder von anderen gefunden werden, noch diese per E-Mail oder Telefonnummer einladen kannst.</string> + <string name="settings_discovery_disconnect_with_bound_pid">Du teilst deine E-Mail-Adressen oder Telefonnummern momentan auf dem Identitäts-Server %1$s. Du wirst dich erneut mit %2$s verbinden müssen, um mit dem Teilen aufzuhören.</string> + <string name="settings_agree_to_terms">Stimme den Nutzungsbedingungen des Identitäts-Servers (%s) zu, um per E-Mail-Adresse oder Telefonnummer auffindbar zu sein zu können.</string> <string name="error_handling_incoming_share">Zu teilende Daten nicht verarbeitbar</string> <string name="login_splash_text3">Erweitere und individualisiere dein Benutzererlebnis</string> <string name="login_connect_to">Mit %1$s verbinden</string> @@ -1272,21 +1272,21 @@ <string name="verification_code_notice">Vergleiche den Code mit dem Code auf dem Bildschirm deines Gegenübers.</string> <string name="verification_conclusion_ok_notice">Nachrichten mit diesem Gegenüber sind Ende-zu-Ende-verschlüsselt und können nicht von Dritten gelesen werden.</string> <string name="verification_conclusion_ok_self_notice">Deine neue Sitzung ist jetzt verifiziert. Sie hat Zugriff auf deine verschlüsselten Nachrichten, und andere Benutzer sehen sie als vertrauenswürdig an.</string> - <string name="encryption_information_cross_signing_state">Cross-Signing</string> - <string name="encryption_information_dg_xsigning_complete">Cross-Signing ist aktiviert -\nPrivate Schlüssel auf dem Gerät.</string> - <string name="encryption_information_dg_xsigning_trusted">Cross-Signing ist aktiviert + <string name="encryption_information_cross_signing_state">Quersignierung</string> + <string name="encryption_information_dg_xsigning_complete">Quersignierung ist aktiviert, +\nprivate Schlüssel auf dem Gerät.</string> + <string name="encryption_information_dg_xsigning_trusted">Quersignierung ist aktiviert, \nSchlüssel sind vertrauenswürdig. \nPrivate Schlüssel sind nicht bekannt</string> - <string name="encryption_information_dg_xsigning_not_trusted">Cross-Signing ist aktiviert + <string name="encryption_information_dg_xsigning_not_trusted">Quersignierung ist aktiviert, \nSchlüssel sind nicht vertrauenswürdig</string> - <string name="encryption_information_dg_xsigning_disabled">Cross-Signing ist nicht aktiviert</string> + <string name="encryption_information_dg_xsigning_disabled">Quersignierung ist nicht aktiviert</string> <string name="settings_active_sessions_list">Aktive Sitzungen</string> <string name="settings_active_sessions_show_all">Alle Sitzungen anzeigen</string> <string name="settings_active_sessions_manage">Sitzungen verwalten</string> <string name="settings_active_sessions_signout_device">Diese Sitzung abmelden</string> <string name="settings_failed_to_get_crypto_device_info">Keine kryptografischen Informationen verfügbar</string> - <string name="settings_active_sessions_verified_device_desc">Diese Sitzung ist für sichere Nachrichtenübertragung vertrauenswürdig, da du sie überprüft hast:</string> + <string name="settings_active_sessions_verified_device_desc">Diese Sitzung ist für sichere Kommunikation vertrauenswürdig, da du sie überprüft hast:</string> <string name="settings_active_sessions_unverified_device_desc">Verifiziere diese Sitzung, um sie als vertrauenswürdig zu markieren, und gewähren ihr Zugriff auf verschlüsselte Nachrichten. Wenn du dich nicht bei dieser Sitzung angemeldet hast, ist dein Konto möglicherweise gefährdet:</string> <plurals name="settings_active_sessions_count"> <item quantity="one">Eine aktive Sitzung</item> @@ -1301,10 +1301,10 @@ <string name="room_member_profile_sessions_section_title">Sitzungen</string> <string name="trusted">Vertraut</string> <string name="not_trusted">Nicht vertraut</string> - <string name="verification_profile_device_verified_because">Diese Sitzung ist für sichere Nachrichtenübertragung vertrauenswürdig, weil %1$s (%2$s) sie verifiziert hat:</string> + <string name="verification_profile_device_verified_because">Diese Sitzung ist für sichere Kommunikation vertrauenswürdig, weil %1$s (%2$s) sie verifiziert hat:</string> <string name="verification_profile_device_new_signing">%1$s (%2$s) hat sich in einer neuen Sitzung angemeldet:</string> <string name="verification_profile_device_untrust_info">Bis dieser Benutzer dieser Sitzung vertraut, werden an und von ihm gesendete Nachrichten mit Warnungen gekennzeichnet. Alternativ kannst du dies manuell überprüfen.</string> - <string name="initialize_cross_signing">Initialisiere Cross-Signing</string> + <string name="initialize_cross_signing">Quersignierung initialisieren</string> <string name="reset_cross_signing">Schlüssel zurücksetzen</string> <string name="a11y_qr_code_for_verification">QR-Code</string> <string name="qr_code_scanned_by_other_notice">Fast geschafft! Zeigt %s ein Häkchen\?</string> @@ -1461,7 +1461,7 @@ <string name="action_dismiss">Ablehnen</string> <string name="dialog_title_success">Erfolg</string> <string name="call_failed_no_connection_description">Echtzeitverbindung konnte nicht hergestellt werden. -\nBitte den Administrator deines Heimservers, einen TURN-Server zu konfigurieren, damit Anrufe zuverlässig funktionieren.</string> +\nBitte den Administrator deines Heim-Servers, einen TURN-Server zu konfigurieren, damit Anrufe zuverlässig funktionieren.</string> <string name="call_select_sound_device">Audiogerät auswählen</string> <string name="sound_device_phone">Telefon</string> <string name="sound_device_speaker">Lautsprecher</string> @@ -1555,20 +1555,20 @@ <string name="choose_locale_other_locales_title">Andere verfügbare Sprachen</string> <string name="choose_locale_loading_locales">Lade verfügbare Sprachen…</string> <string name="open_terms_of">Öffne AGBs von %s</string> - <string name="disconnect_identity_server_dialog_content">Trenne Verbindung zu Identitätsserver %s\?</string> - <string name="identity_server_error_outdated_identity_server">Dieser Identitätsserver ist veraltet. ${app_name} unterstützt nur API V2.</string> + <string name="disconnect_identity_server_dialog_content">Verbindung zu Identitäts-Server %s trennen\?</string> + <string name="identity_server_error_outdated_identity_server">Dieser Identitäts-Server ist veraltet. ${app_name} unterstützt nur API V2.</string> <string name="identity_server_error_outdated_home_server">Diese Operation ist nicht möglich. Der Homeserver ist veraltet.</string> - <string name="identity_server_error_no_identity_server_configured">Bitte konfiguriere zuerst einen Identitätsserver.</string> - <string name="identity_server_error_terms_not_signed">Bitte akzeptiere zuerst die AGB des Identitätsservers in den Einstellungen.</string> + <string name="identity_server_error_no_identity_server_configured">Bitte konfiguriere zuerst einen Identitäts-Server.</string> + <string name="identity_server_error_terms_not_signed">Bitte akzeptiere zuerst die AGB des Identitäts-Servers in den Einstellungen.</string> <string name="identity_server_error_bulk_sha256_not_supported">Deiner Privatsphäre wegen unterstützt ${app_name} nur das Senden gehashter E-Mail-Adressen und Telefonnummern.</string> <string name="identity_server_error_binding_error">Die Assoziierung ist fehlgeschlagen.</string> <string name="identity_server_error_no_current_binding_error">Für diese Kennung gibt es aktuell keine Zuordnung.</string> - <string name="identity_server_set_default_notice">Dein Homeserver (%1$s) schlägt %2$s als Identitätsserver vor</string> + <string name="identity_server_set_default_notice">Dein Heim-Server (%1$s) schlägt %2$s als Identitäts-Server vor</string> <string name="identity_server_set_default_submit">Benutze %1$s</string> - <string name="identity_server_set_alternative_notice">Alternativ kannst du die URL eines beliebigen anderen Identitätsservers angeben</string> - <string name="identity_server_set_alternative_notice_no_default">Gib die URL von einem Identitätsserver ein</string> - <string name="identity_server_set_alternative_submit">Bestätigen</string> - <string name="power_level_edit_title">Lege Rolle fest</string> + <string name="identity_server_set_alternative_notice">Alternativ kannst du die URL eines beliebigen anderen Identitäts-Servers angeben</string> + <string name="identity_server_set_alternative_notice_no_default">Gib die Adresse eines Identitäts-Servers ein</string> + <string name="identity_server_set_alternative_submit">Absenden</string> + <string name="power_level_edit_title">Rolle festlegen</string> <string name="power_level_title">Rolle</string> <string name="a11y_open_chat">Unterhaltung öffnen</string> <string name="a11y_mute_microphone">Stelle Mikrophon stumm</string> @@ -1747,7 +1747,7 @@ <string name="start_chatting">Beginne eine Unterhaltung</string> <string name="settings_discovery_consent_action_give_consent">Autorisieren</string> <string name="settings_discovery_consent_action_revoke">Meine Zustimmung widerrufen</string> - <string name="settings_discovery_consent_notice_on">Du hast zugestimmt E-Mails und Telefonnummern an diesen Identitätsserver zu senden, um von anderen Nutzern entdeckt zu werden.</string> + <string name="settings_discovery_consent_notice_on">Du hast zugestimmt, E-Mail-Adressen und Telefonnummern an diesen Identitäts-Server zu übermitteln, um für andere auffindbar zu sein.</string> <string name="settings_discovery_consent_title">E-Mails und Telefonnummern senden</string> <string name="direct_room_user_list_suggestions_title">Vorschläge</string> <string name="direct_room_user_list_known_title">Bekannte Personen</string> @@ -1794,7 +1794,7 @@ <string name="create_room_alias_invalid">Manche Zeichen sind nicht zulässig</string> <string name="create_room_alias_empty">Bitte gib eine Raumadresse an</string> <string name="create_room_alias_already_in_use">Diese Adresse ist bereits vergeben</string> - <string name="create_room_disable_federation_description">Aktivieren, wenn der Raum nur von Mitgliedern deines Heimservers zur internen Kommunikation verwendet wird. Das kann später nicht mehr geändert werden.</string> + <string name="create_room_disable_federation_description">Aktivieren, wenn der Raum nur von Mitgliedern deines Heim-Servers zur internen Kommunikation verwendet wird. Das kann später nicht mehr geändert werden.</string> <string name="create_room_disable_federation_title">Begrenze Zugang zu diesem Raum (für immer!) auf Mitglieder von %s</string> <string name="attachment_viewer_item_x_of_y">%1$d von %2$d</string> <string name="room_preview_no_preview_join">Keine Vorschau für diesen Raum verfügbar. Willst du direkt beitreten\?</string> @@ -1859,7 +1859,7 @@ <string name="authentication_error">Authentifizierung fehlgeschlagen</string> <string name="re_authentication_default_confirm_text">Deine Anmeldeinformationen müssen für ${app_name} eingegeben werden, um diese Aktion auszuführen.</string> <string name="re_authentication_activity_title">Erneute Authentifizierung erforderlich</string> - <string name="failed_to_initialize_cross_signing">Cross-Signing konnte nicht eingerichtet werden</string> + <string name="failed_to_initialize_cross_signing">Quersignierung konnte nicht eingerichtet werden</string> <string name="error_unauthorized">Nicht autorisierte, fehlende gültige Authentifizierungsdaten</string> <string name="call_transfer_users_tab_title">Nutzer</string> <string name="call_transfer_failure">Beim Weiterleiten des Anrufs ist ein Fehler aufgetreten</string> @@ -1917,7 +1917,7 @@ <item quantity="other">%d Einträge</item> </plurals> <string name="settings_server_upload_size_unknown">Die Obergrenze ist nicht bekannt.</string> - <string name="settings_server_upload_size_content">Dein Homeserver akzeptiert Anhänge (wie Dateien, Medien, etc.) mit einer Größe bis zu %s.</string> + <string name="settings_server_upload_size_content">Dein Heim-Server akzeptiert Anhänge (wie Dateien, Medien, etc.) mit einer Größe bis zu %s.</string> <string name="settings_server_upload_size_title">Datei-Upload-Obergrenze des Servers</string> <string name="settings_server_version">Serverversion</string> <string name="settings_server_name">Servername</string> @@ -2086,7 +2086,7 @@ <string name="a11y_pause_voice_message">Sprachnachricht pausieren</string> <string name="a11y_play_voice_message">Sprachnachricht abspielen</string> <string name="a11y_start_voice_message">Sprachnachricht aufnehmen</string> - <string name="room_using_unstable_room_version">Dieser Raum verwendet die Raumversion %s, die von diesem Heimserver als instabil markiert ist.</string> + <string name="room_using_unstable_room_version">Dieser Raum verwendet die Raumversion %s, die von diesem Heim-Server als instabil markiert ist.</string> <string name="upgrade_room_no_power_to_manage">Du benötigst die Berechtigung, um einen Raum upzugraden</string> <string name="upgrade_room_update_parent_space">Übergeordneten Space automatisch updaten</string> <string name="upgrade_room_auto_invite">Benutzer automatisch einladen</string> @@ -2129,7 +2129,7 @@ <item quantity="one">Verpasster Sprachanruf</item> <item quantity="other">%d verpasste Sprachanrufe</item> </plurals> - <string name="hs_client_url">Heimserver API URL</string> + <string name="hs_client_url">Heim-Server API URL</string> <string name="denied_permission_voice_message">Um Sprachnachrichten zu senden, erlaube bitte Zugriff aufs Mikrofon.</string> <string name="denied_permission_camera">Um fortzufahren, erlaube bitte in den Systemeinstellungen Zugriff auf die Kamera.</string> <string name="denied_permission_generic">Für diese Aktion fehlen einige Berechtigungen, bitte erlaube diese in den Systemeinstellungen.</string> @@ -2243,8 +2243,8 @@ <string name="open_discovery_settings">Auffindungseinstellungen öffnen</string> <string name="shortcut_disabled_reason_sign_out">Sitzung abgemeldet!</string> <string name="shortcut_disabled_reason_room_left">Raum verlassen!</string> - <string name="login_error_homeserver_from_url_not_found_enter_manual">Heimserver auswählen</string> - <string name="login_error_homeserver_from_url_not_found">Es konnte kein Heimserver mit der Adresse %s gefunden werden. Bitte überprüfe die Adresse oder wähle den Heimserver manuell.</string> + <string name="login_error_homeserver_from_url_not_found_enter_manual">Heim-Server auswählen</string> + <string name="login_error_homeserver_from_url_not_found">Es konnte kein Heim-Server mit der Adresse %s gefunden werden. Bitte überprüfe die Adresse oder wähle den Heim-Server manuell.</string> <string name="space_add_space_to_any_space_you_manage">Untergeordneten Space hinzufügen.</string> <string name="identity_server_consent_dialog_content_question">Bist du dir wirklich sicher, dass du diese Informationen senden willst\?</string> <string name="identity_server_consent_dialog_title_2">E-Mail-Adressen und Telefonnummern an %s senden</string> @@ -2259,9 +2259,9 @@ \n%s kannst du alle unsere Bedingungen lesen.</string> <string name="create_spaces_invite_public_header_desc">Stelle sicher, dass die richtigen Personen Zugriff auf %s haben. Du kannst jederzeit weitere Personen einladen.</string> <string name="create_spaces_invite_public_header">Wer ist Mitglied deines Teams\?</string> - <string name="settings_discovery_no_policy_provided">Der Identitätsserver gibt keine Bedingungen an</string> - <string name="settings_discovery_hide_identity_server_policy_title">Bedingungen des Identitätsservers ausblenden</string> - <string name="settings_discovery_show_identity_server_policy_title">Bedingungen des Identitätsservers anzeigen</string> + <string name="settings_discovery_no_policy_provided">Der Identitäts-Server gibt keine Bedingungen an</string> + <string name="settings_discovery_hide_identity_server_policy_title">Richtlinie des Identitäts-Servers ausblenden</string> + <string name="settings_discovery_show_identity_server_policy_title">Bedingungen des Identitäts-Servers anzeigen</string> <string name="preference_system_settings">Systemeinstellungen</string> <string name="preference_versions">Versionen</string> <string name="preference_help_summary">Erhalte Hilfe bei der Bedienung von ${app_name}</string> @@ -2296,15 +2296,15 @@ <string name="discovery_section">Auffindbarkeit (%s)</string> <string name="discovery_invite">Per E-Mail einladen, finde deine Kontakte und mehr…</string> <string name="finish_setting_up_discovery">Schließe die Konfiguration des Auffindbarkeitsdienstes ab.</string> - <string name="create_space_identity_server_info_none">Du verwendest derzeit keinen Identitätsserver. Um Teammitglieder einzuladen und für sie auffindbar zu sein, müssen du einen solchen Server konfigurieren.</string> - <string name="login_splash_already_have_account">Ich habe schon ein Konto</string> - <string name="ftue_auth_carousel_encrypted_title">Sichere Nachrichtenübertragung.</string> - <string name="ftue_auth_carousel_secure_title">Besitze deine Konversationen.</string> - <string name="identity_server_consent_dialog_content_3">Um bestehende Kontakte ermitteln zu können, müsst du Kontaktinformationen (E-Mails und Telefonnummern) an Ihren Identitätsserver senden. Wir verschlüsseln deine Daten vor dem Senden, um den Datenschutz zu gewährleisten.</string> - <string name="settings_discovery_consent_notice_off_2">Deine Kontakte sind privat. Um in deinen Kontakten Benutzer erkennen zu können, benötigen wir deine Erlaubnis, Kontaktinformationen an deinen Identitätsserver zu senden.</string> + <string name="create_space_identity_server_info_none">Du verwendest derzeit keinen Identitäts-Server. Um Team-Mitglieder einzuladen und für sie auffindbar zu sein, konfiguriere zunächst einen.</string> + <string name="login_splash_already_have_account">Ich habe bereits ein Konto</string> + <string name="ftue_auth_carousel_encrypted_title">Sichere Kommunikation.</string> + <string name="ftue_auth_carousel_secure_title">Besitze deine Unterhaltungen.</string> + <string name="identity_server_consent_dialog_content_3">Um bestehende Kontakte ermitteln zu können, musst du Kontaktinformationen (E-Mail-Adressen und Telefonnummern) an deinen Identitäts-Server übermitteln. Wir verschlüsseln deine Daten vor der Übermittlung, um den Datenschutz gewährleisten zu können.</string> + <string name="settings_discovery_consent_notice_off_2">Deine Kontakte sind privat. Um unter deinen Kontakten Matrix-Nutzer finden zu können, benötigen wir deine Erlaubnis, Kontaktinformationen an deinen Identitäts-Server zu übermitteln.</string> <string name="legals_no_policy_provided">Dieser Server stellt keine Richtlinie bereit.</string> - <string name="legals_identity_server_title">Deine Identitätsserver-Richtlinie</string> - <string name="legals_home_server_title">Deine Heimserver Richtlinie</string> + <string name="legals_identity_server_title">Richtlinie deines Identitäts-Servers</string> + <string name="legals_home_server_title">Richtlinie deines Heim-Servers</string> <string name="legals_application_title">${app_name} Richtlinie</string> <string name="tooltip_attachment_poll">Abstimmung erstellen</string> <string name="tooltip_attachment_contact">Kontakte öffnen</string> @@ -2340,10 +2340,10 @@ <string name="edit_poll_title">Umfrage bearbeiten</string> <string name="poll_no_votes_cast">Keine Stimmen abgegeben</string> <string name="login_splash_create_account">Konto erstellen</string> - <string name="ftue_auth_carousel_workplace_title">Nachrichtenaustausch für dein Team.</string> + <string name="ftue_auth_carousel_workplace_title">Kommunikation für dein Team.</string> <string name="ftue_auth_carousel_encrypted_body">Ende-zu-Ende-verschlüsselt und ohne Telefonnummer nutzbar. Keine Werbung oder Datenerfassung.</string> - <string name="ftue_auth_carousel_control_body">Wähle wo deine Gespräche liegen, für Kontrolle und Unabhängigkeit. Verbunden mit Matrix.</string> - <string name="ftue_auth_carousel_secure_body">Sichere und unabhängige Kommunikation, die für die gleiche Vertraulichkeit sorgt, wie ein Gespräch von Angesicht zu Angesicht in deinem eigenen Zuhause.</string> + <string name="ftue_auth_carousel_control_body">Wähle, wo deine Unterhaltungen gespeichert werden, um Kontrolle und Unabhängigkeit zu erhalten. Verbunden via Matrix.</string> + <string name="ftue_auth_carousel_secure_body">Sichere und unabhängige Kommunikation, die für eine Vertraulichkeit sorgt, wie ein Gespräch von Angesicht zu Angesicht in deinen eigenen vier Wänden.</string> <string name="attachment_type_location">Standort</string> <string name="encryption_misconfigured">Die Verschlüsselung ist fehlerhaft konfiguriert</string> <string name="contact_admin_to_restore_encryption">Bitte kontaktiere einen Admin, um die Verschlüsselung zurückzusetzen.</string> @@ -2411,7 +2411,7 @@ <string name="beta">BETA</string> <string name="give_feedback_threads">Rückmeldung geben</string> <string name="beta_title_bottom_sheet_action">BETA</string> - <string name="threads_labs_enable_notice_title">Threads Beta</string> + <string name="threads_labs_enable_notice_title">Threads-Beta</string> <string name="threads_beta_enable_notice_title">Threads Beta</string> <string name="call_start_screen_sharing">Bildschirm teilen</string> <string name="action_try_it_out">Probiere es aus</string> @@ -2480,11 +2480,11 @@ <string name="settings_presence">Präsenz</string> <string name="settings_autoplay_animated_images_summary">Animierte Bilder in der Zeitleiste abspielen, sobald sie sichtbar sind</string> <string name="settings_autoplay_animated_images_title">Animierte Bilder automatisch abspielen</string> - <string name="settings_troubleshoot_test_endpoint_registration_failed">Das Endpunkt-Token konnte nicht auf dem Heimserver registriert werden: + <string name="settings_troubleshoot_test_endpoint_registration_failed">Das Endpunkt-Token konnte nicht auf dem Heim-Server registriert werden: \n%1$s</string> - <string name="settings_troubleshoot_test_endpoint_registration_success">Endpunkt erfolgreich beim Heimserver registriert.</string> + <string name="settings_troubleshoot_test_endpoint_registration_success">Endpunkt erfolgreich beim Heim-Server registriert.</string> <string name="settings_troubleshoot_test_endpoint_registration_title">Endpunkt-Registrierung</string> - <string name="threads_labs_enable_notice_message">Dein Heimserver unterstützt derzeit keine Threads, daher kann diese Funktion evtl. nicht richtig funktionieren. Einige Nachrichten mit Threads sind möglicherweise nicht zuverlässig verfügbar. %sMöchtest Du Threads trotzdem aktivieren\?</string> + <string name="threads_labs_enable_notice_message">Dein Heim-Server unterstützt derzeit keine Threads, daher könnte diese Funktion evtl. nicht richtig funktionieren. Einige Nachrichten mit Threads sind möglicherweise nicht zuverlässig verfügbar. %sMöchtest Du Threads trotzdem aktivieren\?</string> <string name="threads_beta_enable_notice_message">Threads helfen dabei, Unterhaltungen beim Thema zu halten und leichter zu verfolgen. %sDie Aktivierung von Threads aktualisiert die App. Dies kann bei einigen Konten länger dauern.</string> <string name="threads_notice_migration_message">Wir nähern uns der Veröffentlichung einer öffentlichen Beta für Threads. \n @@ -2506,7 +2506,7 @@ <string name="a11y_presence_busy">Beschäftigt</string> <string name="settings_security_pin_code_use_biometrics_error">Die biometrische Authentifizierung konnte nicht aktiviert werden.</string> <string name="auth_biometric_key_invalidated_message">Die biometrische Authentifizierung wurde deaktiviert, weil kürzlich eine neue biometrische Authentifizierungsmethode hinzugefügt wurde. Du kannst sie in den Einstellungen wieder aktivieren.</string> - <string name="error_forbidden_digits_only_username">Der Heimserver akzeptiert keine Benutzernamen, die nur aus Ziffern bestehen.</string> + <string name="error_forbidden_digits_only_username">Der Heim-Server akzeptiert keine Benutzernamen, die nur aus Ziffern bestehen.</string> <string name="sent_live_location">teilten ihren Live-Standort</string> <string name="ftue_personalize_skip_this_step">Schritt überspringen</string> <string name="ftue_personalize_submit">Speichern und fortfahren</string> @@ -2527,7 +2527,7 @@ <string name="ftue_auth_terms_title">Server-Richtlinien</string> <string name="ftue_auth_email_verification_subtitle">Folge den Anweisungen, die an %s gesendet wurden</string> <string name="ftue_auth_email_verification_title">E-Mail bestätigen</string> - <string name="poll_undisclosed_not_ended">Ergebnisse sind nach Beenden der Abstimmung sichtbar</string> + <string name="poll_undisclosed_not_ended">Ergebnisse werden nach Abschluss der Abstimmung sichtbar sein</string> <string name="ftue_auth_reset_password_breaker_title">Prüfe deine E-Mails.</string> <string name="ftue_auth_reset_password">Passwort zurücksetzen</string> <string name="ftue_auth_new_password_subtitle">Gib mindestens 8 Zeichen ein.</string> @@ -2550,12 +2550,12 @@ <item quantity="one">%d Nachricht gelöscht</item> <item quantity="other">%d Nachrichten gelöscht</item> </plurals> - <string name="labs_enable_element_call_permission_shortcuts">Keine Element Call-Berechtigungsabfragen</string> - <string name="labs_enable_element_call_permission_shortcuts_summary">Bestätige automatisch Element Call-Widgets und erlaube Kamera- und Mikrofonzugriff</string> + <string name="labs_enable_element_call_permission_shortcuts">Keine Element-Call-Berechtigungsabfragen</string> + <string name="labs_enable_element_call_permission_shortcuts_summary">Bestätige automatisch Element-Call-Widgets und erlaube Kamera- und Mikrofonzugriff</string> <string name="create_room_action_go">Los</string> <string name="ftue_auth_create_account_edit_server_selection">ändern</string> <string name="ftue_auth_create_account_sso_section_header">oder</string> - <string name="ftue_auth_sign_in_choose_server_header">Das Zuhause deiner Gespräche</string> + <string name="ftue_auth_sign_in_choose_server_header">Der Ort, an dem deine Gespräche stattfinden</string> <string name="ftue_auth_create_account_choose_server_header">Das zukünftige Zuhause für deine Gespräche</string> <string name="font_size_use_system">Systemstandard nutzen</string> <string name="font_size_section_auto">Automatisch festlegen</string> @@ -2565,9 +2565,9 @@ <string name="auth_reset_password_error_unverified">E-Mail nicht bestätigt, prüfe deinen Posteingang</string> <string name="ftue_auth_welcome_back_title">Willkommen zurück!</string> <string name="ftue_auth_forgot_password">Passwort vergessen</string> - <string name="ftue_auth_login_username_entry">Benutzername / E-Mail / Telefon</string> + <string name="ftue_auth_login_username_entry">Nutzername / E-Mail-Adresse / Telefonnummer</string> <string name="ftue_auth_create_account_title">Erstelle dein Konto</string> - <string name="ftue_auth_choose_server_entry_hint">Serveradresse</string> + <string name="ftue_auth_choose_server_entry_hint">Server-URL</string> <string name="ftue_auth_choose_server_subtitle">Wie lautet die Adresse deines Servers\? Das wird eine Art Zuhause für deine Daten</string> <string name="ftue_auth_choose_server_sign_in_subtitle">Wie lautet die Adresse deines Servers\?</string> <string name="ftue_auth_create_account_password_entry_footer">Muss 8 oder mehr Zeichen umfassen</string> @@ -2595,7 +2595,7 @@ <string name="room_list_filter_favourites">Favoriten</string> <string name="room_list_filter_all">Alle</string> <string name="location_share_loading_map_error">Karte laden nicht möglich -\nDieser Heimserver könnte für die Kartendarstellung nicht konfiguriert sein.</string> +\nDieser Heim-Server könnte für die Kartendarstellung nicht konfiguriert sein.</string> <string name="a11y_open_settings">Einstellungen öffnen</string> <string name="verify_invalid_qr_notice">Dieser QR-Code ist fehlerhaft. Bitte versuche es mit einer anderen Methode.</string> <string name="crosssigning_cannot_verify_this_session_desc">Du wirst deinen verschlüsselten Nachrichtenverlauf nicht abrufen können. Um neu zu beginnen, setze deine Sicherung und Verifizierungsschlüssel zurück.</string> From f9d9a0c1784286bcdf24f586d42751a72c8da86a Mon Sep 17 00:00:00 2001 From: Lamdarer <Lamdarer@mailbox.org> Date: Mon, 26 Sep 2022 17:30:29 +0000 Subject: [PATCH 080/187] Translated using Weblate (German) Currently translated at 100.0% (2419 of 2419 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 | 2 +- 1 file changed, 1 insertion(+), 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 a7c5d7530e..a15e9f6c67 100644 --- a/library/ui-strings/src/main/res/values-de/strings.xml +++ b/library/ui-strings/src/main/res/values-de/strings.xml @@ -2401,7 +2401,7 @@ <string name="location_share_live_stop">Beenden</string> <string name="location_share_live_enabled">Live-Standort aktiviert</string> <string name="a11y_location_share_option_pinned_icon">Standort teilen</string> - <string name="location_share_option_pinned">Standort teilen</string> + <string name="location_share_option_pinned">Diesen Standort teilen</string> <string name="location_share_option_user_current">Meinen Standort teilen</string> <string name="a11y_location_share_option_user_current_icon">Meinen Standort teilen</string> <string name="a11y_location_share_option_user_live_icon">Live-Standort teilen</string> From 366d273fc04bc56dd5e0031f7028fa3d51583061 Mon Sep 17 00:00:00 2001 From: Linerly <linerly@protonmail.com> Date: Sun, 25 Sep 2022 11:36:00 +0000 Subject: [PATCH 081/187] Translated using Weblate (Indonesian) Currently translated at 100.0% (2419 of 2419 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 | 2 +- 1 file changed, 1 insertion(+), 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 c608d82c85..7b103a9131 100644 --- a/library/ui-strings/src/main/res/values-in/strings.xml +++ b/library/ui-strings/src/main/res/values-in/strings.xml @@ -2617,7 +2617,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.</string> <string name="space_list_empty_title">Belum ada space.</string> <string name="a11y_collapse_space_children">Tutup %s anak</string> <string name="a11y_expand_space_children">Buka %s anak</string> - <string name="change_space">Buat Space</string> + <string name="change_space">Ubah Space</string> <string name="device_manager_session_details_device_ip_address">Alamat IP</string> <string name="device_manager_session_details_session_last_activity">Aktivitas terakhir</string> <string name="device_manager_session_details_session_name">Nama sesi</string> From a2ad855ce29389794b9c253aeb570ac03ae37586 Mon Sep 17 00:00:00 2001 From: Anonimas <weblate@govindas.net> Date: Thu, 29 Sep 2022 20:04:22 +0000 Subject: [PATCH 082/187] Translated using Weblate (Lithuanian) Currently translated at 80.1% (1940 of 2419 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/lt/ --- .../src/main/res/values-lt/strings.xml | 779 +++++++++++++++++- 1 file changed, 778 insertions(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values-lt/strings.xml b/library/ui-strings/src/main/res/values-lt/strings.xml index adfc70c36e..aeba3d53e6 100644 --- a/library/ui-strings/src/main/res/values-lt/strings.xml +++ b/library/ui-strings/src/main/res/values-lt/strings.xml @@ -1406,4 +1406,781 @@ <string name="room_permissions_change_room_name">Keisti kambario pavadinimą</string> <string name="room_permissions_change_history_visibility">Keisti istorijos matomumą</string> <string name="notice_direct_room_update">%s atnaujino čia.</string> -</resources> + <string name="login_a11y_captcha_container">Atlikite captcha iššūkį</string> + <string name="login_a11y_choose_other">Pasirinkti pasirinktinį namų serverį</string> + <string name="login_a11y_choose_modular">Pasirinkti Element Matrix Services</string> + <string name="login_a11y_choose_matrix_org">Pasirinkti matrix.org</string> + <string name="login_signup_cancel_confirmation_content">Jūsų paskyra dar nesukurta. Sustabdyti registracijos procesą\?</string> + <string name="login_signup_cancel_confirmation_title">Perspėjimas</string> + <string name="login_signup_error_user_in_use">Šis vartotojo vardas yra užimtas</string> + <string name="login_signup_submit">Toliau</string> + <string name="login_signup_password_hint">Slaptažodis</string> + <string name="login_signup_username_hint">Naudotojo vardas</string> + <string name="login_signin_username_hint">Naudotojo vardas arba el. pašto adresas</string> + <string name="login_msisdn_confirm_submit">Toliau</string> + <string name="login_msisdn_confirm_send_again">Siųsti vėl</string> + <string name="login_msisdn_confirm_hint">Įvesti kodą</string> + <string name="login_msisdn_confirm_notice">Ką tik išsiuntėme kodą į %1$s. Įveskite jį toliau, kad patvirtintumėte, kad tai jūs.</string> + <string name="login_set_msisdn_title">Nustatyti telefono numerį</string> + <string name="does_not_look_like_valid_email">Neatrodo kaip tinkamas el. pašto adresas</string> + <string name="login_set_email_submit">Toliau</string> + <string name="login_msisdn_confirm_title">Patvirtinkite telefono numerį</string> + <string name="login_set_email_optional_hint">El. pašto adresas (nebūtinas)</string> + <string name="login_set_email_mandatory_hint">El. pašto adresas</string> + <string name="login_set_msisdn_submit">Toliau</string> + <string name="login_set_msisdn_optional_hint">Telefono numeris (nebūtinas)</string> + <string name="login_reset_password_success_notice">Jūsų slaptažodis buvo nustatytas iš naujo.</string> + <string name="login_reset_password_success_title">Sėkmė!</string> + <string name="login_reset_password_mail_confirmation_submit">Patvirtinau savo el. pašto adresą</string> + <string name="login_reset_password_mail_confirmation_notice_2">Bakstelėkite nuorodą ir patvirtinkite naująjį slaptažodį. Paspaudę joje esančią nuorodą, spustelėkite žemiau.</string> + <string name="login_reset_password_mail_confirmation_notice">Patvirtinimo el. laiškas buvo išsiųstas į %1$s.</string> + <string name="login_reset_password_mail_confirmation_title">Patikrinkite savo pašto dėžutę</string> + <string name="login_reset_password_error_not_found">Šis el. paštas nėra susietas su jokia paskyra</string> + <string name="login_reset_password_warning_submit">Tęsti</string> + <string name="login_reset_password_warning_content">Pakeitus slaptažodį bus iš naujo nustatyti visų jūsų sesijų visapusiško šifravimo raktai, todėl užšifruotų pokalbių istorijos nebus galima perskaityti. Prieš iš naujo nustatydami slaptažodį, sukurkite raktų atsarginę kopiją arba eksportuokite kambario raktus iš kitos sesijos.</string> + <string name="login_reset_password_warning_title">Perspėjimas!</string> + <string name="login_reset_password_password_hint">Naujas slaptažodis</string> + <string name="login_reset_password_email_hint">El. paštas</string> + <string name="login_reset_password_submit">Toliau</string> + <string name="login_reset_password_notice">Į jūsų pašto dėžutę bus išsiųstas patvirtinimo el. laiškas, naujo slaptažodžio nustatymo patvirtinimui.</string> + <string name="login_reset_password_on">Iš naujo nustatyti slaptažodį %1$s</string> + <string name="login_login_with_email_error">Šis el. paštas nesusijęs su jokia paskyra.</string> + <string name="login_registration_not_supported">Programa negali sukurti paskyros šiame namų serveryje. +\n +\nAr norite užsiregistruoti naudodami žiniatinklio klientą\?</string> + <string name="login_registration_disabled">Atsiprašome, šis serveris nepriima naujų paskyrų.</string> + <string name="login_mode_not_supported">Programa negali prisijungti prie šio namų serverio. Namų serveris palaiko šiuos prisijungimo tipus: %1$s. +\n +\nAr norite prisijungti naudodami žiniatinklio klientą\?</string> + <string name="login_sso_error_message">Įkeliant puslapį įvyko klaida: %1$s (%2$d)</string> + <string name="login_server_url_form_common_notice">Įveskite norimo naudoti serverio adresą</string> + <string name="login_server_url_form_modular_notice">Įveskite adresą Modular Element arba serverio kurį norite naudoti</string> + <string name="login_server_url_form_modular_text">Aukščiausios kokybės talpinimas organizacijoms</string> + <string name="login_server_url_form_other_hint">Adresas</string> + <string name="login_server_url_form_modular_hint">Element Matrix Services Adresas</string> + <string name="login_clear_homeserver_history">Išvalyti istoriją</string> + <string name="login_signin_sso">Tęsti su vienkartiniu prisijungimu</string> + <string name="login_signin">Prisijungti</string> + <string name="login_signup">Registruotis</string> + <string name="login_signin_to">Prisijungti prie %1$s</string> + <string name="login_connect_to_a_custom_server">Prisijungti prie pasirinktinio serverio</string> + <string name="login_connect_to_modular">Prisijungti prie Element Matrix Services</string> + <string name="login_connect_to">Prisijungti prie %1$s</string> + <string name="login_continue">Tęsti</string> + <string name="login_social_sso">vienkartinis prisijungimas</string> + <string name="login_social_signin_with">Prisijungti su %s</string> + <string name="login_social_signup_with">Užsiregistruoti su %s</string> + <string name="login_social_continue_with">Tęsti su %s</string> + <string name="login_social_continue">Arba</string> + <string name="login_server_other_text">Pasirinktiniai & išplėstiniai nustatymai</string> + <string name="login_server_other_title">Kitas</string> + <string name="login_server_modular_learn_more">Sužinoti daugiau</string> + <string name="login_server_modular_text">Aukščiausios kokybės talpinimas organizacijoms</string> + <string name="login_server_matrix_org_text">Nemokamai prisijunkite prie milijonų žmonių didžiausiame viešajame serveryje</string> + <string name="login_server_text">Kaip ir el. paštas, paskyros turi vienus namus, nors galite bendrauti su bet kuo</string> + <string name="login_server_title">Pasirinkti serverį</string> + <string name="login_splash_already_have_account">Aš jau turiu paskyrą</string> + <string name="login_splash_create_account">Sukurti paskyrą</string> + <string name="login_splash_submit">Pradėkite</string> + <string name="login_splash_text3">Išplėskite ir pritaikykite savo patirtį</string> + <string name="login_splash_text2">Saugokite pokalbių privatumą naudodami šifravimą</string> + <string name="login_splash_text1">Bendraukite su žmonėmis tiesiogiai arba grupėse</string> + <string name="login_splash_title">Tai jūsų pokalbis. Priklauso jums.</string> + <string name="ftue_personalize_skip_this_step">Praleisti šį žingsnį</string> + <string name="ftue_personalize_submit">Išsaugoti ir tęsti</string> + <string name="ftue_personalize_complete_subtitle">Bet kada eikite į nustatymus norint atnaujinti savo profilį</string> + <string name="ftue_personalize_complete_title">Atrodo gerai!</string> + <string name="ftue_personalize_lets_go">Pirmyn</string> + <string name="ftue_profile_picture_subtitle">Laikas prie vardo pridėti veidą</string> + <string name="ftue_profile_picture_title">Pridėti profilio nuotrauką</string> + <string name="ftue_display_name_entry_footer">Jūs tai galite pakeisti vėliau</string> + <string name="ftue_display_name_entry_title">Rodomas vardas</string> + <string name="ftue_display_name_title">Pasirinkite rodomą vardą</string> + <string name="ftue_auth_login_username_entry">Vartotojo vardas / el. paštas / telefonas</string> + <string name="ftue_auth_captcha_title">Ar esate žmogus\?</string> + <string name="ftue_auth_password_reset_email_confirmation_subtitle">Vykdykite nurodymus, išsiųstus adresu %s</string> + <string name="ftue_auth_forgot_password">Pamiršau slaptažodį</string> + <string name="ftue_auth_password_reset_confirmation">Slaptažodžio nustatymas iš naujo</string> + <string name="ftue_auth_email_resend_email">Iš naujo siųsti el. laišką</string> + <string name="ftue_auth_email_verification_footer">Negavote el. laiško\?</string> + <string name="ftue_auth_email_verification_subtitle">Vykdykite nurodymus, išsiųstus adresu %s</string> + <string name="ftue_auth_email_verification_title">Patvirtinkite savo el. pašto adresą</string> + <string name="ftue_auth_phone_confirmation_resend_code">Iš naujo siųsti kodą</string> + <string name="ftue_auth_phone_confirmation_subtitle">Kodas buvo išsiųstas į %s</string> + <string name="ftue_auth_phone_confirmation_title">Patvirtinkite savo telefono numerį</string> + <string name="ftue_auth_sign_out_all_devices">Atjungti visus prietaisus</string> + <string name="ftue_auth_reset_password">Iš naujo nustatyti slaptažodį</string> + <string name="ftue_auth_use_case_option_one">Draugai ir šeima</string> + <string name="ftue_auth_use_case_subtitle">Padėsime jums užmegzti ryšį</string> + <string name="ftue_auth_use_case_title">Su kuo daugiausiai bendrausite\?</string> + <string name="ftue_auth_carousel_workplace_body">${app_name} taip pat puikiai tinka darbo vietoje. Ja pasitiki saugiausios pasaulio organizacijos.</string> + <string name="ftue_auth_carousel_encrypted_body">Visapusiškai užšifruota ir nereikia telefono numerio. Jokių reklamų ar duomenų rinkimo.</string> + <string name="ftue_auth_carousel_control_body">Pasirinkite, kur bus saugomi jūsų pokalbiai, taip suteikdami jums galimybę kontroliuoti ir būti nepriklausomiems. Sujungta naudojant Matrix.</string> + <string name="ftue_auth_carousel_secure_body">Saugus ir nepriklausomas bendravimas, suteikiantis tiek pat privatumo, kiek ir pokalbis akis į akį jūsų namuose.</string> + <string name="error_terms_not_accepted">Bandykite dar kartą, kai sutiksite su savo namų serverio nuostatomis ir sąlygomis.</string> + <string name="labs_allow_extended_logging_summary">Išsamūs žurnalai padės kūrėjams, nes siųsdami piktą purtymą pateiksite daugiau žurnalų. Net ir įjungus šią funkciją, programa nerenka žinučių turinio ar kitų privačių duomenų.</string> + <string name="labs_allow_extended_logging">Įjungti išsamius žurnalus.</string> + <string name="settings_agree_to_terms">Sutikite su tapatybės serverio (%s) paslaugų teikimo sąlygomis, kad galėtumėte būti atrandami pagal el. pašto adresą arba telefono numerį.</string> + <string name="settings_discovery_disconnect_with_bound_pid">Šiuo metu bendrinate el. pašto adresus arba telefono numerius tapatybės serveryje %1$s. Norėdami nustoti juos bendrinti, turėsite iš naujo prisijungti prie %2$s.</string> + <string name="settings_text_message_sent">Tekstinė žinutė buvo išsiųsta adresu %s. Įveskite joje esantį patvirtinimo kodą.</string> + <string name="settings_discovery_no_terms">Pasirinktame tapatybės serveryje nėra jokių paslaugų teikimo sąlygų. Tęskite tik tuo atveju, jei pasitikite paslaugos savininku</string> + <string name="settings_discovery_no_terms_title">Tapatybės serveris neturi paslaugų teikimo sąlygų</string> + <string name="settings_discovery_please_enter_server">Įveskite tapatybės serverio url</string> + <string name="settings_discovery_bad_identity_server">Nepavyko prisijungti prie tapatybės serverio</string> + <string name="settings_discovery_enter_identity_server">Įveskite tapatybės serverio URL</string> + <string name="identity_server_consent_dialog_content_question">Ar sutinkate siųsti šią informaciją\?</string> + <string name="identity_server_consent_dialog_content_3">Jei norite atrasti esamus kontaktus, į tapatybės serverį reikia nusiųsti kontaktinę informaciją (el. paštus ir telefono numerius). Prieš išsiunčiant duomenis, siekiant užtikrinti privatumą, juos sutriname.</string> + <string name="give_feedback_threads">Pateikti atsiliepimą</string> + <string name="give_feedback" tools:ignore="UnusedResources">Pateikti atsiliepimą</string> + <string name="feedback_failed">Atsiliepimo nepavyko išsiųsti (%s)</string> + <string name="feedback_sent">Ačiū, jūsų atsiliepimas sėkmingai išsiųstas</string> + <string name="you_may_contact_me">Jei turite papildomų klausimų, galite susisiekti su manimi</string> + <string name="feedback">Atsiliepimas</string> + <string name="beta_title_bottom_sheet_action">BETA</string> + <string name="send_suggestion_failed">Pasiūlymo nepavyko išsiųsti (%s)</string> + <string name="send_suggestion_sent">Ačiū, pasiūlymas sėkmingai išsiųstas</string> + <string name="send_suggestion_report_placeholder">Aprašykite savo pasiūlymą čia</string> + <string name="send_suggestion_content">Žemiau parašykite savo pasiūlymą.</string> + <string name="send_suggestion">Pateikti pasiūlymą</string> + <string name="preference_versions">Versijos</string> + <string name="preference_help_summary">Gaukite pagalbos naudojant ${app_name}</string> + <string name="preference_help_title">Pagalba ir parama</string> + <string name="preference_help">Pagalba</string> + <string name="preference_root_legals">Teisės aktai</string> + <string name="preference_root_help_about">Pagalba & Apie</string> + <string name="preference_voice_and_video">Balsas & Vaizdas</string> + <string name="push_gateway_item_profile_tag">Profilio žyma:</string> + <string name="push_gateway_item_format">Formatas:</string> + <string name="push_gateway_item_url">Url:</string> + <string name="push_gateway_item_device_name">session_name:</string> + <string name="push_gateway_item_app_display_name">app_display_name:</string> + <string name="push_gateway_item_push_key">push_key:</string> + <string name="push_gateway_item_app_id">app_id:</string> + <string name="navigate_to_thread_when_already_in_the_thread">Jūs jau žiūrite šią temą!</string> + <string name="navigate_to_room_when_already_in_the_room">Jūs jau žiūrite šį kambarį!</string> + <string name="import_e2e_keys_from_file">Importuoti šifravimo raktus iš failo \"%1$s\".</string> + <string name="keys_backup_unable_to_get_keys_backup_data">Įvyko klaida gaunant raktų atsarginės kopijos duomenis</string> + <string name="keys_backup_unable_to_get_trust_info">Įvyko klaida gaunant pasitikėjimo informaciją</string> + <string name="create_room_federation_error">Kambarys sukurtas, tačiau kai kurie kvietimai nebuvo išsiųsti dėl šios priežasties: +\n +\n%s</string> + <string name="create_room_public_description">Kiekvienas galės prisijungti prie šio kambario</string> + <string name="create_room_public_title">Viešas</string> + <string name="create_room_topic_hint">Tema</string> + <string name="create_room_topic_section">Kambario tema (nebūtina)</string> + <string name="create_room_name_hint">Pavadinimas</string> + <string name="create_room_name_section">Kambario pavadinimas</string> + <string name="create_room_action_go">Eiti</string> + <string name="create_room_action_create">SUKURTI</string> + <string name="fab_menu_create_chat">Tiesioginės žinutės</string> + <string name="fab_menu_create_room">Kambariai</string> + <string name="room_preview_no_preview_join">Šio kambario negalima peržiūrėti. Ar norite prie jo prisijungti\?</string> + <string name="room_preview_not_found">Šiuo metu į šį kambarį patekti negalima. +\nPabandykite vėliau arba paprašykite kambario admino patikrinti, ar turite prieigą.</string> + <string name="room_preview_no_preview">Šio kambario negalima peržiūrėti</string> + <string name="updating_your_data">Atnaujinami jūsų duomenys…</string> + <string name="please_wait">Prašome palaukti…</string> + <string name="change_room_directory_network">Keisti tinklą</string> + <string name="error_no_network">Tinklo nėra. Patikrinkite interneto ryšį.</string> + <string name="create_new_room">Sukurti naują kambarį</string> + <string name="malformed_message">Neteisingai suformuotas įvykis, negalima rodyti</string> + <string name="event_redacted_by_admin_reason">Įvykis moderuotas kambario admino</string> + <string name="event_redacted_by_user_reason">Naudotojo ištrintas įvykis</string> + <string name="event_redacted">Žinutė pašalinta</string> + <string name="reactions">Reakcijos</string> + <string name="message_view_reaction">Peržiūrėti reakcijas</string> + <string name="message_add_reaction">Pridėti reakciją</string> + <string name="title_activity_emoji_reaction_picker">Reakcijos</string> + <string name="room_list_filter_people">Žmonės</string> + <string name="room_list_filter_favourites">Parankiniai</string> + <string name="room_list_filter_unreads">Neperskaityti</string> + <string name="room_list_filter_all">Visi</string> + <string name="room_list_rooms_empty_body">Čia bus rodomi jūsų kambariai. Bakstelėkite \"+\" apačioje dešinėje, kad rastumėte esamus kambarius arba pradėtumėte kurti savo.</string> + <string name="room_list_rooms_empty_title">Kambariai</string> + <string name="room_list_people_empty_body">Jūsų tiesioginių žinučių pokalbiai bus rodomi čia. Bakstelėkite \"+\" apačioje dešinėje, kad pradėtumėte keletą.</string> + <string name="room_list_people_empty_title">Pokalbiai</string> + <string name="room_list_catchup_empty_body">Neturite daugiau neperskaitytų žinučių</string> + <string name="room_list_catchup_empty_title">Jūs viską pasivijote!</string> + <string name="invited_by">Pakvietė %s</string> + <string name="send_you_invite">Išsiuntė jums kvietimą</string> + <string name="global_retry">Pakartoti</string> + <string name="view_in_room">Peržiūrėti kambaryje</string> + <string name="reply_in_thread">Atsakyti temoje</string> + <string name="reply">Atsakyti</string> + <string name="edit">Redaguoti</string> + <string name="error_user_already_logged_in">Atrodo, kad bandote prisijungti prie kito namų serverio. Ar norite atsijungti\?</string> + <string name="identity_server_not_defined">Jūs nenaudojate jokio tapatybės serverio</string> + <string name="sas_error_unknown">Nežinoma klaida</string> + <string name="sas_incoming_request_notif_content">%s nori patvirtinti jūsų sesiją</string> + <string name="sas_incoming_request_notif_title">Patvirtinimo užklausa</string> + <string name="sas_got_it">Supratau</string> + <string name="sas_verified">Patvirtinta!</string> + <string name="keys_backup_info_title_signature">Parašas</string> + <string name="keys_backup_info_title_algorithm">Algoritmas</string> + <string name="keys_backup_info_title_version">Versija</string> + <plurals name="keys_backup_info_keys_backing_up"> + <item quantity="one">Kuriama atsarginė %d rakto kopija…</item> + <item quantity="few">Kuriama atsarginė %d raktų kopija…</item> + <item quantity="other">Kuriama atsarginė %d raktų kopija…</item> + </plurals> + <string name="keys_backup_info_keys_all_backup_up">Visų raktų atsarginė kopija sukurta</string> + <string name="secure_backup_setup">Nustatyti saugią atsarginę kopiją</string> + <string name="keys_backup_banner_in_progress">Kuriama raktų atsarginė kopija. Tai gali užtrukti kelias minutes…</string> + <string name="keys_backup_banner_update_line2">Valdyti raktų atsarginėje kopijoje</string> + <string name="keys_backup_banner_update_line1">Nauji saugių žinučių raktai</string> + <string name="keys_backup_banner_recover_line2">Naudoti raktų atsarginę kopiją</string> + <string name="keys_backup_banner_recover_line1">Niekada nepraraskite užšifruotų žinučių</string> + <string name="secure_backup_banner_setup_line2">Apsisaugokite nuo užšifruotų žinučių ir duomenų praradimo</string> + <string name="secure_backup_banner_setup_line1">Saugi atsarginė kopija</string> + <string name="notification_off">Išjungta</string> + <string name="command_description_clear_scalar_token">Kad ištaisyti Matrix programėlių valdymą</string> + <string name="command_description_markdown">Įj./Išj. markdown</string> + <string name="key_share_request">Prašymas dalytis raktais</string> + <string name="timeline_error_room_not_found">Atsiprašome, šis kambarys nerastas. +\nPrašome bandyti vėliau.%s</string> + <string name="widget_integration_review_terms">Jei norite tęsti, turite sutikti su šios paslaugos sąlygomis.</string> + <string name="room_no_active_widgets">Nėra aktyvių valdiklių</string> + <string name="widget_integration_missing_user_id">Užklausoje trūksta user_id.</string> + <string name="widget_integration_missing_room_id">Užklausoje trūksta room_id.</string> + <string name="widget_integration_positive_power_level">Galios lygis turi būti teigiamas sveikasis skaičius.</string> + <string name="widget_integration_failed_to_send_request">Nepavyko išsiųsti užklausos.</string> + <string name="widget_integration_unable_to_create">Nepavyko sukurti valdiklio.</string> + <string name="room_widget_webview_read_protected_media">Skaityti DRM apsaugotą mediją</string> + <string name="room_widget_resource_permission_title">Šis valdiklis nori naudoti šiuos išteklius:</string> + <string name="jitsi_leave_conf_to_join_another_one_content">Palikti dabartinę konferenciją ir pereiti į kitą\?</string> + <string name="error_jitsi_join_conf">Atsiprašome, bandant prisijungti prie konferencijos įvyko klaida</string> + <string name="error_jitsi_not_supported_on_old_device">Atsiprašome, konferenciniai skambučiai su Jitsi nepalaikomi senuose įrenginiuose (įrenginiuose su žemesne nei 6.0 Android OS)</string> + <string name="room_widget_permission_widget_id">Valdiklio ID</string> + <string name="room_widget_permission_theme">Jūsų tema</string> + <string name="room_widget_permission_user_id">Jūsų naudotojo ID</string> + <string name="room_widget_permission_avatar_url">Jūsų avataro URL</string> + <string name="room_widget_permission_display_name">Jūsų rodomas vardas</string> + <string name="room_widget_revoke_access">Atšaukti prieigą man</string> + <string name="room_widget_open_in_browser">Atidaryti naršyklėje</string> + <string name="room_widget_reload">Iš naujo įkelti valdiklį</string> + <string name="room_widget_failed_to_load">Nepavyko įkelti valdiklio. +\n%s</string> + <string name="room_widget_permission_shared_info_title">Naudojant jį duomenys gali būti bendrinami su %s:</string> + <string name="room_widget_permission_webview_shared_info_title">Naudojant jį gali būti nustatyti slapukai ir bendrinami duomenys su %s:</string> + <string name="room_widget_permission_added_by">Šį valdiklį pridėjo:</string> + <string name="room_widget_permission_title">Įkelti valdiklį</string> + <string name="room_widget_activity_title">Valdiklis</string> + <string name="active_widgets_title">Aktyvūs valdikliai</string> + <string name="active_widget_view_action">PERŽIŪRĖTI</string> + <plurals name="active_widgets"> + <item quantity="one">%d aktyvus valdiklis</item> + <item quantity="few">%d aktyvūs valdikliai</item> + <item quantity="other">%d aktyvių valdiklių</item> + </plurals> + <string name="widget_delete_message_confirmation">Ar tikrai norite ištrinti valdiklį iš šio kambario\?</string> + <string name="huge">Milžiniškas</string> + <string name="largest">Didžiausias</string> + <string name="larger">Didesnis</string> + <string name="large">Didelis</string> + <string name="normal">Vidutinis</string> + <string name="small">Mažas</string> + <string name="tiny">Mažytis</string> + <string name="font_size">Šrifto dydis</string> + <string name="font_size_use_system">Naudoti sistemos numatytąjį</string> + <string name="font_size_section_manually">Pasirinkti rankiniu būdu</string> + <string name="font_size_section_auto">Nustatyti automatiškai</string> + <string name="font_size_title">Pasirinkti šrifto dydį</string> + <string name="notification_ticker_text_group">%1$s: %2$s %3$s</string> + <string name="notification_ticker_text_dm">%1$s: %2$s</string> + <string name="notification_inline_reply_failed">** Nepavyko išsiųsti - atidarykite kambarį</string> + <string name="notification_sender_me">Aš</string> + <string name="notification_new_invitation">Naujas pakvietimas</string> + <string name="notification_new_messages">Naujos žinutės</string> + <string name="notification_unknown_room_name">Kambarys</string> + <string name="notification_unknown_new_event">Naujas įvykis</string> + <string name="notification_unread_notified_messages_and_invitation">%1$s ir %2$s</string> + <string name="notification_unread_notified_messages_in_room_and_invitation">%1$s esantys %2$s ir %3$s</string> + <string name="notification_unread_notified_messages_in_room">%1$s esantys %2$s</string> + <plurals name="notification_compat_summary_title"> + <item quantity="one">%d pranešimas</item> + <item quantity="few">%d pranešimai</item> + <item quantity="other">%d pranešimų</item> + </plurals> + <plurals name="notification_compat_summary_line_for_room"> + <item quantity="one">%1$s: %2$d žinutė</item> + <item quantity="few">%1$s: %2$d žinutės</item> + <item quantity="other">%1$s: %2$d žinučių</item> + </plurals> + <plurals name="notification_invitations"> + <item quantity="one">%d pakvietimas</item> + <item quantity="few">%d pakvietimai</item> + <item quantity="other">%d pakvietimų</item> + </plurals> + <plurals name="notification_unread_notified_messages_in_room_rooms"> + <item quantity="one">%d kambarys</item> + <item quantity="few">%d kambariai</item> + <item quantity="other">%d kambarių</item> + </plurals> + <plurals name="notification_unread_notified_messages"> + <item quantity="one">%d neperskaityta pranešta žinutė</item> + <item quantity="few">%d neperskaitytos praneštos žinutės</item> + <item quantity="other">%d neperskaitytų praneštų žinučių</item> + </plurals> + <string name="directory_add_a_new_server_error_already_added">Šis serveris jau yra sąraše</string> + <string name="directory_add_a_new_server_error">Negalima rasti šio serverio arba jo kambarių sąrašo</string> + <string name="directory_add_a_new_server_prompt">Įveskite naujo serverio, kurį norite patyrinėti, pavadinimą.</string> + <string name="directory_add_a_new_server">Pridėti naują serverį</string> + <string name="directory_your_server">Jūsų serveris</string> + <string name="directory_server_native_rooms">Visi vietiniai %s kambariai</string> + <string name="directory_server_all_rooms_on_server">Visi kambariai %s serveryje</string> + <string name="directory_server_placeholder">Serverio pavadinimas</string> + <string name="select_room_directory">Pasirinkti kambarių katalogą</string> + <string name="encryption_information_verify_device_warning2">Jei jie nesutampa, gali kilti pavojus jūsų komunikacijos saugumui.</string> + <string name="encryption_information_verify">Patvirtinti</string> + <string name="encryption_information_unknown_ip">nežinomas ip</string> + <string name="encryption_information_verified">Patvirtinta</string> + <string name="encryption_information_not_verified">Nepatvirtinta</string> + <plurals name="encryption_import_room_keys_success"> + <item quantity="one">%1$d/%2$d raktas importuotas sėkmingai.</item> + <item quantity="few">%1$d/%2$d raktai importuoti sėkmingai.</item> + <item quantity="other">%1$d/%2$d raktų importuota sėkmingai.</item> + </plurals> + <string name="encryption_never_send_to_unverified_devices_summary">Niekada nesiųsti užšifruotų žinučių į nepatvirtintas sesijas iš šios sesijos.</string> + <string name="encryption_never_send_to_unverified_devices_title">Šifruoti tik į patvirtintas sesijas</string> + <string name="encryption_import_import">Importuoti</string> + <string name="encryption_import_room_keys_summary">Importuoti raktus iš vietinio failo</string> + <string name="encryption_import_room_keys">Importuoti kambarių raktus</string> + <string name="encryption_import_e2e_room_keys">Importuoti šifruotų kambarių raktus</string> + <string name="encryption_message_recovery">Užšifruotų žinučių atkūrimas</string> + <string name="encryption_exported_successfully">Raktai sėkmingai eksportuoti</string> + <string name="encryption_export_notice">Sukurkite slaptafrazę eksportuojamiems raktams užšifruoti. Norėdami importuoti raktus, turėsite įvesti tą pačią slaptafrazę.</string> + <string name="encryption_export_export">Eksportuoti</string> + <string name="encryption_export_room_keys_summary">Eksportuoti raktus į vietinį failą</string> + <string name="encryption_export_room_keys">Eksportuoti kambarių raktus</string> + <string name="encryption_export_e2e_room_keys">Eksportuoti šifruotų kambarių raktus</string> + <string name="encryption_information_device_key">Sesijos raktas</string> + <string name="encryption_information_device_name">Viešas pavadinimas</string> + <string name="encryption_information_decryption_error">Iššifravimo klaida</string> + <string name="decide_who_can_find_and_join">Nuspręskite, kas gali rasti ir prisijungti prie šio kambario.</string> + <string name="room_alias_publish_to_directory_error">Nepavyko gauti dabartinio kambarių katalogo matomumo (%1$s).</string> + <string name="room_alias_publish_to_directory">Paskelbti šį kambarį viešai %1$s kambarių kataloge\?</string> + <string name="room_alias_action_unpublish">Panaikinti šio adreso skelbimą</string> + <string name="room_alias_action_publish">Paskelbti šį adresą</string> + <string name="room_alias_local_address_add">Pridėti vietinį adresą</string> + <string name="room_alias_local_address_empty">Šis kambarys neturi vietinių adresų</string> + <string name="room_alias_local_address_subtitle">Nustatykite šio kambario adresus, kad naudotojai galėtų rasti šį kambarį per jūsų namų serverį (%1$s)</string> + <string name="room_alias_local_address_title">Vietiniai adresai</string> + <string name="room_alias_address_hint">Naujas skelbiamas adresas (pvz., #pseudonimas:serveris)</string> + <string name="room_alias_address_empty">Kitų paskelbtų adresų dar nėra.</string> + <string name="room_alias_address_empty_can_add">Kitų paskelbtų adresų dar nėra, pridėkite juos žemiau.</string> + <string name="room_alias_delete_confirmation">Ištrinti adresą \"%1$s\"\?</string> + <string name="room_alias_unpublish_confirmation">Panaikinti adreso \"%1$s\" skelbimą\?</string> + <string name="room_alias_published_alias_add_manually_submit">Paskelbti</string> + <string name="room_alias_published_alias_add_manually">Paskelbti naują adresą rankiniu būdu</string> + <string name="room_alias_published_other">Kiti paskelbti adresai:</string> + <string name="room_alias_published_alias_main">Tai yra pagrindinis adresas</string> + <string name="room_alias_published_alias_subtitle">Paskelbtus adresus gali naudoti bet kas bet kuriame serveryje, prisijungimui prie jūsų kambario. Norint paskelbti adresą, pirmiausia nustatykite jį kaip vietinį adresą.</string> + <string name="room_alias_published_alias_title">Paskelbti adresai</string> + <string name="settings_troubleshoot_test_token_registration_title">Žetono registracija</string> + <string name="settings_troubleshoot_test_fcm_failed_account_missing_quick_fix">Pridėti paskyrą</string> + <string name="settings_troubleshoot_test_fcm_failed_account_missing">[%1$s] +\nŠi klaida yra nekontroliuojama ${app_name}. Telefone nėra Google paskyros. Atidarykite paskyrų tvarkytuvę ir pridėkite Google paskyrą.</string> + <string name="encryption_misconfigured">Šifravimas neteisingai sukonfigūruotas</string> + <string name="encryption_not_enabled">Šifravimas nėra įjungtas</string> + <string name="direct_room_encryption_enabled_tile_description_future">Šiame pokalbyje žinutės bus visapusiškai užšifruojamos.</string> + <string name="direct_room_encryption_enabled_tile_description">Šiame pokalbyje žinutės yra visapusiškai užšifruotos.</string> + <string name="encryption_enabled_tile_description">Šiame kambaryje žinutės yra visapusiškai užšifruotos. Sužinokite daugiau ir patvirtinkite naudotojus jų profilyje.</string> + <string name="encryption_enabled">Šifravimas įjungtas</string> + <string name="encryption_unknown_algorithm_tile_description">Šiame kambaryje naudojamas šifravimas nepalaikomas</string> + <string name="qr_code_scanned_verif_waiting_notice">Jau beveik! Laukiama patvirtinimo…</string> + <string name="qr_code_scanned_self_verif_notice">Jau beveik! Ar kitas prietaisas rodo varnelę\?</string> + <string name="topic_prefix">"Tema: "</string> + <string name="add_a_topic_link_text">Pridėkite temą</string> + <string name="send_your_first_msg_to_invite">Siųskite pirmąją žinutę kad pakviestumėte %s į pokalbį</string> + <string name="this_is_the_beginning_of_dm">Tai yra jūsų tiesioginių žinučių su %s istorijos pradžia.</string> + <string name="this_is_the_beginning_of_room_no_name">Tai šio pokalbio pradžia.</string> + <string name="this_is_the_beginning_of_room">Tai yra %s pradžia.</string> + <string name="direct_room_created_summary_item_by_you">Jūs prisijungėte.</string> + <string name="direct_room_created_summary_item">%s prisijungė.</string> + <string name="room_created_summary_item_by_you">Sukūrėte ir sukonfigūravote kambarį.</string> + <string name="room_created_summary_item">%s sukūrė ir sukonfigūravo kambarį.</string> + <string name="error_failed_to_import_keys">Nepavyko importuoti raktų</string> + <string name="qr_code_scanned_verif_waiting">Laukiama %s…</string> + <string name="auth_invalid_login_deactivated_account">Ši paskyra buvo deaktyvuota.</string> + <string name="room_message_placeholder">Žinutė…</string> + <string name="bootstrap_progress_checking_backup">Tikrinamas atsarginės kopijos raktas</string> + <string name="recovery_key_empty_error_message">Įveskite atkūrimo raktą</string> + <string name="bootstrap_invalid_recovery_key">Tai netinkamas atkūrimo raktas</string> + <string name="use_file">Naudoti failą</string> + <string name="bootstrap_enter_recovery">Norėdami tęsti, įveskite savo %s</string> + <string name="security_prompt_text">Patvirtinkite save ir kitus, kad pokalbiai būtų saugūs</string> + <string name="upgrade_security">Galimas šifravimo patobulinimas</string> + <string name="bootstrap_progress_checking_backup_with_info">Tikrinamas atsarginės kopijos raktas (%s)</string> + <string name="settings_troubleshoot_test_token_registration_success">FCM žetonas sėkmingai užregistruotas namų serveryje.</string> + <string name="terms_description_for_integration_manager">Naudoti botus, tiltus, valdiklius ir lipdukų paketus</string> + <string name="change_identity_server">Keisti tapatybės serverį</string> + <string name="settings_discovery_consent_title">Siųsti el. paštus ir telefono numerius</string> + <string name="add_identity_server">Konfigūruoti tapatybės serverį</string> + <string name="disconnect_identity_server">Atjungti tapatybės serverį</string> + <string name="identity_server">Tapatybės serveris</string> + <string name="settings_text_message_sent_wrong_code">Patvirtinimo kodas neteisingas.</string> + <string name="settings_text_message_sent_hint">Kodas</string> + <string name="error_network_timeout">Atrodo, kad serveris neatsako per ilgai, tai gali būti dėl prasto ryšio arba serverio klaidos. Pabandykite dar kartą po kurio laiko.</string> + <string name="one_user_read">%s perskaitė</string> + <string name="two_users_read">%1$s ir %2$s perskaitė</string> + <string name="three_users_read">%1$s, %2$s ir %3$s perskaitė</string> + <plurals name="two_and_some_others_read"> + <item quantity="one">%1$s, %2$s ir %3$d kitas perskaitė</item> + <item quantity="few">%1$s, %2$s ir %3$d kiti perskaitė</item> + <item quantity="other">%1$s, %2$s ir %3$d kitų perskaitė</item> + </plurals> + <string name="a11y_jump_to_bottom">Peršokti į apačią</string> + <string name="a11y_close_keys_backup_banner">Uždaryti raktų atsarginės kopijos antraštę</string> + <string name="a11y_create_room">Sukurti naują kambarį</string> + <string name="a11y_create_message">Sukurti naują pokalbį arba kambarį</string> + <string name="a11y_create_direct_message">Sukurti naują tiesioginį pokalbį</string> + <string name="a11y_create_menu_close">Uždaryti kambario kūrimo meniu…</string> + <string name="a11y_create_menu_open">Atidaryti kambario kūrimo meniu</string> + <string name="a11y_open_drawer">Atidaryti navigacijos stalčių</string> + <string name="send_attachment">Siųsti priedą</string> + <plurals name="fallback_users_read"> + <item quantity="one">%d naudotojas perskaitė</item> + <item quantity="few">%d naudotojai perskaitė</item> + <item quantity="other">%d naudotojų perskaitė</item> + </plurals> + <string name="error_file_too_big_simple">Failas yra per didelis, kad jį būtų galima įkelti.</string> + <string name="attachment_type_dialog_title">Pridėti paveikslėlį iš</string> + <string name="content_reported_as_inappropriate_content">Šis turinys buvo praneštas kaip nepadorus. +\n +\nJei nenorite matyti daugiau šio naudotojo turinio, galite jį ignoruoti kad paslėpti jo žinutes.</string> + <string name="content_reported_as_inappropriate_title">Pranešta kaip nepadorus turinys</string> + <string name="content_reported_as_spam_content">Apie šį turinį buvo pranešta kaip apie šlamštą. +\n +\nJei nenorite matyti daugiau šio naudotojo turinio, galite jį ignoruoti kad paslėpti jo žinutes.</string> + <string name="content_reported_as_spam_title">Pranešta kaip šlamštas</string> + <string name="content_reported_content">Buvo pranešta apie šį turinį. +\n +\nJei nenorite matyti daugiau šio naudotojo turinio, galite jį ignoruoti kad paslėpti jo žinutes.</string> + <string name="content_reported_title">Turinys praneštas</string> + <string name="block_user">IGNORUOTI NAUDOTOJĄ</string> + <string name="report_content_custom_submit">PRANEŠTI</string> + <string name="report_content_custom_hint">Pranešimo apie šį turinį priežastis</string> + <string name="report_content_custom_title">Pranešti apie šį turinį</string> + <string name="report_content_custom">Pasirinktinis pranešimas…</string> + <string name="report_content_inappropriate">Tai nepadoru</string> + <string name="report_content_spam">Tai šlamštas</string> + <string name="uploads_files_no_result">Šiame kambaryje nėra failų</string> + <string name="uploads_files_subtitle">%1$s %2$s</string> + <string name="uploads_files_title">FAILAI</string> + <string name="uploads_media_no_result">Šiame kambaryje nėra medijos</string> + <string name="uploads_media_title">MEDIJA</string> + <string name="attachment_viewer_item_x_of_y">%1$d iš %2$d</string> + <string name="error_handling_incoming_share">Nepavyko tvarkyti bendrinimo duomenų</string> + <string name="rotate_and_crop_screen_title">Pasukti ir apkarpyti</string> + <string name="attachment_type_location">Vietovė</string> + <string name="attachment_type_poll">Apklausa</string> + <string name="attachment_type_sticker">Lipdukas</string> + <string name="attachment_type_gallery">Galerija</string> + <string name="attachment_type_camera">Kamera</string> + <string name="attachment_type_contact">Kontaktas</string> + <string name="attachment_type_file">Failas</string> + <string name="reaction_search_type_hint">Įveskite raktažodžius, reakcijos radimui.</string> + <string name="spoiler">Spoileris</string> + <string name="command_description_spoiler">Siunčia duotą žinutę kaip spoilerį</string> + <string name="notice_member_no_changes_by_you">Nepadarėte jokių pakeitimų</string> + <string name="notice_member_no_changes">%1$s nepadarė jokių pakeitimų</string> + <string name="room_join_rules_invite">%1$s padarė šį kambarį tik pakviestiems.</string> + <string name="room_join_rules_public_by_you">Paviešinote kambarį visiems, kurie žino nuorodą.</string> + <string name="room_join_rules_public">%1$s paviešino kambarį visiems, kurie žino nuorodą.</string> + <string name="help_long_click_on_room_for_more_options">Ilgai spauskite ant kambario, kad pamatytumėte daugiau parinkčių</string> + <string name="no_ignored_users">Jūs neignoruojate jokių naudotojų</string> + <string name="room_list_quick_actions_low_priority_remove">Pašalinti iš žemo prioriteto</string> + <string name="room_list_quick_actions_low_priority_add">Pridėti prie žemo prioriteto</string> + <string name="room_list_quick_actions_favorite_remove">Pašalinti iš parankinių</string> + <string name="room_list_quick_actions_favorite_add">Pridėti prie parankinių</string> + <string name="message_ignore_user">Ignoruoti naudotoją</string> + <string name="room_list_quick_actions_notifications_all_noisy">Visos žinutės (triukšmingas)</string> + <string name="room_list_quick_actions_notifications_mute">Nutildyti</string> + <string name="room_list_quick_actions_notifications_mentions">Tik paminėjimai</string> + <string name="room_list_quick_actions_notifications_all">Visos žinutės</string> + <string name="room_list_quick_actions_settings">Nustatymai</string> + <string name="room_list_quick_actions_room_settings">Kambario nustatymai</string> + <string name="room_list_quick_actions_leave">Išeiti iš kambario</string> + <string name="direct_room_join_rules_invite_by_you">Padarėte šitai tik pakviestiems.</string> + <string name="direct_room_join_rules_invite">%1$s padarė šitai tik pakviestiems.</string> + <string name="room_join_rules_invite_by_you">Padarėte šį kambarį tik pakviestiems.</string> + <string name="ftue_auth_carousel_workplace_title">Žinučių siuntimas jūsų komandai.</string> + <string name="ftue_auth_carousel_encrypted_title">Saugus žinučių siuntimas.</string> + <string name="ftue_auth_carousel_control_title">Jūs viską kontroliuojate.</string> + <string name="ftue_auth_carousel_secure_title">Turėkite savo pokalbius.</string> + <string name="timeline_unread_messages">Neperskaitytos žinutės</string> + <string name="ftue_auth_use_case_skip">Dar nesate tikri\? %s</string> + <string name="ftue_auth_use_case_option_three">Bendruomenės</string> + <string name="ftue_auth_use_case_option_two">Komandos</string> + <string name="ftue_auth_create_account_edit_server_selection">Redaguoti</string> + <string name="ftue_auth_create_account_sso_section_header">Arba</string> + <string name="ftue_auth_sign_in_choose_server_header">Kur laikomi jūsų pokalbiai</string> + <string name="ftue_auth_create_account_choose_server_header">Kur bus laikomi jūsų pokalbiai</string> + <string name="ftue_auth_create_account_password_entry_footer">Turi būti ne mažiau kaip 8 simboliai</string> + <string name="ftue_auth_create_account_username_entry_footer">Kiti gali jus atrasti %s</string> + <string name="ftue_auth_create_account_title">Sukurti savo paskyrą</string> + <string name="ftue_account_created_subtitle">Jūsų paskyra %s buvo sukurta</string> + <string name="ftue_account_created_congratulations_title">Sveikiname!</string> + <string name="ftue_account_created_take_me_home">Pasiimkite mane namo</string> + <string name="ftue_account_created_personalize">Suasmeninti profilį</string> + <string name="ftue_auth_use_case_connect_to_server">Prisijungti prie serverio</string> + <string name="ftue_auth_use_case_join_existing_server">Norite prisijungti prie esamo serverio\?</string> + <string name="ftue_auth_use_case_skip_partial">Praleisti šį klausimą</string> + <string name="ftue_auth_welcome_back_title">Sveiki sugrįžę!</string> + <string name="ftue_auth_terms_subtitle">Perskaitykite %s sąlygas ir taisykles</string> + <string name="ftue_auth_terms_title">Serverio politikos</string> + <string name="ftue_auth_reset_password_breaker_title">Patikrinkite savo el. paštą.</string> + <string name="ftue_auth_choose_server_ems_cta">Susisiekite su mumis</string> + <string name="ftue_auth_choose_server_ems_subtitle">Element Matrix Services (EMS) yra tvirta ir patikima talpinimo paslauga, skirta greitam ir saugiam bendravimui realiuoju laiku. Sužinokite, kaip <a href=\"${ftue_ems_url}\">element.io/ems</a></string> + <string name="ftue_auth_choose_server_ems_title">Norite turėti savo serverį\?</string> + <string name="ftue_auth_reset_password_email_subtitle">%s atsiųs jums patvirtinimo nuorodą</string> + <string name="ftue_auth_choose_server_entry_hint">Serverio URL</string> + <string name="ftue_auth_phone_confirmation_entry_title">Patvirtinimo kodas</string> + <string name="ftue_auth_choose_server_sign_in_subtitle">Koks yra jūsų serverio adresas\?</string> + <string name="ftue_auth_choose_server_subtitle">Koks yra jūsų serverio adresas\? Tai tarsi visų jūsų duomenų namai</string> + <string name="ftue_auth_choose_server_title">Pasirinkti savo serverį</string> + <string name="ftue_auth_phone_entry_title">Telefono numeris</string> + <string name="ftue_auth_phone_subtitle">%s turi patvirtinti jūsų paskyrą</string> + <string name="ftue_auth_phone_title">Įveskite savo telefono numerį</string> + <string name="ftue_auth_email_entry_title">El. paštas</string> + <string name="ftue_auth_email_subtitle">%s turi patvirtinti jūsų paskyrą</string> + <string name="ftue_auth_email_title">Įveskite savo el. paštą</string> + <string name="ftue_auth_new_password_subtitle">Įsitikinkite, kad jis yra 8 ar daugiau simbolių.</string> + <string name="ftue_auth_new_password_title">Pasirinkite naują slaptažodį</string> + <string name="ftue_auth_new_password_entry_title">Naujas slaptažodis</string> + <string name="settings_notifications_targets">Pranešimų tikslai</string> + <string name="settings_olm_version">olm versija</string> + <string name="settings_integrations_summary">Naudokite integracijų tvarkyklę botams, tiltams, valdikliams ir lipdukų paketams tvarkyti. +\nIntegracijų valdytojai gauna konfigūracijos duomenis ir gali keisti valdiklius, siųsti kvietimus į kambarius ir nustatyti galios lygius jūsų vardu.</string> + <string name="settings_contacts_phonebook_country">Telefonų knygos šalis</string> + <string name="settings_contact">Vietiniai kontaktai</string> + <string name="settings_pin_missed_notifications">Prisegti kambarius su praleistais pranešimais</string> + <string name="settings_home_display">Pradžios ekranas</string> + <string name="settings_inline_url_preview_summary">Nuorodų peržiūra pokalbyje, kai jūsų namų serveris palaiko šią funkciją.</string> + <string name="settings_inline_url_preview">Įterptinė URL peržiūra</string> + <string name="settings_pin_unread_messages">Prisegti kambarius su neperskaitytomis žinutėmis</string> + <string name="settings_integrations">Integracijos</string> + <string name="settings_cryptography_manage_keys">Kriptografijos raktų valdymas</string> + <string name="settings_cryptography">Kriptografija</string> + <string name="analytics_opt_in_content">Padėkite mums nustatyti problemas ir tobulinti ${app_name} dalydamiesi anoniminiais naudojimo duomenimis. Kad suprastume, kaip žmonės naudojasi keliais įrenginiais, sugeneruosime atsitiktinį identifikatorių, kuriuo dalijasi jūsų įrenginiai. +\n +\nGalite perskaityti visas mūsų sąlygas %s.</string> + <string name="settings_presence_user_always_appears_offline_summary">Jei įjungta, kitiems naudotojams visada atrodysite neprisijungę, net jei naudosite programą.</string> + <string name="settings_presence_user_always_appears_offline">Neprisijungęs režimas</string> + <string name="settings_presence">Esamumas</string> + <string name="media_saving_period_forever">Amžinai</string> + <string name="media_saving_period_1_month">1 mėnuo</string> + <string name="media_saving_period_1_week">1 savaitė</string> + <string name="media_saving_period_3_days">3 dienos</string> + <string name="settings_play_shutter_sound">Groti užrakto garsą</string> + <string name="media_source_choose">Pasirinkti</string> + <string name="settings_default_media_source">Numatytasis medijos šaltinis</string> + <string name="compression_opt_list_choose">Pasirinkti</string> + <string name="settings_default_compression">Numatytasis glaudinimas</string> + <string name="settings_media">Medija</string> + <string name="settings_select_country">Pasirinkti šalį</string> + <string name="settings_discovery_consent_notice_on">Sutikote siųsti el. paštus ir telefono numerius į šį tapatybės serverį, kad būtų galima atrasti kitus naudotojus iš jūsų kontaktų.</string> + <string name="identity_server_consent_dialog_title_2">Siųsti el. paštus ir telefono numerius į %s</string> + <string name="settings_discovery_consent_action_give_consent">Duoti sutikimą</string> + <string name="settings_discovery_consent_action_revoke">Atšaukti mano sutikimą</string> + <string name="settings_discovery_consent_notice_off_2">Jūsų kontaktai yra privatūs. Kad galėtume rasti naudotojus iš jūsų kontaktų, mums reikia jūsų leidimo siųsti kontaktinę informaciją į jūsų tapatybės serverį.</string> + <string name="settings_discovery_confirm_mail_not_clicked">Išsiuntėme jums patvirtinimo el. laišką į %s, pirmiausia patikrinkite savo el. paštą ir spustelėkite patvirtinimo nuorodą</string> + <string name="settings_discovery_confirm_mail">Išsiuntėme jums patvirtinimo el. laišką į %s, patikrinkite savo el. paštą ir spustelėkite patvirtinimo nuorodą</string> + <string name="settings_discovery_msisdn_title">Atrandami telefono numeriai</string> + <string name="settings_discovery_disconnect_identity_server_info">Atsijungimas nuo tapatybės serverio reiškia, kad jūsų negalės rasti kiti naudotojai ir negalėsite pakviesti kitų el. paštu ar telefonu.</string> + <string name="settings_discovery_no_msisdn">Pridėjus telefono numerį bus rodomos atradimo parinktys.</string> + <string name="settings_discovery_no_mails">Pridėjus el. pašto adresą, bus rodomos atradimo parinktys.</string> + <string name="settings_discovery_emails_title">Atrandami el. pašto adresai</string> + <string name="settings_discovery_identity_server_info_none">Šiuo metu nenaudojate tapatybės serverio. Norėdami atrasti esamus žinomus kontaktus ir būti jų atrandami, sukonfigūruokite jį žemiau.</string> + <string name="settings_discovery_identity_server_info">Šiuo metu naudojate %1$s, esamų kontaktų atradimui, kuriuos pažįstate, ir kad būtumėte jų atrandami.</string> + <string name="settings_discovery_no_policy_provided">Tapatybės serveris nepateikė jokios politikos</string> + <string name="beta">BETA</string> + <string name="send_feedback_threads_info">Temos yra nebaigtas darbas, kuriame bus naujų, įdomių būsimų funkcijų, pvz., patobulinti pranešimai. Norėtume išgirsti jūsų atsiliepimus!</string> + <string name="send_feedback_threads_title">Temų Beta atsiliepimai</string> + <string name="settings_emails_and_phone_numbers_summary">Tvarkyti el. paštus ir telefono numerius susietus su jūsų Matrix paskyra</string> + <string name="settings_emails_and_phone_numbers_title">El. paštai ir telefono numeriai</string> + <string name="settings_unignore_user">Rodyti visas žinutes nuo %s\?</string> + <string name="settings_password_updated">Jūsų slaptažodis buvo atnaujintas</string> + <string name="settings_fail_to_update_password_invalid_current_password">Slaptažodis nėra tinkamas</string> + <string name="settings_fail_to_update_password">Nepavyko atnaujinti slaptažodžio</string> + <string name="settings_new_password">Naujas slaptažodis</string> + <string name="settings_old_password">Dabartinis slaptažodis</string> + <string name="settings_change_password">Keisti slaptažodį</string> + <string name="settings_password">Slaptažodis</string> + <string name="account_phone_number_already_used_error">Šis telefono numeris jau naudojamas.</string> + <string name="account_email_already_used_error">Šis el. pašto adresas jau naudojamas.</string> + <string name="account_email_validation_message">Patikrinkite savo el. paštą ir spustelėkite jame esančią nuorodą. Kai tai padarysite, spauskite tęsti.</string> + <string name="analytics_opt_in_title">Padėkite tobulinti ${app_name}</string> + <string name="settings_opt_in_of_analytics_summary">${app_name} renka anoniminę analizę, kad galėtume tobulinti programą.</string> + <string name="settings_select_language">Pasirinkti kalbą</string> + <string name="settings_interface_language">Kalba</string> + <string name="settings_opt_in_of_analytics">Siųsti analitikos duomenis</string> + <string name="settings_analytics">Analitika</string> + <string name="settings_discovery_manage">Tvarkyti atradimo nustatymus.</string> + <string name="settings_discovery_category">Atradimas</string> + <string name="settings_deactivate_my_account">Deaktyvuoti mano paskyrą</string> + <string name="reset_secure_backup_warning">Tai pakeis dabartinį raktą arba frazę.</string> + <string name="reset_secure_backup_title">Generuoti naują saugumo raktą arba nustatyti naują esamos atsarginės kopijos saugumo frazę.</string> + <string name="settings_secure_backup_section_info">Apsisaugokite nuo užšifruotų žinučių ir duomenų praradimo, darydami šifravimo raktų atsargines kopijas serveryje.</string> + <string name="settings_secure_backup_enter_to_setup">Nustatyti šiame įrenginyje</string> + <string name="settings_secure_backup_reset">Nustatyti saugią atsarginę kopiją iš naujo</string> + <string name="settings_secure_backup_setup">Nustatyti saugią atsarginę kopiją</string> + <string name="settings_secure_backup_section_title">Saugi atsarginė kopija</string> + <string name="settings_show_emoji_keyboard_summary">Pridėti žinutės kompozitoriuje mygtuką jaustukų klaviatūros atidarymui</string> + <string name="settings_show_emoji_keyboard">Rodyti jaustukų klaviatūrą</string> + <string name="settings_send_message_with_enter_summary">Programinės klaviatūros mygtukas Enter išsiųs žinutę, o ne pridės eilutės pertrauką</string> + <string name="settings_send_message_with_enter">Siųsti žinutę su enter</string> + <string name="settings_preview_media_before_sending">Medijos peržiūra prieš siunčiant</string> + <string name="settings_vibrate_on_mention">Vibruoti paminėjus naudotoją</string> + <string name="settings_show_avatar_display_name_changes_messages_summary">Įtraukiami avataro ir rodomojo vardo keitimai.</string> + <string name="settings_show_avatar_display_name_changes_messages">Rodyti paskyrų įvykius</string> + <string name="settings_show_join_leave_messages_summary">Kvietimai, pašalinimai ir užblokavimai nėra įtakojami.</string> + <string name="settings_show_join_leave_messages">Rodyti prisijungimo ir išėjimo įvykius</string> + <string name="settings_autoplay_animated_images_summary">Paleisti animuotus paveikslėlius laiko juostoje, kai tik jie tampa matomi</string> + <string name="settings_autoplay_animated_images_title">Automatinis animuotų vaizdų paleidimas</string> + <string name="settings_chat_effects_description">Naudokite /confetti komandą arba siųskite žinutę, kurioje yra ❄️ arba 🎉</string> + <string name="settings_chat_effects_title">Rodyti pokalbio efektus</string> + <string name="settings_show_read_receipts_summary">Spustelėkite ant skaitymo kvitų, kad pamatytumėte išsamų sąrašą.</string> + <string name="settings_show_read_receipts">Rodyti skaitymo kvitus</string> + <string name="settings_12_24_timestamps">Rodyti laiko žymas 12 valandų formatu</string> + <string name="settings_contacts_app_permission">Leidimas naudotis kontaktais</string> + <string name="settings_always_show_timestamps">Rodyti laiko žymas visoms žinutėms</string> + <string name="settings_send_markdown_summary">Prieš siunčiant žinutes, suformatuoti jas naudojant Markdown sintakse. Tai leidžia atlikti išplėstinį formatavimą, pavyzdžiui, naudoti žvaigždutes tekstui kursyvu rodyti.</string> + <string name="settings_send_markdown">Markdown formatavimas</string> + <string name="settings_user_interface">Naudotojo sąsaja</string> + <string name="settings_send_typing_notifs_summary">Leisti kitiems naudotojams žinoti, kad rašote.</string> + <string name="disabled_integration_dialog_content">Norėdami tai daryti, Įjunkite \'Leisti integracijas\' nustatymuose.</string> + <string name="settings_send_typing_notifs">Siųsti pranešimus apie rašymą</string> + <string name="legals_third_party_notices">Trečiųjų šalių bibliotekos</string> + <string name="legals_identity_server_title">Jūsų tapatybės serverio politika</string> + <string name="legals_home_server_title">Jūsų namų serverio politika</string> + <string name="legals_application_title">${app_name} politika</string> + <string name="settings_integration_manager">Integracijų tvarkyklė</string> + <string name="settings_integration_allow">Leisti integracijas</string> + <string name="settings_identity_server">Tapatybės serveris</string> + <string name="settings_home_server">Namų serveris</string> + <string name="settings_logged_in">Prisijungta kaip</string> + <string name="devices_delete_dialog_title">Autentifikacija</string> + <string name="devices_details_last_seen_format">%1$s @ %2$s</string> + <string name="devices_details_last_seen_title">Paskutinį kartą matytas</string> + <string name="devices_details_device_name">Atnaujinti viešą pavadinimą</string> + <string name="devices_details_name_title">Viešas pavadinimas</string> + <string name="settings_deactivate_account_section">Deaktyvuoti paskyrą</string> + <string name="devices_details_id_title">ID</string> + <string name="analytics_opt_in_list_item_3">Tai galite bet kada išjungti nustatymuose</string> + <string name="analytics_opt_in_list_item_2">Mes <b>nesidalijame</b> informacija su trečiosiomis šalimis</string> + <string name="analytics_opt_in_list_item_1">Mes <b>neįrašome ir neprofiliuojame</b> jokių paskyros duomenų</string> + <string name="analytics_opt_in_content_link">čia</string> + <string name="disabled_integration_dialog_title">Integracijos yra išjungtos</string> + <string name="legals_no_policy_provided">Šis serveris nepateikia jokios politikos.</string> + <string name="notice_call_candidates_by_you">Išsiuntėte duomenis skambučiui nustatyti.</string> + <string name="settings_discovery_hide_identity_server_policy_title">Slėpti tapatybės serverio politiką</string> + <string name="settings_discovery_show_identity_server_policy_title">Rodyti tapatybės serverio politiką</string> + <string name="downloaded_file">Failas %1$s buvo atsiųstas!</string> + <string name="send_file_step_compressing_video">Suglaudinamas vaizdo įrašas %d%%</string> + <string name="send_file_step_compressing_image">Suglaudinamas paveikslėlis…</string> + <string name="send_file_step_sending_file">Siunčiamas failas (%1$s / %2$s)</string> + <string name="send_file_step_sending_thumbnail">Siunčiama miniatiūra (%1$s / %2$s)</string> + <string name="send_file_step_encrypting_file">Užšifruojamas failas…</string> + <string name="send_file_step_encrypting_thumbnail">Užšifruojama miniatiūra…</string> + <string name="room_filtering_footer_title">Nerandate to, ko ieškote\?</string> + <string name="send_file_step_idle">Laukiama…</string> + <string name="room_filtering_filter_hint">Filtruoti pokalbius…</string> + <string name="no_message_edits_found">Redagavimų nerasta</string> + <string name="message_edits">Žinutės redagavimai</string> + <string name="edited_suffix">(redaguota)</string> + <string name="labs_show_unread_notifications_as_tab">Pagrindiniame ekrane pridėti specialų skirtuką neperskaitytiems pranešimams.</string> + <string name="labs_swipe_to_reply_in_timeline">Įjungti perbraukimą, kad atsakytumėte laiko juostoje</string> + <string name="search_hint_room_name">Ieškoti pavadinimo</string> + <string name="user_directory_search_hint_2">Ieškoti pagal vardą, ID arba paštą</string> + <string name="room_directory_search_hint">Pavadinimas arba ID (#pavyzdys:matrix.org)</string> + <string name="room_filtering_footer_open_room_directory">Peržiūrėti kambarių katalogą</string> + <string name="room_filtering_footer_create_new_direct_message">Siųsti naują tiesioginę žinutę</string> + <string name="bottom_action_people_x">Tiesioginės žinutės</string> + <string name="room_filtering_footer_create_new_room">Sukurti naują kambarį</string> + <string name="direct_room_user_list_suggestions_title">Pasiūlymai</string> + <string name="direct_room_user_list_known_title">Žinomi naudotojai</string> + <string name="creating_direct_room">Kuriamas kambarys…</string> + <string name="qr_code">QR kodas</string> + <string name="add_by_qr_code">Pridėti pagal QR kodą</string> + <string name="terms_description_for_identity_server">Būkite atrandami kitų</string> + <string name="terms_of_service">Paslaugų teikimo sąlygos</string> + <string name="message_view_edit_history">Peržiūrėti redagavimo istoriją</string> + <string name="link_copied_to_clipboard">Nuoroda nukopijuota į iškarpinę</string> + <string name="open_discovery_settings">Atidaryti atradimo nustatymus</string> + <string name="settings_labs_show_complete_history_in_encrypted_room">Rodyti pilną istoriją užšifruotuose kambariuose</string> + <string name="settings_labs_show_hidden_events_in_timeline">Rodyti paslėptus įvykius laiko juostoje</string> + <string name="settings_troubleshoot_test_endpoint_registration_quick_fix">Iš naujo nustatyti pranešimų metodą</string> + <string name="settings_troubleshoot_test_token_registration_quick_fix">Registruoti žetoną</string> + <string name="preference_system_settings">Sistemos nustatymai</string> + <string name="settings_push_gateway_no_pushers">Nėra registruotų tiesioginių pranešimų vartų</string> + <string name="settings_push_rules_no_rules">Nėra nustatytų tiesioginų pranešimų taisyklių</string> + <string name="settings_push_rules">Tiesioginių pranešimų taisyklės</string> + <string name="settings_security_and_privacy">Saugumas & Privatumas</string> + <string name="settings_preferences">Nuostatos</string> + <string name="settings_general_title">Bendrieji</string> + <string name="settings_other_third_party_notices">Kiti trečiųjų šalių pranešimai</string> + <string name="settings_sdk_version">Matrix SDK versija</string> + <string name="create_room_settings_section">Kambario nustatymai</string> + <string name="settings_show_redacted_summary">Rodyti pašalintų žinučių vietoje užrašą</string> + <string name="settings_show_redacted">Rodyti pašalintas žinutes</string> + <string name="keys_backup_settings_delete_confirm_message">ištrinti iš serverio atsarginę šifravimo raktų kopiją\? Atkūrimo rakto nebegalėsite naudoti užšifruotai žinučių istorijai skaityti.</string> + <string name="keys_backup_settings_delete_confirm_title">Ištrinti atsarginę kopiją</string> + <string name="keys_backup_settings_checking_backup_state">Tikrinama atsarginės kopijos būsena</string> + <string name="keys_backup_settings_deleting_backup">Atsarginė kopija ištrinama…</string> + <string name="keys_backup_settings_untrusted_backup">Jei norite naudoti atsarginę raktų kopiją šioje sesijoje, dabar atkurkite naudodami slaptažodį arba atkūrimo raktą.</string> + <string name="keys_backup_settings_invalid_signature_from_unverified_device">Atsarginė kopija turi netinkamą parašą iš nepatvirtintos sesijos %s</string> + <string name="keys_backup_settings_invalid_signature_from_verified_device">Atsarginė kopija turi netinkamą parašą iš patvirtintos sesijos %s</string> + <string name="settings_labs_native_camera_summary">Įjungti sistemos kamerą, vietoj pritaikytos kameros ekrano.</string> + <string name="settings_labs_native_camera">Naudoti vietinę kamerą</string> + <string name="encryption_information_verify_device_warning">Patvirtinkite palygindami šiuos duomenis su naudotojo nustatymais kitoje sesijoje:</string> + <string name="encryption_settings_manage_message_recovery_summary">Tvarkyti raktų atsarginę kopiją</string> + <string name="settings_theme">Tema</string> + <string name="room_settings_unset_main_address">Atšaukti nustatymą pagrindiniu adresu</string> + <string name="room_settings_set_main_address">Nustatyti kaip pagrindinį adresą</string> + <string name="room_settings_labs_warning_message">Tai eksperimentinės funkcijos, kurios gali netikėtai sugesti. Naudokite atsargiai.</string> + <string name="room_settings_labs_pref_title">Laboratorijos</string> + <string name="room_settings_room_version_title">Kambario versija</string> + <string name="room_settings_room_internal_id">Šio kambario vidinis ID</string> + <string name="room_settings_category_advanced_title">Išplėstiniai</string> + <plurals name="room_settings_banned_users_count"> + <item quantity="one">%d užblokuotas naudotojas</item> + <item quantity="few">%d užblokuoti naudotojai</item> + <item quantity="other">%d užblokuotų naudotojų</item> + </plurals> + <string name="room_settings_banned_users_title">Užblokuoti naudotojai</string> + <string name="room_settings_room_access_public_description">Bet kas gali rasti kambarį ir prisijungti</string> + <string name="room_settings_room_access_public_title">Viešas</string> + <string name="room_settings_room_access_private_description">Tik pakviesti žmonės gali rasti ir prisijungti</string> + <string name="room_settings_room_access_private_invite_only_title">Privatus (tik su kvietimais)</string> + <string name="room_settings_room_access_private_title">Privatus</string> + <string name="room_settings_room_access_entry_unknown">Nežinomas prieigos nustatymas (%s)</string> + <string name="room_settings_room_access_entry_knock">Bet kas gali pasibelsti į kambarį, o nariai gali priimti arba atmesti</string> + <string name="room_settings_read_history_entry_members_only_joined">Tik nariai (nuo jų prisijungimo)</string> + <string name="room_settings_read_history_entry_members_only_invited">Tik nariai (nuo jų pakvietimo)</string> + <string name="room_settings_read_history_entry_members_only_option_time_shared">Tik nariai (nuo šios parinkties pasirinkimo momento)</string> + <string name="room_settings_read_history_entry_anyone">Bet kas</string> + <string name="room_settings_guest_access_title">Leisti svečiams prisijungti</string> + <string name="room_settings_room_notifications_notify_me">Pranešti man apie</string> + <string name="room_settings_alias_subtitle">Peržiūrėti ir tvarkyti šio kambario adresus bei jo matomumą kambarių kataloge.</string> + <string name="room_settings_access_rules_pref_dialog_title">Kas gali prieiti\?</string> + <string name="room_settings_room_read_history_dialog_subtitle">Pakeitimai, kas gali skaityti istoriją, bus taikomi tik būsimoms šio kambario žinutėms. Esamos istorijos matomumas išliks nepakitęs.</string> + <string name="room_settings_room_read_history_rules_pref_dialog_title">Kas gali skaityti istoriją\?</string> + <string name="room_settings_room_read_history_rules_pref_title">Kambario istorijos skaitomumas</string> + <string name="room_settings_room_notifications_account_settings">Paskyros nustatymai</string> + <string name="room_settings_topic">Tema</string> + <string name="room_settings_alias_title">Kambario adresai</string> + <string name="room_settings_room_access_title">Kambario prieiga</string> + <string name="room_settings_room_notifications_manage_notifications">Pranešimus galite tvarkyti %1$s.</string> + <string name="room_settings_room_notifications_encryption_notice">Atkreipkite dėmesį, kad pranešimai apie paminėjimus ir raktinius žodžius užšifruotuose kambariuose, nėra prieinami mobiliuosiuose įrenginiuose.</string> + <string name="settings_notification_configuration">Pranešimų konfigūracija</string> + <string name="settings_security_prevent_screenshots_summary">Įjungus šį nustatymą, prie visų veiksmų pridedamas žymuo FLAG_SECURE. Iš naujo paleiskite programą, kad pakeitimas įsigaliotų.</string> + <string name="settings_security_prevent_screenshots_title">Neleisti programos ekrano nuotraukų</string> + <string name="auth_biometric_key_invalidated_message">Biometrinis autentifikavimas buvo išjungtas, nes neseniai buvo pridėtas naujas biometrinis autentifikavimo metodas. Jį vėl galite įjungti nustatymuose.</string> + <string name="settings_security_pin_code_use_biometrics_error">Nepavyko įjungti biometrinio autentifikavimo.</string> + <string name="a11y_open_settings">Atidaryti nustatymus</string> + <string name="labs_enable_deferred_dm_summary">Sukurti AŽ tik po pirmos žinutės</string> + <string name="labs_enable_deferred_dm_title">Įjungti atidėtas AŽ</string> + <string name="labs_enable_new_app_layout_summary">Supaprastintas Element su nebūtinais skirtukais</string> + <string name="labs_enable_new_app_layout_title">Įjungti naują išdėstymą</string> +</resources> \ No newline at end of file From fa66ce1833c9928c3d5fdf7a4558cdfa241eee0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Romanik?= <github@rom4nik.pl> Date: Fri, 30 Sep 2022 14:20:10 +0000 Subject: [PATCH 083/187] Translated using Weblate (Polish) Currently translated at 97.8% (2367 of 2419 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 4109a130a5..c9bac8977b 100644 --- a/library/ui-strings/src/main/res/values-pl/strings.xml +++ b/library/ui-strings/src/main/res/values-pl/strings.xml @@ -732,7 +732,7 @@ <string name="settings_send_message_with_enter">Wysyłaj wiadomości za pomocą klawisza enter</string> <string name="settings_send_message_with_enter_summary">Przycisk enter na klawiaturze programowej wyśle wiadomość zamiast wprowadzania łamanania linii</string> <string name="settings_discovery_category">Ustawienia wyszukiwania</string> - <string name="settings_discovery_manage">Ustal jak inni mogą odnaleść twoje konto.</string> + <string name="settings_discovery_manage">Ustal jak inni mogą odnaleźć twoje konto.</string> <string name="settings_media">Media</string> <string name="settings_default_media_source">Domyślne źródło mediów</string> <string name="encryption_message_recovery">Odzyskiwanie zaszyfrowanych wiadomości</string> From feff368f856928d791c67c767a37e4ec67da0cfe Mon Sep 17 00:00:00 2001 From: Nui Harime <harime.nui@yandex.ru> Date: Fri, 30 Sep 2022 10:00:35 +0000 Subject: [PATCH 084/187] Translated using Weblate (Russian) Currently translated at 98.3% (2380 of 2419 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 | 43 +++++++++++++------ 1 file changed, 29 insertions(+), 14 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 38dd4a831e..80941dd86a 100644 --- a/library/ui-strings/src/main/res/values-ru/strings.xml +++ b/library/ui-strings/src/main/res/values-ru/strings.xml @@ -273,7 +273,7 @@ <string name="home_filter_placeholder_home">Фильтр названий комнат</string> <string name="invitations_header">Приглашения</string> <string name="low_priority_header">Маловажные</string> - <string name="direct_chats_header">Беседы</string> + <string name="direct_chats_header">Личные сообщения</string> <string name="matrix_only_filter">Только Matrix контакты</string> <string name="no_result_placeholder">Нет результатов</string> <string name="rooms_header">Комнаты</string> @@ -452,7 +452,7 @@ <string name="encryption_information_verify_device_warning">Чтобы убедиться, что этой сессии можно доверять, обратитесь к ее владельцу, используя другие способы (например, лично или по телефону), и спросите, соответствует ли ключ, который он видит в настройках для этой сессии:</string> <string name="encryption_information_verify_device_warning2">Если они не совпадают, безопасность вашего общения может быть поставлена под угрозу.</string> <string name="select_room_directory">Выбор каталога комнат</string> - <string name="directory_server_placeholder">Имя сервера</string> + <string name="directory_server_placeholder">Название сервера</string> <string name="directory_server_all_rooms_on_server">Все комнаты на сервере %s</string> <string name="directory_server_native_rooms">Все местные комнаты %s</string> <string name="settings_user_interface">Пользовательский интерфейс</string> @@ -910,7 +910,7 @@ <string name="create_new_room">Создать комнату</string> <string name="error_no_network">Нет сети. Пожалуйста, проверьте подключение к Интернету.</string> <string name="action_change">Изменить</string> - <string name="change_room_directory_network">Изменить сеть</string> + <string name="change_room_directory_network">Изменить сервер</string> <string name="please_wait">Пожалуйста, подождите…</string> <string name="room_preview_no_preview">Эту комнату нельзя предварительно просмотреть</string> <string name="fab_menu_create_room">Комнаты</string> @@ -1039,7 +1039,7 @@ <string name="room_widget_webview_access_camera">Использовать камеру</string> <string name="room_widget_webview_access_microphone">Использовать микрофон</string> <string name="room_widget_webview_read_protected_media">Получать доступ к медиа, защищённым DRM</string> - <string name="a11y_create_room">Создать новую комнату</string> + <string name="a11y_create_room">Создать комнату</string> <string name="attachment_type_file">Файл</string> <string name="attachment_type_camera">Камера</string> <string name="attachment_type_gallery">Галерея</string> @@ -1390,7 +1390,7 @@ <string name="verification_request_you_accepted">Вы приняли</string> <string name="verification_sent">Подтверждение отправлено</string> <string name="verification_request">Запрос на подтверждение</string> - <string name="verification_verify_device">Подтвердите эту сессию</string> + <string name="verification_verify_device">Заверьте эту сессию</string> <string name="verification_scan_notice">Сканируйте код с помощью устройства другого пользователя, чтобы безопасно проверить друг друга</string> <string name="verification_scan_their_code">Сканировать их код</string> <string name="verification_scan_emoji_title">Невозможно сканировать</string> @@ -1450,7 +1450,7 @@ <item quantity="few">%d сессии активны</item> <item quantity="many">%d сессий активно</item> </plurals> - <string name="crosssigning_verify_this_session">Подтвердите это устройство</string> + <string name="crosssigning_verify_this_session">Заверьте эту сессию</string> <string name="verification_open_other_to_verify">Используйте существующую сессию для подтверждения этой, предоставив ей доступ к зашифрованным сообщениям.</string> <string name="settings_dev_tools">Инструменты для разработчиков</string> <string name="settings_account_data">Данные учётной записи</string> @@ -1473,13 +1473,13 @@ <string name="bottom_sheet_setup_secure_backup_title">Безопасное резервное копирование</string> <string name="settings_active_sessions_verified_device_desc">Эта сессия является надежной для безопасного обмена сообщениями, поскольку вы подтвердили ее:</string> <string name="settings_active_sessions_unverified_device_desc">Подтвердите эту сессию, чтобы пометить её доверенной и предоставить ей доступ к зашифрованным сообщениям. Если вы не входили в эту сессию, ваша учетная запись может быть скомпрометирована:</string> - <string name="verification_profile_verify">Проверить</string> - <string name="verification_profile_verified">Проверено</string> + <string name="verification_profile_verify">Заверить</string> + <string name="verification_profile_verified">Заверено</string> <string name="verification_profile_warning">Предупреждение</string> <string name="room_member_profile_failed_to_get_devices">Не удалось получить список сессий</string> <string name="room_member_profile_sessions_section_title">Сессии</string> - <string name="trusted">Доверенные</string> - <string name="not_trusted">Недоверенные</string> + <string name="trusted">Заверенная</string> + <string name="not_trusted">Незаверенная</string> <string name="verification_profile_device_verified_because">Эта сессия является доверенной для безопасного обмена сообщениями, так как %1$s (%2$s) проверил(а) его:</string> <string name="verification_profile_device_new_signing">%1$s (%2$s) вошел(ла), используя новую сессию:</string> <string name="verification_profile_device_untrust_info">Пока этот пользователь не доверяет этой сессии, сообщения, отправленные в обе стороны, помечаются предупреждениями. Кроме того, вы можете подтвердить сессию вручную.</string> @@ -2037,7 +2037,7 @@ <string name="space_leave_prompt_msg_only_you">Вы здесь единственный человек. Если вы уйдёте, никто не сможет присоединиться в будущем, включая вас.</string> <string name="leave_space">Покинуть</string> <string name="space_add_child_title">Добавить комнаты</string> - <string name="space_explore_activity_title">Исследуйте комнаты</string> + <string name="space_explore_activity_title">Обзор комнат</string> <plurals name="space_people_you_know"> <item quantity="one">%d человек, которого вы знаете, уже присоединился</item> <item quantity="few">%d людей, которых вы знаете, уже присоединились</item> @@ -2665,7 +2665,7 @@ <string name="settings_sessions_list">Сессии</string> <string name="a11y_create_message">Создать беседу или комнату</string> <string name="device_manager_settings_active_sessions_show_all">Показать все сессии (V2, в разработке)</string> - <string name="room_list_filter_people">Люди</string> + <string name="room_list_filter_people">ЛС</string> <string name="home_layout_preferences">Настройки вида</string> <string name="home_layout_preferences_filters">Фильтры</string> <string name="home_layout_preferences_recents">Недавние</string> @@ -2675,7 +2675,7 @@ <string name="home_layout_preferences_sort_name">А - Я</string> <string name="home_layout_preferences_sort_activity">Активности</string> <string name="home_layout_preferences_sort_by">Сортировать по</string> - <string name="explore_rooms">Каталог комнат</string> + <string name="explore_rooms">Обзор комнат</string> <string name="start_chat">Отправить ЛС</string> <string name="create_room">Создать комнату</string> <string name="device_manager_other_sessions_view_all">Посмотреть все (%1$d)</string> @@ -2699,7 +2699,7 @@ <string name="device_manager_session_details_session_last_activity">Последняя активность</string> <string name="device_manager_session_details_title">Сведения о сессии</string> <string name="device_manager_other_sessions_recommendation_description_verified">Для лучшей безопасности выйдите из всех сессий, которые более не признаёте или не используете.</string> - <string name="device_manager_filter_option_verified">Заверено</string> + <string name="device_manager_filter_option_verified">Заверенные</string> <string name="device_manager_filter_option_all_sessions">Все сессии</string> <string name="device_manager_session_last_activity">Последняя активность %1$s</string> <string name="device_manager_device_title">Устройство</string> @@ -2727,4 +2727,19 @@ <string name="device_manager_other_sessions_recommendation_description_unverified">Подтвердите свои сессии для более безопасного обмена сообщениями или выйдите из тех, которые более не признаёте или не используете.</string> <string name="device_manager_unverified_sessions_description">Подтвердите или выйдите из незаверенных сессий.</string> <string name="device_manager_verification_status_detail_other_session_unverified">Подтвердите или выйдите из этой сессии для лучшей безопасности и надёжности.</string> + <string name="invites_empty_title">Ничего нового.</string> + <string name="device_manager_other_sessions_no_verified_sessions_found">Заверенных сессий не обнаружено.</string> + <string name="device_manager_other_sessions_no_unverified_sessions_found">Незаверенных сессий не обнаружено.</string> + <string name="device_manager_other_sessions_no_inactive_sessions_found">Неактивных сессий не обнаружено.</string> + <string name="device_manager_other_sessions_clear_filter">Очистить фильтр</string> + <string name="device_manager_filter_option_unverified_description">Не готовы к безопасному обмену сообщениями</string> + <string name="device_manager_filter_option_verified_description">Готовы к безопасному обмену сообщениями</string> + <plurals name="device_manager_filter_option_inactive_description"> + <item quantity="one">Неактивны %1$d день или дольше</item> + <item quantity="few">Неактивны %1$d дня или дольше</item> + <item quantity="many">Неактивны %1$d дней или дольше</item> + <item quantity="other">Неактивны %1$d дней или дольше</item> + </plurals> + <string name="device_manager_filter_option_unverified">Незаверенные</string> + <string name="device_manager_filter_bottom_sheet_title">Фильтр</string> </resources> \ No newline at end of file From 5566901b712f27e16b42f8103511e5efefa750a9 Mon Sep 17 00:00:00 2001 From: phardyle <bradney_ccea@aleeas.com> Date: Fri, 30 Sep 2022 13:33:43 +0000 Subject: [PATCH 085/187] Translated using Weblate (Chinese (Simplified)) Currently translated at 96.0% (73 of 76 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/zh_Hans/ --- fastlane/metadata/android/zh-CN/full_description.txt | 2 +- fastlane/metadata/android/zh-CN/short_description.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fastlane/metadata/android/zh-CN/full_description.txt b/fastlane/metadata/android/zh-CN/full_description.txt index 9b60098c34..03fdb6e34d 100644 --- a/fastlane/metadata/android/zh-CN/full_description.txt +++ b/fastlane/metadata/android/zh-CN/full_description.txt @@ -30,7 +30,7 @@ Element 透过不同的方式让你掌控一切: 你可以与 Matrix 网络上的任何人聊天,不论他们是使用 Element、其他 Matrix 应用或其他通讯应用。 <b>超级安全</b> -真正的端到端加密(仅有那些在对话中的可以解密讯息)以及交叉签章装置验证。 +真正的端到端加密(仅有那些在对话中的人可以解密讯息)以及交叉签章装置验证。 <b>完整的通讯与整合</b> 信息传递、语音与视频通话、文件分享、画面分享与超多的整合、机器人与挂件。建构房间、社群、保持联络并完成工作。 diff --git a/fastlane/metadata/android/zh-CN/short_description.txt b/fastlane/metadata/android/zh-CN/short_description.txt index e271e7f9a4..8cfea85b90 100644 --- a/fastlane/metadata/android/zh-CN/short_description.txt +++ b/fastlane/metadata/android/zh-CN/short_description.txt @@ -1 +1 @@ -群组消息应用-加密的消息传递、群组聊天和视频通话 +群组消息应用——加密的消息传递、群组聊天和视频通话 From 73997d456923c1a9877896e8a6002f75897e0951 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= <riot@joeruut.com> Date: Fri, 30 Sep 2022 06:40:33 +0000 Subject: [PATCH 086/187] Translated using Weblate (Estonian) Currently translated at 100.0% (76 of 76 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/et/ --- fastlane/metadata/android/et/changelogs/40104360.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 fastlane/metadata/android/et/changelogs/40104360.txt diff --git a/fastlane/metadata/android/et/changelogs/40104360.txt b/fastlane/metadata/android/et/changelogs/40104360.txt new file mode 100644 index 0000000000..1c2733683d --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/40104360.txt @@ -0,0 +1,3 @@ +Testide alt saad sisse lülitada uue kujunduse - palun proovi seda! +Parandasime teavitustega seotud vigu ning andmete sünkroniseerimist pika viitega. +Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases From 28af6bb2080efff2662e2b985002277303a758ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= <riot@joeruut.com> Date: Sat, 1 Oct 2022 19:16:35 +0000 Subject: [PATCH 087/187] Translated using Weblate (Estonian) Currently translated at 99.6% (2411 of 2419 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/et/ --- .../src/main/res/values-et/strings.xml | 46 ++++++++++++++++++- 1 file changed, 44 insertions(+), 2 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 9bd1dd23b7..dbdbbdbb00 100644 --- a/library/ui-strings/src/main/res/values-et/strings.xml +++ b/library/ui-strings/src/main/res/values-et/strings.xml @@ -2594,7 +2594,7 @@ <string name="device_manager_settings_active_sessions_show_all">Näita kõiki sessioone (V2, WIP)</string> <string name="device_manager_sessions_other_description">Parima turvalisuse nimel verifitseeri kõik oma sessioonid ning logi välja neist, mida sa enam ei kasuta.</string> <string name="device_manager_sessions_other_title">Muud sessioonid</string> - <string name="settings_sessions_list">Sessionid</string> + <string name="settings_sessions_list">Sessioonid</string> <string name="a11y_open_spaces">Ava kogukondade loend</string> <string name="a11y_create_message">Alusta uut vestlust või loo uus jututuba</string> <string name="room_list_filter_people">Inimesed</string> @@ -2659,4 +2659,46 @@ <string name="invites_empty_message">Siin saavad olema sinu tulevased päringud ja kutsed.</string> <string name="a11y_collapse_space_children">Ahenda %s alamkogukonnad</string> <string name="a11y_expand_space_children">Näita %s alamkogukondi</string> -</resources> + <string name="device_manager_session_details_device_ip_address">IP-aadress</string> + <string name="device_manager_session_details_session_last_activity">Viimati kasutusel</string> + <string name="device_manager_session_details_session_name">Sessiooni nimi</string> + <string name="device_manager_session_details_description">Rakendus, seade ja kasutamise teave.</string> + <string name="device_manager_session_details_title">Sessiooni teave</string> + <string name="device_manager_other_sessions_clear_filter">Eemalda filter</string> + <string name="device_manager_other_sessions_no_inactive_sessions_found">Ei leidu sessioone, mis pole aktiivses kasutuses.</string> + <string name="device_manager_other_sessions_no_unverified_sessions_found">Verifitseerimata sessioone ei leidu.</string> + <string name="device_manager_other_sessions_no_verified_sessions_found">Verifitseeritud sessioone ei leidu.</string> + <plurals name="device_manager_other_sessions_recommendation_description_inactive"> + <item quantity="one">Kaalu vanadest ja kasutamata sessioonidest väljalogimist (vanemad kui %1$d või enam päeva).</item> + <item quantity="other">Kaalu vanadest ja kasutamata sessioonidest väljalogimist (vanemad kui %1$d või enam päeva).</item> + </plurals> + <string name="device_manager_other_sessions_recommendation_title_inactive">Pole pidevas kasutuses</string> + <string name="device_manager_other_sessions_recommendation_description_unverified">Turvalise sõnumvahetuse nimel verifitseeri kõik oma sessioonid ning logi neist välja, mida sa enam ei kasuta või ei tunne enam ära.</string> + <string name="device_manager_other_sessions_recommendation_title_unverified">Verifitseerimata</string> + <string name="device_manager_other_sessions_recommendation_description_verified">Parima turvalisuse nimel logi välja neist sessioonidest, mida sa enam ei kasuta või ei tunne ära.</string> + <string name="device_manager_other_sessions_recommendation_title_verified">Verifitseeritud</string> + <string name="a11y_device_manager_filter">Filtreeri</string> + <plurals name="device_manager_filter_option_inactive_description"> + <item quantity="one">Pole olnud kasutusel %1$d või enam päeva</item> + <item quantity="other">Pole olnud kasutusel %1$d või enam päeva</item> + </plurals> + <string name="device_manager_filter_option_inactive">Pole pidevas kasutuses</string> + <string name="device_manager_filter_option_unverified_description">Pole valmis turvaliseks sõnumivahetuseks</string> + <string name="device_manager_filter_option_unverified">Verifitseerimata</string> + <string name="device_manager_filter_option_verified_description">Valmis turvaliseks sõnumivahetuseks</string> + <string name="device_manager_filter_option_verified">Verifitseeritud</string> + <string name="device_manager_filter_option_all_sessions">Kõik sessioonid</string> + <string name="device_manager_filter_bottom_sheet_title">Filtreeri</string> + <string name="device_manager_session_last_activity">Viimati kasutusel %1$s</string> + <string name="device_manager_device_title">Seade</string> + <string name="device_manager_session_title">Sessioonid</string> + <string name="device_manager_current_session_title">Praegune sessioon</string> + <string name="device_manager_verification_status_detail_other_session_unverified">Parima turvalisuse ja töökindluse nimel verifitseeri see sessioon või logi ta võrgust välja.</string> + <string name="device_manager_verification_status_detail_current_session_unverified">Turvalise sõnumivahetuse nimel palun verifitseeri oma praegune sessioon.</string> + <string name="device_manager_verification_status_detail_other_session_verified">See sessioon on valmis turvaliseks sõnumivahetuseks.</string> + <string name="device_manager_verification_status_detail_current_session_verified">Sinu praegune sessioon on valmis turvaliseks sõnumivahetuseks.</string> + <string name="labs_enable_deferred_dm_summary">Alusta otsevestlust esimese sõnumiga</string> + <string name="labs_enable_deferred_dm_title">Võta kasutusele viivitusega otsevestlused</string> + <string name="labs_enable_new_app_layout_summary">Lihtsustatud Element valikuliste kaartidega</string> + <string name="labs_enable_new_app_layout_title">Võta kasutusele rakenduse uus välimus</string> +</resources> \ No newline at end of file From d8b3c66c62d5c190c43b90dfc1523dd89d34aed3 Mon Sep 17 00:00:00 2001 From: Szimszon <github@oregpreshaz.eu> Date: Sun, 2 Oct 2022 09:48:47 +0000 Subject: [PATCH 088/187] Translated using Weblate (Hungarian) Currently translated at 100.0% (76 of 76 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/40104360.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 fastlane/metadata/android/hu-HU/changelogs/40104360.txt diff --git a/fastlane/metadata/android/hu-HU/changelogs/40104360.txt b/fastlane/metadata/android/hu-HU/changelogs/40104360.txt new file mode 100644 index 0000000000..a63a8d1a83 --- /dev/null +++ b/fastlane/metadata/android/hu-HU/changelogs/40104360.txt @@ -0,0 +1,3 @@ +Az új alkalmazás megjelenés a Laborokban bekapcsolható. Próbáld ki! +Hiányzó értesítések és hosszú inkrementális szinkronizáció javítása. +Teljes változásnapló: https://github.com/vector-im/element-android/releases From 0b0ea64a4bd7ed14212ce0cc7da9de92b760a583 Mon Sep 17 00:00:00 2001 From: Florian Renaud <florianr@element.io> Date: Wed, 21 Sep 2022 11:50:16 +0200 Subject: [PATCH 089/187] Add Voice Broadcast feature flag --- .../app/features/debug/features/DebugFeaturesStateFactory.kt | 5 +++++ .../app/features/debug/features/DebugVectorFeatures.kt | 4 ++++ .../src/main/java/im/vector/app/features/VectorFeatures.kt | 2 ++ 3 files changed, 11 insertions(+) diff --git a/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt b/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt index 9118dea1e3..b927d66b69 100644 --- a/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt +++ b/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt @@ -90,6 +90,11 @@ class DebugFeaturesStateFactory @Inject constructor( key = DebugFeatureKeys.newDeviceManagementEnabled, factory = VectorFeatures::isNewDeviceManagementEnabled ), + createBooleanFeature( + label = "Enable Voice Broadcast", + key = DebugFeatureKeys.voiceBroadcastEnabled, + factory = VectorFeatures::isVoiceBroadcastEnabled + ), ) ) } diff --git a/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt b/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt index c01c058fc6..c347accfc3 100644 --- a/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt +++ b/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt @@ -79,6 +79,9 @@ class DebugVectorFeatures( override fun isNewDeviceManagementEnabled(): Boolean = read(DebugFeatureKeys.newDeviceManagementEnabled) ?: vectorFeatures.isNewDeviceManagementEnabled() + override fun isVoiceBroadcastEnabled(): Boolean = read(DebugFeatureKeys.voiceBroadcastEnabled) + ?: vectorFeatures.isVoiceBroadcastEnabled() + fun <T> override(value: T?, key: Preferences.Key<T>) = updatePreferences { if (value == null) { it.remove(key) @@ -140,4 +143,5 @@ object DebugFeatureKeys { val startDmOnFirstMsg = booleanPreferencesKey("start-dm-on-first-msg") val newAppLayoutEnabled = booleanPreferencesKey("new-app-layout-enabled") val newDeviceManagementEnabled = booleanPreferencesKey("new-device-management-enabled") + val voiceBroadcastEnabled = booleanPreferencesKey("voice-broadcast-enabled") } diff --git a/vector/src/main/java/im/vector/app/features/VectorFeatures.kt b/vector/src/main/java/im/vector/app/features/VectorFeatures.kt index e1c083db29..9c3ebae641 100644 --- a/vector/src/main/java/im/vector/app/features/VectorFeatures.kt +++ b/vector/src/main/java/im/vector/app/features/VectorFeatures.kt @@ -41,6 +41,7 @@ interface VectorFeatures { */ fun isNewAppLayoutFeatureEnabled(): Boolean fun isNewDeviceManagementEnabled(): Boolean + fun isVoiceBroadcastEnabled(): Boolean } class DefaultVectorFeatures : VectorFeatures { @@ -57,4 +58,5 @@ class DefaultVectorFeatures : VectorFeatures { override fun forceUsageOfOpusEncoder(): Boolean = false override fun isNewAppLayoutFeatureEnabled(): Boolean = true override fun isNewDeviceManagementEnabled(): Boolean = false + override fun isVoiceBroadcastEnabled(): Boolean = false } From 2bc08069cc1593663f0f4ef6187c5360af007af6 Mon Sep 17 00:00:00 2001 From: Florian Renaud <florianr@element.io> Date: Wed, 21 Sep 2022 15:51:44 +0200 Subject: [PATCH 090/187] Add Voice Broadcast action in the composer --- .../src/main/res/values/strings.xml | 2 ++ .../vector/app/core/utils/PermissionsTools.kt | 1 + .../attachments/AttachmentTypeSelectorView.kt | 6 +++++- .../home/room/detail/RoomDetailAction.kt | 1 + .../home/room/detail/TimelineFragment.kt | 5 +++++ .../home/room/detail/TimelineViewModel.kt | 6 ++++++ .../ic_attachment_voice_broadcast.xml | 21 +++++++++++++++++++ .../layout/view_attachment_type_selector.xml | 10 +++++++++ 8 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 vector/src/main/res/drawable/ic_attachment_voice_broadcast.xml diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 4ff7aae750..a1d4bd302e 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -1871,6 +1871,7 @@ <string name="attachment_type_sticker">"Sticker"</string> <string name="attachment_type_poll">Poll</string> <string name="attachment_type_location">Location</string> + <string name="attachment_type_voice_broadcast">Voice Broadcast</string> <string name="rotate_and_crop_screen_title">Rotate and crop</string> <string name="error_handling_incoming_share">Couldn\'t handle share data</string> @@ -3179,6 +3180,7 @@ <string name="tooltip_attachment_contact">Open contacts</string> <string name="tooltip_attachment_poll">Create poll</string> <string name="tooltip_attachment_location">Share location</string> + <string name="tooltip_attachment_voice_broadcast">Start a voice broadcast</string> <string name="message_reaction_show_less">Show less</string> <plurals name="message_reaction_show_more"> diff --git a/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt b/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt index 9ad95d3c55..a287626671 100644 --- a/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt +++ b/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt @@ -42,6 +42,7 @@ val PERMISSIONS_FOR_ROOM_AVATAR = listOf(Manifest.permission.CAMERA) val PERMISSIONS_FOR_WRITING_FILES = listOf(Manifest.permission.WRITE_EXTERNAL_STORAGE) val PERMISSIONS_FOR_PICKING_CONTACT = listOf(Manifest.permission.READ_CONTACTS) val PERMISSIONS_FOR_FOREGROUND_LOCATION_SHARING = listOf(Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION) +val PERMISSIONS_FOR_VOICE_BROADCAST = listOf(Manifest.permission.RECORD_AUDIO) // This is not ideal to store the value like that, but it works private var permissionDialogDisplayed = false diff --git a/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt b/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt index c85c3aa6b5..8536b765d4 100644 --- a/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt +++ b/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt @@ -40,6 +40,7 @@ import im.vector.app.core.utils.PERMISSIONS_EMPTY import im.vector.app.core.utils.PERMISSIONS_FOR_FOREGROUND_LOCATION_SHARING import im.vector.app.core.utils.PERMISSIONS_FOR_PICKING_CONTACT import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO +import im.vector.app.core.utils.PERMISSIONS_FOR_VOICE_BROADCAST import im.vector.app.databinding.ViewAttachmentTypeSelectorBinding import im.vector.app.features.attachments.AttachmentTypeSelectorView.Callback import kotlin.math.max @@ -75,6 +76,7 @@ class AttachmentTypeSelectorView( views.attachmentContactButton.configure(Type.CONTACT) views.attachmentPollButton.configure(Type.POLL) views.attachmentLocationButton.configure(Type.LOCATION) + views.attachmentVoiceBroadcast.configure(Type.VOICE_BROADCAST) width = LinearLayout.LayoutParams.MATCH_PARENT height = LinearLayout.LayoutParams.WRAP_CONTENT animationStyle = 0 @@ -134,6 +136,7 @@ class AttachmentTypeSelectorView( Type.CONTACT -> views.attachmentContactButton Type.POLL -> views.attachmentPollButton Type.LOCATION -> views.attachmentLocationButton + Type.VOICE_BROADCAST -> views.attachmentVoiceBroadcast }.let { it.isVisible = isVisible } @@ -221,6 +224,7 @@ class AttachmentTypeSelectorView( STICKER(PERMISSIONS_EMPTY, R.string.tooltip_attachment_sticker), CONTACT(PERMISSIONS_FOR_PICKING_CONTACT, R.string.tooltip_attachment_contact), POLL(PERMISSIONS_EMPTY, R.string.tooltip_attachment_poll), - LOCATION(PERMISSIONS_FOR_FOREGROUND_LOCATION_SHARING, R.string.tooltip_attachment_location) + LOCATION(PERMISSIONS_FOR_FOREGROUND_LOCATION_SHARING, R.string.tooltip_attachment_location), + VOICE_BROADCAST(PERMISSIONS_FOR_VOICE_BROADCAST, R.string.tooltip_attachment_voice_broadcast), } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt index c1e3b58a80..10708d2290 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt @@ -79,6 +79,7 @@ sealed class RoomDetailAction : VectorViewModelAction { data class ReRequestKeys(val eventId: String) : RoomDetailAction() object SelectStickerAttachment : RoomDetailAction() + object StartVoiceBroadcast : RoomDetailAction() object OpenIntegrationManager : RoomDetailAction() object ManageIntegrations : RoomDetailAction() data class AddJitsiWidget(val withVideo: Boolean) : RoomDetailAction() 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 bba607eeb4..f758d4a684 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 @@ -1575,6 +1575,10 @@ class TimelineFragment : attachmentTypeSelector.setAttachmentVisibility( AttachmentTypeSelectorView.Type.POLL, !isThreadTimeLine() ) + attachmentTypeSelector.setAttachmentVisibility( + AttachmentTypeSelectorView.Type.VOICE_BROADCAST, + vectorFeatures.isVoiceBroadcastEnabled(), // TODO check user permission + ) } attachmentTypeSelector.show(views.composerLayout.views.attachmentButton) } @@ -2668,6 +2672,7 @@ class TimelineFragment : locationOwnerId = session.myUserId ) } + AttachmentTypeSelectorView.Type.VOICE_BROADCAST -> timelineViewModel.handle(RoomDetailAction.StartVoiceBroadcast) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt index 02dd2604e1..4bed477711 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt @@ -456,6 +456,7 @@ class TimelineViewModel @AssistedInject constructor( is RoomDetailAction.ReRequestKeys -> handleReRequestKeys(action) is RoomDetailAction.TapOnFailedToDecrypt -> handleTapOnFailedToDecrypt(action) is RoomDetailAction.SelectStickerAttachment -> handleSelectStickerAttachment() + is RoomDetailAction.StartVoiceBroadcast -> handleStartVoiceBroadcast() is RoomDetailAction.OpenIntegrationManager -> handleOpenIntegrationManager() is RoomDetailAction.StartCall -> handleStartCall(action) is RoomDetailAction.AcceptCall -> handleAcceptCall(action) @@ -597,6 +598,11 @@ class TimelineViewModel @AssistedInject constructor( } } + private fun handleStartVoiceBroadcast() { + // Todo implement start voice broadcast action + Timber.d("Start voice broadcast clicked") + } + private fun handleOpenIntegrationManager() { viewModelScope.launch { val viewEvent = withContext(Dispatchers.Default) { diff --git a/vector/src/main/res/drawable/ic_attachment_voice_broadcast.xml b/vector/src/main/res/drawable/ic_attachment_voice_broadcast.xml new file mode 100644 index 0000000000..3e93522b18 --- /dev/null +++ b/vector/src/main/res/drawable/ic_attachment_voice_broadcast.xml @@ -0,0 +1,21 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M20.189,4.187C19.85,3.751 19.222,3.672 18.785,4.011C18.35,4.35 18.271,4.977 18.609,5.413L18.61,5.414L18.611,5.415L18.624,5.434C18.638,5.451 18.659,5.48 18.687,5.52C18.744,5.6 18.828,5.722 18.931,5.884C19.137,6.207 19.415,6.683 19.693,7.281C20.253,8.481 20.799,10.134 20.799,12.001C20.799,13.867 20.253,15.52 19.693,16.721C19.415,17.318 19.137,17.794 18.931,18.117C18.828,18.279 18.744,18.401 18.687,18.481C18.659,18.521 18.638,18.55 18.624,18.568L18.611,18.586L18.61,18.587L18.609,18.588C18.271,19.024 18.35,19.651 18.785,19.99C19.222,20.329 19.85,20.25 20.189,19.815L19.444,19.235C20.189,19.815 20.189,19.815 20.189,19.815L20.191,19.812L20.194,19.808L20.202,19.797L20.229,19.762C20.25,19.733 20.281,19.691 20.318,19.639C20.393,19.534 20.496,19.383 20.618,19.191C20.862,18.807 21.184,18.255 21.506,17.566C22.145,16.195 22.799,14.248 22.799,12.001C22.799,9.753 22.145,7.806 21.506,6.435C21.184,5.746 20.862,5.194 20.618,4.81C20.496,4.618 20.393,4.467 20.318,4.362C20.281,4.31 20.25,4.268 20.229,4.239L20.202,4.204L20.194,4.193L20.191,4.189L20.19,4.188C20.19,4.188 20.189,4.187 19.399,4.801L20.189,4.187Z" + android:fillColor="#737D8C" /> + <path + android:pathData="M17.59,7.787C17.251,7.351 16.622,7.272 16.186,7.611C15.752,7.95 15.672,8.576 16.008,9.011L16.012,9.016C16.016,9.022 16.025,9.033 16.037,9.05C16.06,9.084 16.098,9.138 16.144,9.211C16.237,9.357 16.365,9.576 16.494,9.852C16.754,10.41 17,11.163 17,12.001C17,12.839 16.754,13.592 16.494,14.149C16.365,14.425 16.237,14.644 16.144,14.791C16.098,14.864 16.06,14.918 16.037,14.951C16.025,14.968 16.016,14.98 16.012,14.986L16.008,14.99C15.672,15.426 15.752,16.052 16.186,16.39C16.622,16.729 17.251,16.651 17.59,16.215L16.8,15.601C17.59,16.215 17.59,16.215 17.59,16.215L17.591,16.212L17.594,16.21L17.599,16.202L17.616,16.18C17.629,16.163 17.646,16.139 17.667,16.11C17.709,16.051 17.765,15.968 17.831,15.865C17.963,15.657 18.135,15.362 18.306,14.995C18.646,14.267 19,13.22 19,12.001C19,10.782 18.646,9.735 18.306,9.006C18.135,8.639 17.963,8.344 17.831,8.137C17.765,8.033 17.709,7.951 17.667,7.892C17.646,7.863 17.629,7.839 17.616,7.821L17.599,7.799L17.594,7.792L17.591,7.789L17.59,7.788C17.59,7.788 17.59,7.787 16.8,8.401L17.59,7.787Z" + android:fillColor="#737D8C" /> + <path + android:pathData="M3.611,19.815C3.95,20.25 4.578,20.329 5.014,19.99C5.45,19.651 5.528,19.024 5.191,18.588L5.19,18.587L5.189,18.586L5.175,18.568C5.162,18.55 5.141,18.521 5.112,18.481C5.056,18.401 4.971,18.279 4.869,18.117C4.663,17.794 4.385,17.318 4.106,16.721C3.546,15.52 3,13.867 3,12C3,10.134 3.546,8.481 4.106,7.281C4.385,6.683 4.663,6.207 4.869,5.884C4.971,5.722 5.056,5.6 5.112,5.52C5.141,5.48 5.162,5.451 5.175,5.433L5.189,5.415L5.19,5.414L5.191,5.413C5.528,4.977 5.449,4.35 5.014,4.011C4.578,3.672 3.95,3.751 3.611,4.187L4.356,4.766C3.611,4.187 3.611,4.187 3.611,4.187L3.609,4.189L3.606,4.193L3.598,4.204L3.571,4.239C3.549,4.268 3.519,4.31 3.482,4.362C3.407,4.467 3.304,4.618 3.181,4.81C2.937,5.194 2.615,5.746 2.294,6.435C1.654,7.806 1,9.753 1,12C1,14.248 1.654,16.195 2.294,17.566C2.615,18.255 2.937,18.807 3.181,19.191C3.304,19.383 3.407,19.534 3.482,19.639C3.519,19.691 3.549,19.733 3.571,19.762L3.598,19.797L3.606,19.808L3.609,19.812L3.61,19.813C3.61,19.813 3.611,19.815 4.4,19.201L3.611,19.815Z" + android:fillColor="#737D8C" /> + <path + android:pathData="M6.21,16.214C6.549,16.65 7.177,16.729 7.613,16.39C8.048,16.052 8.127,15.426 7.791,14.99L7.788,14.985C7.783,14.979 7.775,14.968 7.763,14.951C7.739,14.917 7.702,14.863 7.655,14.79C7.562,14.644 7.434,14.425 7.305,14.149C7.045,13.591 6.799,12.838 6.799,12C6.799,11.162 7.045,10.409 7.305,9.852C7.434,9.576 7.562,9.357 7.655,9.21C7.702,9.137 7.739,9.083 7.763,9.05C7.775,9.033 7.783,9.021 7.788,9.015L7.791,9.011C8.127,8.575 8.048,7.949 7.613,7.611C7.177,7.272 6.549,7.35 6.21,7.786L6.999,8.4C6.21,7.786 6.21,7.786 6.21,7.786L6.208,7.789L6.206,7.792L6.2,7.799L6.184,7.821C6.171,7.838 6.153,7.862 6.132,7.892C6.091,7.951 6.034,8.033 5.968,8.137C5.836,8.344 5.664,8.639 5.493,9.006C5.153,9.734 4.799,10.781 4.799,12C4.799,13.219 5.153,14.267 5.493,14.995C5.664,15.362 5.836,15.657 5.968,15.864C6.034,15.968 6.091,16.05 6.132,16.109C6.153,16.139 6.171,16.162 6.184,16.18L6.2,16.202L6.206,16.209L6.208,16.212L6.209,16.213C6.209,16.213 6.21,16.214 6.999,15.6L6.21,16.214Z" + android:fillColor="#737D8C" /> + <path + android:pathData="M12,12m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0" + android:fillColor="#737D8C" /> +</vector> diff --git a/vector/src/main/res/layout/view_attachment_type_selector.xml b/vector/src/main/res/layout/view_attachment_type_selector.xml index c108dfd96e..6ce96111e9 100644 --- a/vector/src/main/res/layout/view_attachment_type_selector.xml +++ b/vector/src/main/res/layout/view_attachment_type_selector.xml @@ -63,6 +63,16 @@ android:src="@drawable/ic_attachment_file" app:tint="?colorPrimary" /> + <ImageButton + android:id="@+id/attachmentVoiceBroadcast" + android:layout_width="@dimen/layout_touch_size" + android:layout_height="@dimen/layout_touch_size" + android:layout_marginStart="2dp" + android:background="?android:attr/selectableItemBackground" + android:contentDescription="@string/attachment_type_voice_broadcast" + android:src="@drawable/ic_attachment_voice_broadcast" + app:tint="?colorPrimary" /> + <ImageButton android:id="@+id/attachmentPollButton" android:layout_width="@dimen/layout_touch_size" From 2812b16cadefd703ba56437c5f69519a83cd9679 Mon Sep 17 00:00:00 2001 From: Florian Renaud <florianr@element.io> Date: Thu, 29 Sep 2022 16:33:02 +0200 Subject: [PATCH 091/187] Add changelog file --- changelog.d/7258.wip | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7258.wip diff --git a/changelog.d/7258.wip b/changelog.d/7258.wip new file mode 100644 index 0000000000..ee4c2b85f3 --- /dev/null +++ b/changelog.d/7258.wip @@ -0,0 +1 @@ +[Voice Broadcast] Add a feature flag with the composer action From 9f8c7688bfbfd2fd005bb4f013c7c57c6f00c9cc Mon Sep 17 00:00:00 2001 From: Nikita Fedrunov <66663241+fedrunov@users.noreply.github.com> Date: Mon, 3 Oct 2022 11:47:58 +0200 Subject: [PATCH 092/187] added analytics for app layout (#7242) --- changelog.d/6508.misc | 1 + .../im/vector/app/SpaceStateHandlerImpl.kt | 8 +++++ .../analytics/extensions/UserPropertiesExt.kt | 10 ++++++ .../room/list/home/HomeRoomListViewModel.kt | 5 +++ .../home/header/HomeRoomsHeadersController.kt | 10 +++++- .../list/home/header/RoomFilterHeaderItem.kt | 23 +++++++++++++ .../room/list/home/invites/InvitesFragment.kt | 6 ++++ .../HomeLayoutSettingBottomDialogFragment.kt | 33 +++++++++++++++++++ .../spaces/NewSpaceSummaryController.kt | 10 +++--- .../app/features/spaces/SpaceListAction.kt | 2 +- .../features/spaces/SpaceListBottomSheet.kt | 6 ++++ .../app/features/spaces/SpaceListFragment.kt | 4 +-- .../app/features/spaces/SpaceListViewModel.kt | 13 +++++++- .../features/spaces/SpaceSummaryController.kt | 8 ++--- .../spaces/create/ChooseSpaceTypeFragment.kt | 6 ++++ .../spaces/create/CreateSpaceViewModel.kt | 12 ++++++- .../app/test/fakes/FakeAnalyticsTracker.kt | 2 +- 17 files changed, 144 insertions(+), 15 deletions(-) create mode 100644 changelog.d/6508.misc diff --git a/changelog.d/6508.misc b/changelog.d/6508.misc new file mode 100644 index 0000000000..096fb750c2 --- /dev/null +++ b/changelog.d/6508.misc @@ -0,0 +1 @@ +[AppLayout]: added tracking of new analytics events diff --git a/vector/src/main/java/im/vector/app/SpaceStateHandlerImpl.kt b/vector/src/main/java/im/vector/app/SpaceStateHandlerImpl.kt index a665b619c0..d230e76c1e 100644 --- a/vector/src/main/java/im/vector/app/SpaceStateHandlerImpl.kt +++ b/vector/src/main/java/im/vector/app/SpaceStateHandlerImpl.kt @@ -22,6 +22,7 @@ import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.utils.BehaviorDataSource import im.vector.app.features.analytics.AnalyticsTracker import im.vector.app.features.analytics.plan.UserProperties +import im.vector.app.features.analytics.plan.ViewRoom import im.vector.app.features.session.coroutineScope import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.ui.UiStateRepository @@ -82,6 +83,13 @@ class SpaceStateHandlerImpl @Inject constructor( return } + analyticsTracker.capture( + ViewRoom( + isDM = false, + isSpace = true, + ) + ) + if (isForwardNavigation) { addToBackstack(spaceToLeave, spaceToSet) } diff --git a/vector/src/main/java/im/vector/app/features/analytics/extensions/UserPropertiesExt.kt b/vector/src/main/java/im/vector/app/features/analytics/extensions/UserPropertiesExt.kt index e5446f438b..0ff04f0854 100644 --- a/vector/src/main/java/im/vector/app/features/analytics/extensions/UserPropertiesExt.kt +++ b/vector/src/main/java/im/vector/app/features/analytics/extensions/UserPropertiesExt.kt @@ -17,6 +17,7 @@ package im.vector.app.features.analytics.extensions import im.vector.app.features.analytics.plan.UserProperties +import im.vector.app.features.home.room.list.home.header.HomeRoomFilter import im.vector.app.features.onboarding.FtueUseCase fun FtueUseCase.toTrackingValue(): UserProperties.FtueUseCaseSelection { @@ -27,3 +28,12 @@ fun FtueUseCase.toTrackingValue(): UserProperties.FtueUseCaseSelection { FtueUseCase.SKIP -> UserProperties.FtueUseCaseSelection.Skip } } + +fun HomeRoomFilter.toTrackingValue(): UserProperties.AllChatsActiveFilter { + return when (this) { + HomeRoomFilter.ALL -> UserProperties.AllChatsActiveFilter.All + HomeRoomFilter.UNREADS -> UserProperties.AllChatsActiveFilter.Unreads + HomeRoomFilter.FAVOURITES -> UserProperties.AllChatsActiveFilter.Favourites + HomeRoomFilter.PEOPlE -> UserProperties.AllChatsActiveFilter.People + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt index 7f62c68850..734fc102bf 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt @@ -34,6 +34,9 @@ import im.vector.app.core.platform.StateView import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.DrawableProvider import im.vector.app.core.resources.StringProvider +import im.vector.app.features.analytics.AnalyticsTracker +import im.vector.app.features.analytics.extensions.toTrackingValue +import im.vector.app.features.analytics.plan.UserProperties import im.vector.app.features.displayname.getBestName import im.vector.app.features.home.room.list.home.header.HomeRoomFilter import kotlinx.coroutines.flow.Flow @@ -74,6 +77,7 @@ class HomeRoomListViewModel @AssistedInject constructor( private val preferencesStore: HomeLayoutPreferencesStore, private val stringProvider: StringProvider, private val drawableProvider: DrawableProvider, + private val analyticsTracker: AnalyticsTracker, ) : VectorViewModel<HomeRoomListViewState, HomeRoomListAction, HomeRoomListViewEvents>(initialState) { @AssistedFactory @@ -358,6 +362,7 @@ class HomeRoomListViewModel @AssistedInject constructor( } setState { copy(headersData = headersData.copy(currentFilter = newFilter)) } updateEmptyState() + analyticsTracker.updateUserProperties(UserProperties(allChatsActiveFilter = newFilter.toTrackingValue())) filteredPagedRoomSummariesLive?.let { liveResults -> liveResults.queryParams = getFilteredQueryParams(newFilter, liveResults.queryParams) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/header/HomeRoomsHeadersController.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/header/HomeRoomsHeadersController.kt index 56cccd9c36..3cc058985a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/header/HomeRoomsHeadersController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/header/HomeRoomsHeadersController.kt @@ -28,6 +28,7 @@ import com.google.android.material.color.MaterialColors import im.vector.app.R import im.vector.app.core.resources.StringProvider import im.vector.app.core.utils.FirstItemUpdatedObserver +import im.vector.app.features.analytics.AnalyticsTracker import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.list.RoomListListener import org.matrix.android.sdk.api.session.room.model.RoomSummary @@ -38,6 +39,7 @@ class HomeRoomsHeadersController @Inject constructor( val stringProvider: StringProvider, private val avatarRenderer: AvatarRenderer, resources: Resources, + private val analyticsTracker: AnalyticsTracker, ) : EpoxyController() { private var data: RoomsHeadersData = RoomsHeadersData() @@ -73,7 +75,11 @@ class HomeRoomsHeadersController @Inject constructor( } host.data.filtersList?.let { - addRoomFilterHeaderItem(host.onFilterChangedListener, it, host.data.currentFilter) + addRoomFilterHeaderItem( + filterChangedListener = host.onFilterChangedListener, + filtersList = it, + currentFilter = host.data.currentFilter, + analyticsTracker = analyticsTracker) } } @@ -158,12 +164,14 @@ class HomeRoomsHeadersController @Inject constructor( filterChangedListener: ((HomeRoomFilter) -> Unit)?, filtersList: List<HomeRoomFilter>, currentFilter: HomeRoomFilter?, + analyticsTracker: AnalyticsTracker, ) { roomFilterHeaderItem { id("filter_header") filtersData(filtersList) selectedFilter(currentFilter) onFilterChangedListener(filterChangedListener) + analyticsTracker(analyticsTracker) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/header/RoomFilterHeaderItem.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/header/RoomFilterHeaderItem.kt index ed99b51681..fd4333b722 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/header/RoomFilterHeaderItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/header/RoomFilterHeaderItem.kt @@ -22,6 +22,8 @@ import com.google.android.material.tabs.TabLayout import im.vector.app.R import im.vector.app.core.epoxy.VectorEpoxyHolder import im.vector.app.core.epoxy.VectorEpoxyModel +import im.vector.app.features.analytics.AnalyticsTracker +import im.vector.app.features.analytics.plan.Interaction @EpoxyModelClass abstract class RoomFilterHeaderItem : VectorEpoxyModel<RoomFilterHeaderItem.Holder>(R.layout.item_home_filter_tabs) { @@ -35,6 +37,9 @@ abstract class RoomFilterHeaderItem : VectorEpoxyModel<RoomFilterHeaderItem.Hold @EpoxyAttribute var selectedFilter: HomeRoomFilter? = null + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) + var analyticsTracker: AnalyticsTracker? = null + override fun bind(holder: Holder) { super.bind(holder) with(holder.tabLayout) { @@ -51,6 +56,7 @@ abstract class RoomFilterHeaderItem : VectorEpoxyModel<RoomFilterHeaderItem.Hold addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { override fun onTabSelected(tab: TabLayout.Tab?) { (tab?.tag as? HomeRoomFilter)?.let { filter -> + trackFilterChangeEvent(filter) onFilterChangedListener?.invoke(filter) } } @@ -61,6 +67,23 @@ abstract class RoomFilterHeaderItem : VectorEpoxyModel<RoomFilterHeaderItem.Hold } } + private fun trackFilterChangeEvent(filter: HomeRoomFilter) { + val interactionName = when (filter) { + HomeRoomFilter.ALL -> Interaction.Name.MobileAllChatsFilterAll + HomeRoomFilter.UNREADS -> Interaction.Name.MobileAllChatsFilterUnreads + HomeRoomFilter.FAVOURITES -> Interaction.Name.MobileAllChatsFilterFavourites + HomeRoomFilter.PEOPlE -> Interaction.Name.MobileAllChatsFilterPeople + } + + analyticsTracker?.capture( + Interaction( + index = null, + interactionType = null, + name = interactionName + ) + ) + } + override fun unbind(holder: Holder) { holder.tabLayout.clearOnTabSelectedListeners() super.unbind(holder) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesFragment.kt index 0dbc1b8f34..ac39d7d567 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesFragment.kt @@ -27,6 +27,7 @@ import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.StateView import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentInvitesBinding +import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.analytics.plan.ViewRoom import im.vector.app.features.home.room.list.RoomListListener import im.vector.app.features.notifications.NotificationDrawerManager @@ -48,6 +49,11 @@ class InvitesFragment : VectorBaseFragment<FragmentInvitesBinding>(), RoomListLi return FragmentInvitesBinding.inflate(inflater, container, false) } + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + analyticsScreenName = MobileScreen.ScreenName.Invites + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/layout/HomeLayoutSettingBottomDialogFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/layout/HomeLayoutSettingBottomDialogFragment.kt index 0c4d64a1cc..63b7f557e3 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/layout/HomeLayoutSettingBottomDialogFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/layout/HomeLayoutSettingBottomDialogFragment.kt @@ -25,6 +25,7 @@ import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment import im.vector.app.databinding.BottomSheetHomeLayoutSettingsBinding +import im.vector.app.features.analytics.plan.Interaction import im.vector.app.features.home.room.list.home.HomeLayoutPreferencesStore import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch @@ -54,9 +55,11 @@ class HomeLayoutSettingBottomDialogFragment : VectorBaseBottomSheetDialogFragmen } views.homeLayoutSettingsRecents.setOnCheckedChangeListener { _, isChecked -> + trackRecentsStateEvent(isChecked) setRecentsEnabled(isChecked) } views.homeLayoutSettingsFilters.setOnCheckedChangeListener { _, isChecked -> + trackFiltersStateEvent(isChecked) setFiltersEnabled(isChecked) } views.homeLayoutSettingsSortGroup.setOnCheckedChangeListener { _, checkedId -> @@ -64,10 +67,40 @@ class HomeLayoutSettingBottomDialogFragment : VectorBaseBottomSheetDialogFragmen } } + private fun trackRecentsStateEvent(areEnabled: Boolean) { + val interactionName = if (areEnabled) { + Interaction.Name.MobileAllChatsRecentsEnabled + } else { + Interaction.Name.MobileAllChatsRecentsDisabled + } + analyticsTracker.capture( + Interaction( + index = null, + interactionType = null, + name = interactionName + ) + ) + } + private fun setRecentsEnabled(isEnabled: Boolean) = lifecycleScope.launch { preferencesStore.setRecentsEnabled(isEnabled) } + private fun trackFiltersStateEvent(areEnabled: Boolean) { + val interactionName = if (areEnabled) { + Interaction.Name.MobileAllChatsFiltersEnabled + } else { + Interaction.Name.MobileAllChatsFiltersDisabled + } + analyticsTracker.capture( + Interaction( + index = null, + interactionType = null, + name = interactionName + ) + ) + } + private fun setFiltersEnabled(isEnabled: Boolean) = lifecycleScope.launch { preferencesStore.setFiltersEnabled(isEnabled) } diff --git a/vector/src/main/java/im/vector/app/features/spaces/NewSpaceSummaryController.kt b/vector/src/main/java/im/vector/app/features/spaces/NewSpaceSummaryController.kt index 5061eb4036..199169484c 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/NewSpaceSummaryController.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/NewSpaceSummaryController.kt @@ -72,7 +72,7 @@ class NewSpaceSummaryController @Inject constructor( text(host.stringProvider.getString(R.string.all_chats)) selected(selected) countState(UnreadCounterBadgeView.State.Count(homeCount.totalCount, homeCount.isHighlight)) - listener { host.callback?.onSpaceSelected(null) } + listener { host.callback?.onSpaceSelected(null, isSubSpace = false) } } } @@ -99,7 +99,7 @@ class NewSpaceSummaryController @Inject constructor( hasChildren(hasChildren) matrixItem(spaceSummary.toMatrixItem()) onLongClickListener { host.callback?.onSpaceSettings(spaceSummary) } - onSpaceSelectedListener { host.callback?.onSpaceSelected(spaceSummary) } + onSpaceSelectedListener { host.callback?.onSpaceSelected(spaceSummary, isSubSpace = false) } onToggleExpandListener { host.callback?.onToggleExpand(spaceSummary) } selected(isSelected) } @@ -140,7 +140,7 @@ class NewSpaceSummaryController @Inject constructor( indent(depth) matrixItem(childSummary.toMatrixItem()) onLongClickListener { host.callback?.onSpaceSettings(childSummary) } - onSubSpaceSelectedListener { host.callback?.onSpaceSelected(childSummary) } + onSubSpaceSelectedListener { host.callback?.onSpaceSelected(childSummary, isSubSpace = true) } onToggleExpandListener { host.callback?.onToggleExpand(childSummary) } selected(isSelected) } @@ -184,8 +184,10 @@ class NewSpaceSummaryController @Inject constructor( } } + /** + * This is a full duplicate of [SpaceSummaryController.Callback]. We need to merge them ASAP*/ interface Callback { - fun onSpaceSelected(spaceSummary: RoomSummary?) + fun onSpaceSelected(spaceSummary: RoomSummary?, isSubSpace: Boolean) fun onSpaceInviteSelected(spaceSummary: RoomSummary) fun onSpaceSettings(spaceSummary: RoomSummary) fun onToggleExpand(spaceSummary: RoomSummary) diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceListAction.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceListAction.kt index fd2e68e172..1ef755e684 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceListAction.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceListAction.kt @@ -20,7 +20,7 @@ import im.vector.app.core.platform.VectorViewModelAction import org.matrix.android.sdk.api.session.room.model.RoomSummary sealed class SpaceListAction : VectorViewModelAction { - data class SelectSpace(val spaceSummary: RoomSummary?) : SpaceListAction() + data class SelectSpace(val spaceSummary: RoomSummary?, val isSubSpace: Boolean) : SpaceListAction() data class OpenSpaceInvite(val spaceSummary: RoomSummary) : SpaceListAction() data class LeaveSpace(val spaceSummary: RoomSummary) : SpaceListAction() data class ToggleExpand(val spaceSummary: RoomSummary) : SpaceListAction() diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceListBottomSheet.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceListBottomSheet.kt index 4787aed8ae..9991384643 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceListBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceListBottomSheet.kt @@ -25,6 +25,7 @@ import im.vector.app.R import im.vector.app.core.extensions.replaceChildFragment import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment import im.vector.app.databinding.FragmentSpacesBottomSheetBinding +import im.vector.app.features.analytics.plan.MobileScreen class SpaceListBottomSheet : VectorBaseBottomSheetDialogFragment<FragmentSpacesBottomSheetBinding>() { @@ -32,6 +33,11 @@ class SpaceListBottomSheet : VectorBaseBottomSheetDialogFragment<FragmentSpacesB return FragmentSpacesBottomSheetBinding.inflate(inflater, container, false) } + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + analyticsScreenName = MobileScreen.ScreenName.SpaceBottomSheet + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { if (savedInstanceState == null) { replaceChildFragment(R.id.space_list, SpaceListFragment::class.java) diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceListFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceListFragment.kt index 27a118e4dc..550db1d0d1 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceListFragment.kt @@ -176,8 +176,8 @@ class SpaceListFragment : } } - override fun onSpaceSelected(spaceSummary: RoomSummary?) { - viewModel.handle(SpaceListAction.SelectSpace(spaceSummary)) + override fun onSpaceSelected(spaceSummary: RoomSummary?, isSubSpace: Boolean) { + viewModel.handle(SpaceListAction.SelectSpace(spaceSummary, isSubSpace = isSubSpace)) roomListSharedActionViewModel.post(RoomListSharedAction.CloseBottomSheet) } diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceListViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceListViewModel.kt index 9786d80ae7..99f6a254b8 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceListViewModel.kt @@ -215,7 +215,18 @@ class SpaceListViewModel @AssistedInject constructor( private fun handleSelectSpace(action: SpaceListAction.SelectSpace) = withState { state -> if (state.selectedSpace?.roomId != action.spaceSummary?.roomId) { - analyticsTracker.capture(Interaction(null, null, Interaction.Name.SpacePanelSwitchSpace)) + val interactionName = if (action.isSubSpace) { + Interaction.Name.SpacePanelSwitchSubSpace + } else { + Interaction.Name.SpacePanelSwitchSpace + } + analyticsTracker.capture( + Interaction( + index = null, + interactionType = null, + name = interactionName + ) + ) setState { copy(selectedSpace = action.spaceSummary) } spaceStateHandler.setCurrentSpace(action.spaceSummary?.roomId) _viewEvents.post(SpaceListViewEvents.CloseDrawer) diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryController.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryController.kt index ff8f5c38f7..acc1df5405 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryController.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryController.kt @@ -88,7 +88,7 @@ class SpaceSummaryController @Inject constructor( id("space_home") selected(selectedSpace == null) countState(UnreadCounterBadgeView.State.Count(homeCount.totalCount, homeCount.isHighlight)) - listener { host.callback?.onSpaceSelected(null) } + listener { host.callback?.onSpaceSelected(null, isSubSpace = false) } } rootSpaces @@ -114,7 +114,7 @@ class SpaceSummaryController @Inject constructor( selected(isSelected) canDrag(true) onMore { host.callback?.onSpaceSettings(roomSummary) } - listener { host.callback?.onSpaceSelected(roomSummary) } + listener { host.callback?.onSpaceSelected(roomSummary, isSubSpace = false) } toggleExpand { host.callback?.onToggleExpand(roomSummary) } countState( UnreadCounterBadgeView.State.Count( @@ -165,7 +165,7 @@ class SpaceSummaryController @Inject constructor( expanded(expanded) onMore { host.callback?.onSpaceSettings(childSummary) } matrixItem(childSummary.toMatrixItem()) - listener { host.callback?.onSpaceSelected(childSummary) } + listener { host.callback?.onSpaceSelected(childSummary, isSubSpace = true) } toggleExpand { host.callback?.onToggleExpand(childSummary) } indent(currentDepth) countState( @@ -184,7 +184,7 @@ class SpaceSummaryController @Inject constructor( } interface Callback { - fun onSpaceSelected(spaceSummary: RoomSummary?) + fun onSpaceSelected(spaceSummary: RoomSummary?, isSubSpace: Boolean) fun onSpaceInviteSelected(spaceSummary: RoomSummary) fun onSpaceSettings(spaceSummary: RoomSummary) fun onToggleExpand(spaceSummary: RoomSummary) diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/ChooseSpaceTypeFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/create/ChooseSpaceTypeFragment.kt index 4c44bfc7a8..6c31b9e856 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/create/ChooseSpaceTypeFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/create/ChooseSpaceTypeFragment.kt @@ -25,6 +25,7 @@ import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.epoxy.onClick import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentSpaceCreateChooseTypeBinding +import im.vector.app.features.analytics.plan.MobileScreen @AndroidEntryPoint class ChooseSpaceTypeFragment : @@ -35,6 +36,11 @@ class ChooseSpaceTypeFragment : override fun getBinding(inflater: LayoutInflater, container: ViewGroup?) = FragmentSpaceCreateChooseTypeBinding.inflate(layoutInflater, container, false) + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + analyticsScreenName = MobileScreen.ScreenName.CreateSpace + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModel.kt index b680f77df2..1cfac4a5fe 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModel.kt @@ -32,6 +32,8 @@ import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.extensions.isEmail import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider +import im.vector.app.features.analytics.AnalyticsTracker +import im.vector.app.features.analytics.plan.Interaction import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.matrix.android.sdk.api.MatrixPatterns @@ -46,7 +48,8 @@ class CreateSpaceViewModel @AssistedInject constructor( private val session: Session, private val stringProvider: StringProvider, private val createSpaceViewModelTask: CreateSpaceViewModelTask, - private val errorFormatter: ErrorFormatter + private val errorFormatter: ErrorFormatter, + private val analyticsTracker: AnalyticsTracker, ) : VectorViewModel<CreateSpaceState, CreateSpaceAction, CreateSpaceEvents>(initialState) { private val identityService = session.identityService() @@ -350,6 +353,13 @@ class CreateSpaceViewModel @AssistedInject constructor( } viewModelScope.launch(Dispatchers.IO) { try { + analyticsTracker.capture( + Interaction( + index = null, + interactionType = null, + name = Interaction.Name.MobileSpaceCreationValidated + ) + ) val alias = if (state.spaceType == SpaceType.Public) { state.aliasLocalPart } else null diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeAnalyticsTracker.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeAnalyticsTracker.kt index 1a490a7287..d35f1bd08c 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeAnalyticsTracker.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeAnalyticsTracker.kt @@ -19,4 +19,4 @@ package im.vector.app.test.fakes import im.vector.app.features.analytics.AnalyticsTracker import io.mockk.mockk -class FakeAnalyticsTracker : AnalyticsTracker by mockk() +class FakeAnalyticsTracker : AnalyticsTracker by mockk(relaxUnitFun = true) From cba920f3e51a450091b6d6caf6c48a8a30c16e09 Mon Sep 17 00:00:00 2001 From: Benoit Marty <benoit@matrix.org> Date: Mon, 3 Oct 2022 16:30:44 +0200 Subject: [PATCH 093/187] Enable Ksp for Epoxy Processor --- build.gradle | 2 ++ library/external/jsonviewer/build.gradle | 3 ++- vector-app/build.gradle | 3 ++- vector/build.gradle | 3 ++- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index e7f7d00159..cfcca7925c 100644 --- a/build.gradle +++ b/build.gradle @@ -44,6 +44,8 @@ plugins { id "org.jlleitschuh.gradle.ktlint" version "11.0.0" // Detekt id "io.gitlab.arturbosch.detekt" version "1.21.0" + // Ksp + id "com.google.devtools.ksp" version "1.7.20-1.0.6" // Dependency Analysis id 'com.autonomousapps.dependency-analysis' version "1.13.1" diff --git a/library/external/jsonviewer/build.gradle b/library/external/jsonviewer/build.gradle index 4e8dc99654..50bb635e8e 100644 --- a/library/external/jsonviewer/build.gradle +++ b/library/external/jsonviewer/build.gradle @@ -2,6 +2,7 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' apply plugin: 'kotlin-parcelize' apply plugin: 'kotlin-kapt' +apply plugin: 'com.google.devtools.ksp' buildscript { repositories { @@ -51,7 +52,7 @@ dependencies { implementation libs.androidx.recyclerview implementation libs.airbnb.epoxy - kapt libs.airbnb.epoxyProcessor + ksp libs.airbnb.epoxyProcessor implementation libs.airbnb.mavericks // Span utils diff --git a/vector-app/build.gradle b/vector-app/build.gradle index a4bc105a1d..923bf96ed3 100644 --- a/vector-app/build.gradle +++ b/vector-app/build.gradle @@ -6,6 +6,7 @@ apply plugin: 'com.google.android.gms.oss-licenses-plugin' apply plugin: 'kotlin-android' apply plugin: 'kotlin-parcelize' apply plugin: 'kotlin-kapt' +apply plugin: 'com.google.devtools.ksp' apply plugin: 'dagger.hilt.android.plugin' apply plugin: 'kotlinx-knit' apply plugin: 'com.likethesalad.stem' @@ -387,7 +388,7 @@ dependencies { // OSS License, gplay flavor only gplayImplementation 'com.google.android.gms:play-services-oss-licenses:17.0.0' kapt libs.dagger.hiltCompiler - kapt libs.airbnb.epoxyProcessor + ksp libs.airbnb.epoxyProcessor androidTestImplementation libs.androidx.testCore androidTestImplementation libs.androidx.testRunner diff --git a/vector/build.gradle b/vector/build.gradle index ff0d907212..bff1cc1d0c 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -2,6 +2,7 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' apply plugin: 'kotlin-parcelize' apply plugin: 'kotlin-kapt' +apply plugin: 'com.google.devtools.ksp' apply plugin: 'dagger.hilt.android.plugin' if (project.hasProperty("coverage")) { @@ -156,7 +157,7 @@ dependencies { api libs.airbnb.epoxy implementation libs.airbnb.epoxyGlide - kapt libs.airbnb.epoxyProcessor + ksp libs.airbnb.epoxyProcessor implementation libs.airbnb.epoxyPaging api libs.airbnb.mavericks From 9f68d9d803c006dc6b2f2926c91f3ee9248cf1a0 Mon Sep 17 00:00:00 2001 From: Benoit Marty <benoit@matrix.org> Date: Mon, 3 Oct 2022 16:42:17 +0200 Subject: [PATCH 094/187] Workaround to have KSP generated Kotlin code available in the IDE (for code completion) Ref: https://github.com/airbnb/epoxy/releases/tag/5.0.0beta02 --- build.gradle | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/build.gradle b/build.gradle index cfcca7925c..6461f63c00 100644 --- a/build.gradle +++ b/build.gradle @@ -329,3 +329,31 @@ ext.initScreenshotTests = { project -> } } } + +// Workaround to have KSP generated Kotlin code available in the IDE (for code completion) +// Ref: https://github.com/airbnb/epoxy/releases/tag/5.0.0beta02 +subprojects { project -> + afterEvaluate { + if (project.hasProperty("android")) { + android { + if (it instanceof com.android.build.gradle.LibraryExtension) { + libraryVariants.all { variant -> + def outputFolder = new File("build/generated/ksp/${variant.name}/kotlin") + variant.addJavaSourceFoldersToModel(outputFolder) + android.sourceSets.getAt(variant.name).java { + srcDir(outputFolder) + } + } + } else if (it instanceof com.android.build.gradle.AppExtension) { + applicationVariants.all { variant -> + def outputFolder = new File("build/generated/ksp/${variant.name}/kotlin") + variant.addJavaSourceFoldersToModel(outputFolder) + android.sourceSets.getAt(variant.name).java { + srcDir(outputFolder) + } + } + } + } + } + } +} From 8e375a7fb247e4f638f9c5ae3d12c11b98b50cc9 Mon Sep 17 00:00:00 2001 From: Benoit Marty <benoit@matrix.org> Date: Mon, 3 Oct 2022 16:08:56 +0200 Subject: [PATCH 095/187] Fix issue with expected types. --- .../home/room/detail/timeline/factory/MessageItemFactory.kt | 2 +- .../app/features/spaces/preview/SpacePreviewController.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 28e256c064..7e48e27cb2 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 @@ -245,7 +245,7 @@ class MessageItemFactory @Inject constructor( .pollQuestion(createPollQuestion(informationData, pollViewState.question, callback)) .canVote(pollViewState.canVote) .votesStatus(pollViewState.votesStatus) - .optionViewStates(pollViewState.optionViewStates) + .optionViewStates(pollViewState.optionViewStates.orEmpty()) .edited(informationData.hasBeenEdited) .highlighted(highlight) .leftGuideline(avatarSizeProvider.leftGuideline) diff --git a/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewController.kt b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewController.kt index 9632087191..d149a3521d 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewController.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/preview/SpacePreviewController.kt @@ -65,7 +65,7 @@ class SpacePreviewController @Inject constructor( subSpaceItem { id(child.roomId) roomId(child.roomId) - title(child.name) + title(child.name ?: "") depth(depth) avatarUrl(child.avatarUrl) avatarRenderer(host.avatarRenderer) From 24fe677e50877653e6f5cc439fbf580d1c48d3bd Mon Sep 17 00:00:00 2001 From: Benoit Marty <benoit@matrix.org> Date: Mon, 3 Oct 2022 16:48:01 +0200 Subject: [PATCH 096/187] Remove usage of @JvmField in Epoxy Items. Fix error `error: annotation type not applicable to this kind of declaration` --- .../room/detail/timeline/factory/MessageItemFactory.kt | 8 ++++---- .../home/room/detail/timeline/item/MessageAudioItem.kt | 5 ++--- .../home/room/detail/timeline/item/MessageFileItem.kt | 10 ++++------ .../home/room/detail/timeline/item/MessageVoiceItem.kt | 5 ++--- .../app/features/home/room/list/RoomSummaryItem.kt | 6 +++--- .../features/home/room/list/RoomSummaryItemCentered.kt | 6 +++--- .../features/home/room/list/RoomSummaryItemFactory.kt | 4 ++-- 7 files changed, 20 insertions(+), 24 deletions(-) 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 7e48e27cb2..6b0a88ac39 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 @@ -279,7 +279,7 @@ class MessageItemFactory @Inject constructor( .duration(messageContent.audioInfo?.duration ?: 0) .playbackControlButtonClickListener(playbackControlButtonClickListener) .audioMessagePlaybackTracker(audioMessagePlaybackTracker) - .isLocalFile(localFilesHelper.isLocalFile(fileUrl)) + .izLocalFile(localFilesHelper.isLocalFile(fileUrl)) .fileSize(messageContent.audioInfo?.size ?: 0L) .onSeek { params.callback?.onAudioSeekBarMovedTo(informationData.eventId, duration, it) } .mxcUrl(fileUrl) @@ -339,7 +339,7 @@ class MessageItemFactory @Inject constructor( .playbackControlButtonClickListener(playbackControlButtonClickListener) .waveformTouchListener(waveformTouchListener) .audioMessagePlaybackTracker(audioMessagePlaybackTracker) - .isLocalFile(localFilesHelper.isLocalFile(fileUrl)) + .izLocalFile(localFilesHelper.isLocalFile(fileUrl)) .mxcUrl(fileUrl) .contentUploadStateTrackerBinder(contentUploadStateTrackerBinder) .contentDownloadStateTrackerBinder(contentDownloadStateTrackerBinder) @@ -399,8 +399,8 @@ class MessageItemFactory @Inject constructor( return MessageFileItem_() .attributes(attributes) .leftGuideline(avatarSizeProvider.leftGuideline) - .isLocalFile(localFilesHelper.isLocalFile(messageContent.getFileUrl())) - .isDownloaded(session.fileService().isFileInCache(messageContent)) + .izLocalFile(localFilesHelper.isLocalFile(messageContent.getFileUrl())) + .izDownloaded(session.fileService().isFileInCache(messageContent)) .mxcUrl(mxcUrl) .contentUploadStateTrackerBinder(contentUploadStateTrackerBinder) .contentDownloadStateTrackerBinder(contentDownloadStateTrackerBinder) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageAudioItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageAudioItem.kt index 256019a2cb..fda9a1465f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageAudioItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageAudioItem.kt @@ -53,8 +53,7 @@ abstract class MessageAudioItem : AbsMessageItem<MessageAudioItem.Holder>() { var fileSize: Long = 0 @EpoxyAttribute - @JvmField - var isLocalFile = false + var izLocalFile = false @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var onSeek: ((percentage: Float) -> Unit)? = null @@ -91,7 +90,7 @@ abstract class MessageAudioItem : AbsMessageItem<MessageAudioItem.Holder>() { holder.view.context.getString(R.string.error_audio_message_unable_to_play, filename) holder.progressLayout.isVisible = false } else { - contentUploadStateTrackerBinder.bind(attributes.informationData.eventId, isLocalFile, holder.progressLayout) + contentUploadStateTrackerBinder.bind(attributes.informationData.eventId, izLocalFile, holder.progressLayout) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageFileItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageFileItem.kt index b11d8fbb52..bf16c8959e 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageFileItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageFileItem.kt @@ -48,12 +48,10 @@ abstract class MessageFileItem : AbsMessageItem<MessageFileItem.Holder>() { var iconRes: Int = 0 @EpoxyAttribute - @JvmField - var isLocalFile = false + var izLocalFile = false @EpoxyAttribute - @JvmField - var isDownloaded = false + var izDownloaded = false @EpoxyAttribute lateinit var contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder @@ -66,7 +64,7 @@ abstract class MessageFileItem : AbsMessageItem<MessageFileItem.Holder>() { renderSendState(holder.fileLayout, holder.filenameView) if (!attributes.informationData.sendState.hasFailed()) { - contentUploadStateTrackerBinder.bind(attributes.informationData.eventId, isLocalFile, holder.progressLayout) + contentUploadStateTrackerBinder.bind(attributes.informationData.eventId, izLocalFile, holder.progressLayout) } else { holder.fileImageView.setImageResource(R.drawable.ic_cross) holder.progressLayout.isVisible = false @@ -77,7 +75,7 @@ abstract class MessageFileItem : AbsMessageItem<MessageFileItem.Holder>() { if (attributes.informationData.sendState.isSending()) { holder.fileImageView.setImageResource(iconRes) } else { - if (isDownloaded) { + if (izDownloaded) { holder.fileImageView.setImageResource(iconRes) holder.fileDownloadProgress.progress = 0 } else { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt index 93e95dd4a5..e057950790 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceItem.kt @@ -55,8 +55,7 @@ abstract class MessageVoiceItem : AbsMessageItem<MessageVoiceItem.Holder>() { var waveform: List<Int> = emptyList() @EpoxyAttribute - @JvmField - var isLocalFile = false + var izLocalFile = false @EpoxyAttribute lateinit var contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder @@ -77,7 +76,7 @@ abstract class MessageVoiceItem : AbsMessageItem<MessageVoiceItem.Holder>() { super.bind(holder) renderSendState(holder.voiceLayout, null) if (!attributes.informationData.sendState.hasFailed()) { - contentUploadStateTrackerBinder.bind(attributes.informationData.eventId, isLocalFile, holder.progressLayout) + contentUploadStateTrackerBinder.bind(attributes.informationData.eventId, izLocalFile, holder.progressLayout) } else { holder.voicePlaybackControlButton.setImageResource(R.drawable.ic_cross) holder.voicePlaybackControlButton.contentDescription = holder.view.context.getString(R.string.error_voice_message_unable_to_play) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItem.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItem.kt index e6d162e8c3..4eaf9ff0bf 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItem.kt @@ -76,8 +76,8 @@ abstract class RoomSummaryItem : VectorEpoxyModel<RoomSummaryItem.Holder>(R.layo @EpoxyAttribute var showPresence: Boolean = false - @EpoxyAttribute @JvmField - var isPublic: Boolean = false + @EpoxyAttribute + var izPublic: Boolean = false @EpoxyAttribute var unreadNotificationCount: Int = 0 @@ -121,7 +121,7 @@ abstract class RoomSummaryItem : VectorEpoxyModel<RoomSummaryItem.Holder>(R.layo holder.draftView.isVisible = hasDraft avatarRenderer.render(matrixItem, holder.avatarImageView) holder.roomAvatarDecorationImageView.render(encryptionTrustLevel) - holder.roomAvatarPublicDecorationImageView.isVisible = isPublic + holder.roomAvatarPublicDecorationImageView.isVisible = izPublic holder.roomAvatarFailSendingImageView.isVisible = hasFailedSending renderSelection(holder, showSelected) holder.roomAvatarPresenceImageView.render(showPresence, userPresence) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemCentered.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemCentered.kt index 440df0952c..c896c827ce 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemCentered.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemCentered.kt @@ -61,8 +61,8 @@ abstract class RoomSummaryItemCentered : VectorEpoxyModel<RoomSummaryItemCentere @EpoxyAttribute var showPresence: Boolean = false - @EpoxyAttribute @JvmField - var isPublic: Boolean = false + @EpoxyAttribute + var izPublic: Boolean = false @EpoxyAttribute var unreadNotificationCount: Int = 0 @@ -96,7 +96,7 @@ abstract class RoomSummaryItemCentered : VectorEpoxyModel<RoomSummaryItemCentere holder.titleView.text = matrixItem.getBestName() avatarRenderer.render(matrixItem, holder.avatarImageView) holder.roomAvatarDecorationImageView.render(encryptionTrustLevel) - holder.roomAvatarPublicDecorationImageView.isVisible = isPublic + holder.roomAvatarPublicDecorationImageView.isVisible = izPublic holder.roomAvatarFailSendingImageView.isVisible = hasFailedSending renderSelection(holder, showSelected) holder.roomAvatarPresenceImageView.render(showPresence, userPresence) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt index 290b66e576..79785c142f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt @@ -167,7 +167,7 @@ class RoomSummaryItemFactory @Inject constructor( // .encryptionTrustLevel(roomSummary.roomEncryptionTrustLevel) .displayMode(displayMode) .subtitle(subtitle) - .isPublic(roomSummary.isPublic) + .izPublic(roomSummary.isPublic) .showPresence(roomSummary.isDirect) .userPresence(roomSummary.directUserPresence) .matrixItem(roomSummary.toMatrixItem()) @@ -197,7 +197,7 @@ class RoomSummaryItemFactory @Inject constructor( // We do not display shield in the room list anymore // .encryptionTrustLevel(roomSummary.roomEncryptionTrustLevel) .displayMode(displayMode) - .isPublic(roomSummary.isPublic) + .izPublic(roomSummary.isPublic) .showPresence(roomSummary.isDirect) .userPresence(roomSummary.directUserPresence) .matrixItem(roomSummary.toMatrixItem()) From e282380ab8c68c5e19fbd11300b827ca95084258 Mon Sep 17 00:00:00 2001 From: Benoit Marty <benoit@matrix.org> Date: Mon, 3 Oct 2022 16:42:44 +0200 Subject: [PATCH 097/187] Naming convention: Epoxy item classes have `Item`suffix --- ...{RoomSummaryItemCentered.kt => RoomSummaryCenteredItem.kt} | 2 +- .../app/features/home/room/list/RoomSummaryItemFactory.kt | 2 +- .../app/features/home/room/list/RoomSummaryPagedController.kt | 2 +- ...ummaryItemPlaceHolder.kt => RoomSummaryPlaceHolderItem.kt} | 2 +- ...ilterNoResults.kt => SpaceDirectoryFilterNoResultsItem.kt} | 2 +- .../home/room/list/home/HomeFilteredRoomsController.kt | 4 ++-- .../features/home/room/list/home/invites/InvitesController.kt | 4 ++-- .../app/features/spaces/explore/SpaceDirectoryController.kt | 4 ++-- 8 files changed, 11 insertions(+), 11 deletions(-) rename vector/src/main/java/im/vector/app/features/home/room/list/{RoomSummaryItemCentered.kt => RoomSummaryCenteredItem.kt} (97%) rename vector/src/main/java/im/vector/app/features/home/room/list/{RoomSummaryItemPlaceHolder.kt => RoomSummaryPlaceHolderItem.kt} (90%) rename vector/src/main/java/im/vector/app/features/home/room/list/{SpaceDirectoryFilterNoResults.kt => SpaceDirectoryFilterNoResultsItem.kt} (84%) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemCentered.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryCenteredItem.kt similarity index 97% rename from vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemCentered.kt rename to vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryCenteredItem.kt index c896c827ce..764f50456c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemCentered.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryCenteredItem.kt @@ -41,7 +41,7 @@ import org.matrix.android.sdk.api.session.presence.model.UserPresence import org.matrix.android.sdk.api.util.MatrixItem @EpoxyModelClass -abstract class RoomSummaryItemCentered : VectorEpoxyModel<RoomSummaryItemCentered.Holder>(R.layout.item_room_centered) { +abstract class RoomSummaryCenteredItem : VectorEpoxyModel<RoomSummaryCenteredItem.Holder>(R.layout.item_room_centered) { @EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt index 79785c142f..638e3c185d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt @@ -191,7 +191,7 @@ class RoomSummaryItemFactory @Inject constructor( unreadCount: Int, onClick: ((RoomSummary) -> Unit)?, onLongClick: ((RoomSummary) -> Boolean)? - ) = RoomSummaryItemCentered_() + ) = RoomSummaryCenteredItem_() .id(roomSummary.roomId) .avatarRenderer(avatarRenderer) // We do not display shield in the room list anymore diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryPagedController.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryPagedController.kt index 10d7ef425c..43b20296af 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryPagedController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryPagedController.kt @@ -67,7 +67,7 @@ class RoomSummaryPagedController( override fun buildItemModel(currentPosition: Int, item: RoomSummary?): EpoxyModel<*> { return if (item == null) { val host = this - RoomSummaryItemPlaceHolder_().apply { + RoomSummaryPlaceHolderItem_().apply { id(currentPosition) useSingleLineForLastEvent(host.shouldUseSingleLine) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemPlaceHolder.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryPlaceHolderItem.kt similarity index 90% rename from vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemPlaceHolder.kt rename to vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryPlaceHolderItem.kt index df191bc2ec..75156ad5d9 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemPlaceHolder.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryPlaceHolderItem.kt @@ -24,7 +24,7 @@ import im.vector.app.core.epoxy.VectorEpoxyHolder import im.vector.app.core.epoxy.VectorEpoxyModel @EpoxyModelClass -abstract class RoomSummaryItemPlaceHolder : VectorEpoxyModel<RoomSummaryItemPlaceHolder.Holder>(R.layout.item_room_placeholder) { +abstract class RoomSummaryPlaceHolderItem : VectorEpoxyModel<RoomSummaryPlaceHolderItem.Holder>(R.layout.item_room_placeholder) { @EpoxyAttribute var useSingleLineForLastEvent: Boolean = false diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/SpaceDirectoryFilterNoResults.kt b/vector/src/main/java/im/vector/app/features/home/room/list/SpaceDirectoryFilterNoResultsItem.kt similarity index 84% rename from vector/src/main/java/im/vector/app/features/home/room/list/SpaceDirectoryFilterNoResults.kt rename to vector/src/main/java/im/vector/app/features/home/room/list/SpaceDirectoryFilterNoResultsItem.kt index 6899b59f38..1efbf53214 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/SpaceDirectoryFilterNoResults.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/SpaceDirectoryFilterNoResultsItem.kt @@ -22,6 +22,6 @@ import im.vector.app.core.epoxy.VectorEpoxyHolder import im.vector.app.core.epoxy.VectorEpoxyModel @EpoxyModelClass -abstract class SpaceDirectoryFilterNoResults : VectorEpoxyModel<SpaceDirectoryFilterNoResults.Holder>(R.layout.item_space_directory_filter_no_results) { +abstract class SpaceDirectoryFilterNoResultsItem : VectorEpoxyModel<SpaceDirectoryFilterNoResultsItem.Holder>(R.layout.item_space_directory_filter_no_results) { class Holder : VectorEpoxyHolder() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeFilteredRoomsController.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeFilteredRoomsController.kt index cd245af0fc..500039e3eb 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeFilteredRoomsController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeFilteredRoomsController.kt @@ -24,7 +24,7 @@ import im.vector.app.core.utils.createUIHandler import im.vector.app.features.home.RoomListDisplayMode import im.vector.app.features.home.room.list.RoomListListener import im.vector.app.features.home.room.list.RoomSummaryItemFactory -import im.vector.app.features.home.room.list.RoomSummaryItemPlaceHolder_ +import im.vector.app.features.home.room.list.RoomSummaryPlaceHolderItem_ import im.vector.app.features.settings.FontScalePreferences import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.model.RoomSummary @@ -83,7 +83,7 @@ class HomeFilteredRoomsController @Inject constructor( override fun buildItemModel(currentPosition: Int, item: RoomSummary?): EpoxyModel<*> { return if (item == null) { val host = this - RoomSummaryItemPlaceHolder_().apply { + RoomSummaryPlaceHolderItem_().apply { id(currentPosition) useSingleLineForLastEvent(host.shouldUseSingleLine) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesController.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesController.kt index 1511b97c3c..b59dfbdd43 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesController.kt @@ -22,7 +22,7 @@ import im.vector.app.core.utils.createUIHandler import im.vector.app.features.home.RoomListDisplayMode import im.vector.app.features.home.room.list.RoomListListener import im.vector.app.features.home.room.list.RoomSummaryItemFactory -import im.vector.app.features.home.room.list.RoomSummaryItemPlaceHolder_ +import im.vector.app.features.home.room.list.RoomSummaryPlaceHolderItem_ import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.model.RoomSummary import javax.inject.Inject @@ -43,7 +43,7 @@ class InvitesController @Inject constructor( var listener: RoomListListener? = null override fun buildItemModel(currentPosition: Int, item: RoomSummary?): EpoxyModel<*> { - item ?: return RoomSummaryItemPlaceHolder_().apply { id(currentPosition) } + item ?: return RoomSummaryPlaceHolderItem_().apply { id(currentPosition) } return roomSummaryItemFactory.create(item, roomChangeMembershipStates.orEmpty(), emptySet(), RoomListDisplayMode.ROOMS, listener) } } diff --git a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryController.kt b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryController.kt index 5b362690fa..e2fde4d45b 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryController.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/explore/SpaceDirectoryController.kt @@ -34,7 +34,7 @@ import im.vector.app.core.ui.list.genericEmptyWithActionItem import im.vector.app.core.ui.list.genericPillItem import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.list.spaceChildInfoItem -import im.vector.app.features.home.room.list.spaceDirectoryFilterNoResults +import im.vector.app.features.home.room.list.spaceDirectoryFilterNoResultsItem import im.vector.app.features.spaces.manage.SpaceChildInfoMatchFilter import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence import me.gujun.android.span.span @@ -141,7 +141,7 @@ class SpaceDirectoryController @Inject constructor( val filteredChildInfo = flattenChildInfo.filter { matchFilter.test(it) } if (filteredChildInfo.isEmpty()) { - spaceDirectoryFilterNoResults { + spaceDirectoryFilterNoResultsItem { id("no_results") } } else { From 8653b74964028d39b9ec5d6e382189929a3e5d2c Mon Sep 17 00:00:00 2001 From: Benoit Marty <benoit@matrix.org> Date: Mon, 3 Oct 2022 17:00:04 +0200 Subject: [PATCH 098/187] Prefer to add explicit group. --- dependencies_groups.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies_groups.gradle b/dependencies_groups.gradle index 61ab038b6e..fc9021163a 100644 --- a/dependencies_groups.gradle +++ b/dependencies_groups.gradle @@ -47,7 +47,6 @@ ext.groups = [ ], mavenCentral: [ regex: [ - 'com\\.google\\.auto\\.*', ], group: [ 'app.cash.paparazzi', @@ -85,6 +84,7 @@ ext.groups = [ 'com.google', 'com.google.android', 'com.google.api.grpc', + 'com.google.auto', 'com.google.auto.service', 'com.google.auto.value', 'com.google.code.findbugs', From d14570dbeaba456cd629cf2eb582e705f150f919 Mon Sep 17 00:00:00 2001 From: ericdecanini <eddecanini@gmail.com> Date: Mon, 3 Oct 2022 17:52:59 -0400 Subject: [PATCH 099/187] Replaces AppBuildConfig --- .../src/main/res/values/strings.xml | 4 +- .../app/core/device/GetDeviceInfoUseCase.kt | 35 ++++++++++++++ .../vector/app/core/pushers/PushersManager.kt | 7 +-- .../device/DefaultGetDeviceInfoUseCaseTest.kt | 40 ++++++++++++++++ .../app/core/pushers/PushersManagerTest.kt | 42 ++++++++--------- .../app/test/fakes/FakeCryptoService.kt | 4 ++ .../test/fakes/FakeGetDeviceInfoUseCase.kt} | 13 ++++-- .../test/fixtures/CryptoDeviceInfoFixture.kt | 46 +++++++++++++++++++ 8 files changed, 160 insertions(+), 31 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/core/device/GetDeviceInfoUseCase.kt create mode 100644 vector/src/test/java/im/vector/app/core/device/DefaultGetDeviceInfoUseCaseTest.kt rename vector/src/{main/java/im/vector/app/AppBuildConfig.kt => test/java/im/vector/app/test/fakes/FakeGetDeviceInfoUseCase.kt} (61%) create mode 100644 vector/src/test/java/im/vector/app/test/fixtures/CryptoDeviceInfoFixture.kt diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index c436044153..aa56f6a25a 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -1700,8 +1700,8 @@ <string name="push_gateway_item_app_id">App ID:</string> <string name="push_gateway_item_push_key">Push Key:</string> <string name="push_gateway_item_app_display_name">App Display Name:</string> - <string name="push_gateway_item_device_name">Device Display Name:</string> - <string name="push_gateway_item_device_id">Device ID:</string> + <string name="push_gateway_item_device_name">Session Display Name:</string> + <string name="push_gateway_item_device_id">Session ID:</string> <string name="push_gateway_item_url">Url:</string> <string name="push_gateway_item_format">Format:</string> <string name="push_gateway_item_profile_tag">Profile tag:</string> diff --git a/vector/src/main/java/im/vector/app/core/device/GetDeviceInfoUseCase.kt b/vector/src/main/java/im/vector/app/core/device/GetDeviceInfoUseCase.kt new file mode 100644 index 0000000000..344173aab7 --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/device/GetDeviceInfoUseCase.kt @@ -0,0 +1,35 @@ +/* + * 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.device + +import im.vector.app.core.di.ActiveSessionHolder +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo +import javax.inject.Inject + +interface GetDeviceInfoUseCase { + + fun execute(): CryptoDeviceInfo +} + +class DefaultGetDeviceInfoUseCase @Inject constructor( + private val activeSessionHolder: ActiveSessionHolder +) : GetDeviceInfoUseCase { + + override fun execute(): CryptoDeviceInfo { + return activeSessionHolder.getActiveSession().cryptoService().getMyDevice() + } +} diff --git a/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt b/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt index 67e7868df7..44cccbd3f5 100644 --- a/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt +++ b/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt @@ -16,8 +16,8 @@ package im.vector.app.core.pushers -import im.vector.app.AppBuildConfig import im.vector.app.R +import im.vector.app.core.device.GetDeviceInfoUseCase import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.resources.AppNameProvider import im.vector.app.core.resources.LocaleProvider @@ -35,6 +35,7 @@ class PushersManager @Inject constructor( private val localeProvider: LocaleProvider, private val stringProvider: StringProvider, private val appNameProvider: AppNameProvider, + private val getDeviceInfoUseCase: GetDeviceInfoUseCase, ) { suspend fun testPush() { val currentSession = activeSessionHolder.getActiveSession() @@ -64,12 +65,12 @@ class PushersManager @Inject constructor( pushKey: String, gateway: String ) = HttpPusher( - pushKey, + pushkey = pushKey, appId = stringProvider.getString(R.string.pusher_app_id), profileTag = DEFAULT_PUSHER_FILE_TAG + "_" + abs(activeSessionHolder.getActiveSession().myUserId.hashCode()), lang = localeProvider.current().language, appDisplayName = appNameProvider.getAppName(), - deviceDisplayName = AppBuildConfig.getModel(), + deviceDisplayName = getDeviceInfoUseCase.execute().displayName().orEmpty(), url = gateway, enabled = true, deviceId = activeSessionHolder.getActiveSession().sessionParams.deviceId ?: "MOBILE", diff --git a/vector/src/test/java/im/vector/app/core/device/DefaultGetDeviceInfoUseCaseTest.kt b/vector/src/test/java/im/vector/app/core/device/DefaultGetDeviceInfoUseCaseTest.kt new file mode 100644 index 0000000000..0675556bec --- /dev/null +++ b/vector/src/test/java/im/vector/app/core/device/DefaultGetDeviceInfoUseCaseTest.kt @@ -0,0 +1,40 @@ +/* + * 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.device + +import im.vector.app.test.fakes.FakeActiveSessionHolder +import im.vector.app.test.fakes.FakeCryptoService +import im.vector.app.test.fakes.FakeSession +import org.amshove.kluent.shouldBeEqualTo +import org.junit.Test + +class DefaultGetDeviceInfoUseCaseTest { + + private val cryptoService = FakeCryptoService() + private val session = FakeSession(fakeCryptoService = cryptoService) + private val activeSessionHolder = FakeActiveSessionHolder(session) + + private val getDeviceInfoUseCase = DefaultGetDeviceInfoUseCase(activeSessionHolder.instance) + + @Test + fun `when execute, then get crypto device info`() { + + val result = getDeviceInfoUseCase.execute() + + result shouldBeEqualTo cryptoService.cryptoDeviceInfo + } +} diff --git a/vector/src/test/java/im/vector/app/core/pushers/PushersManagerTest.kt b/vector/src/test/java/im/vector/app/core/pushers/PushersManagerTest.kt index e42f995ef6..50c9024e86 100644 --- a/vector/src/test/java/im/vector/app/core/pushers/PushersManagerTest.kt +++ b/vector/src/test/java/im/vector/app/core/pushers/PushersManagerTest.kt @@ -16,20 +16,20 @@ package im.vector.app.core.pushers -import im.vector.app.AppBuildConfig import im.vector.app.R import im.vector.app.test.fakes.FakeActiveSessionHolder import im.vector.app.test.fakes.FakeAppNameProvider +import im.vector.app.test.fakes.FakeGetDeviceInfoUseCase import im.vector.app.test.fakes.FakeLocaleProvider import im.vector.app.test.fakes.FakePushersService import im.vector.app.test.fakes.FakeSession import im.vector.app.test.fakes.FakeStringProvider -import io.mockk.every +import im.vector.app.test.fixtures.CryptoDeviceInfoFixture.aCryptoDeviceInfo import io.mockk.mockk -import io.mockk.mockkObject import org.amshove.kluent.shouldBeEqualTo -import org.junit.Before import org.junit.Test +import org.matrix.android.sdk.api.session.crypto.model.UnsignedDeviceInfo +import org.matrix.android.sdk.api.session.pushers.HttpPusher import java.util.Locale import kotlin.math.abs @@ -41,6 +41,7 @@ class PushersManagerTest { private val stringProvider = FakeStringProvider() private val localeProvider = FakeLocaleProvider() private val appNameProvider = FakeAppNameProvider() + private val getDeviceInfoUseCase = FakeGetDeviceInfoUseCase() private val pushersManager = PushersManager( mockk(), @@ -48,13 +49,9 @@ class PushersManagerTest { localeProvider, stringProvider.instance, appNameProvider, + getDeviceInfoUseCase, ) - @Before - fun setUp() { - mockkObject(AppBuildConfig) - } - @Test fun `when enqueueRegisterPusher, then HttpPusher created and enqueued`() { val pushKey = "abc" @@ -65,21 +62,24 @@ class PushersManagerTest { stringProvider.given(R.string.pusher_app_id, pusherAppId) localeProvider.givenCurrent(Locale.UK) appNameProvider.givenAppName(appName) - every { AppBuildConfig.getModel() } returns deviceDisplayName + getDeviceInfoUseCase.givenDeviceInfo(aCryptoDeviceInfo(unsigned = UnsignedDeviceInfo(deviceDisplayName))) + val expectedHttpPusher = HttpPusher( + pushkey = pushKey, + appId = pusherAppId, + profileTag = DEFAULT_PUSHER_FILE_TAG + "_" + abs(session.myUserId.hashCode()), + lang = Locale.UK.language, + appDisplayName = appName, + deviceDisplayName = deviceDisplayName, + url = gateway, + enabled = true, + deviceId = session.sessionParams.deviceId!!, + append = false, + withEventIdOnly = true, + ) pushersManager.enqueueRegisterPusher(pushKey, gateway) val httpPusher = pushersService.verifyEnqueueAddHttpPusher() - httpPusher.pushkey shouldBeEqualTo pushKey - httpPusher.appId shouldBeEqualTo pusherAppId - httpPusher.profileTag shouldBeEqualTo DEFAULT_PUSHER_FILE_TAG + "_" + abs(session.myUserId.hashCode()) - httpPusher.lang shouldBeEqualTo Locale.UK.language - httpPusher.appDisplayName shouldBeEqualTo appName - httpPusher.deviceDisplayName shouldBeEqualTo deviceDisplayName - httpPusher.enabled shouldBeEqualTo true - httpPusher.deviceId shouldBeEqualTo session.sessionParams.deviceId - httpPusher.append shouldBeEqualTo false - httpPusher.withEventIdOnly shouldBeEqualTo true - httpPusher.url shouldBeEqualTo gateway + httpPusher shouldBeEqualTo expectedHttpPusher } } diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeCryptoService.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeCryptoService.kt index 538ce671d2..02b911e800 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeCryptoService.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeCryptoService.kt @@ -17,6 +17,7 @@ package im.vector.app.test.fakes import androidx.lifecycle.MutableLiveData +import im.vector.app.test.fixtures.CryptoDeviceInfoFixture.aCryptoDeviceInfo import io.mockk.mockk import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo @@ -32,6 +33,7 @@ class FakeCryptoService( var cryptoDeviceInfos = mutableMapOf<String, CryptoDeviceInfo>() var cryptoDeviceInfoWithIdLiveData: MutableLiveData<Optional<CryptoDeviceInfo>> = MutableLiveData() var myDevicesInfoWithIdLiveData: MutableLiveData<Optional<DeviceInfo>> = MutableLiveData() + var cryptoDeviceInfo = aCryptoDeviceInfo() override fun crossSigningService() = fakeCrossSigningService @@ -50,4 +52,6 @@ class FakeCryptoService( override fun getLiveCryptoDeviceInfoWithId(deviceId: String) = cryptoDeviceInfoWithIdLiveData override fun getMyDevicesInfoLive(deviceId: String) = myDevicesInfoWithIdLiveData + + override fun getMyDevice() = cryptoDeviceInfo } diff --git a/vector/src/main/java/im/vector/app/AppBuildConfig.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeGetDeviceInfoUseCase.kt similarity index 61% rename from vector/src/main/java/im/vector/app/AppBuildConfig.kt rename to vector/src/test/java/im/vector/app/test/fakes/FakeGetDeviceInfoUseCase.kt index 54eb4f87ea..c284263d28 100644 --- a/vector/src/main/java/im/vector/app/AppBuildConfig.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeGetDeviceInfoUseCase.kt @@ -14,13 +14,16 @@ * limitations under the License. */ -package im.vector.app +package im.vector.app.test.fakes -import android.os.Build +import im.vector.app.core.device.GetDeviceInfoUseCase +import io.mockk.every +import io.mockk.mockk +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo -object AppBuildConfig { +class FakeGetDeviceInfoUseCase : GetDeviceInfoUseCase by mockk() { - fun getModel(): String { - return Build.MODEL + fun givenDeviceInfo(cryptoDeviceInfo: CryptoDeviceInfo) { + every { execute() } returns cryptoDeviceInfo } } diff --git a/vector/src/test/java/im/vector/app/test/fixtures/CryptoDeviceInfoFixture.kt b/vector/src/test/java/im/vector/app/test/fixtures/CryptoDeviceInfoFixture.kt new file mode 100644 index 0000000000..834a428b4a --- /dev/null +++ b/vector/src/test/java/im/vector/app/test/fixtures/CryptoDeviceInfoFixture.kt @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.test.fixtures + +import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo +import org.matrix.android.sdk.api.session.crypto.model.UnsignedDeviceInfo + +object CryptoDeviceInfoFixture { + + fun aCryptoDeviceInfo( + deviceId: String = "", + userId: String = "", + algorithms: List<String>? = null, + keys: Map<String, String>? = null, + signatures: Map<String, Map<String, String>>? = null, + unsigned: UnsignedDeviceInfo? = null, + trustLevel: DeviceTrustLevel? = null, + isBlocked: Boolean = false, + firstTimeSeenLocalTs: Long? = null, + ) = CryptoDeviceInfo( + deviceId, + userId, + algorithms, + keys, + signatures, + unsigned, + trustLevel, + isBlocked, + firstTimeSeenLocalTs, + ) +} From f4dc435a970420ab3d5b8fb2971ba7944afa073c Mon Sep 17 00:00:00 2001 From: ericdecanini <eddecanini@gmail.com> Date: Mon, 3 Oct 2022 17:54:06 -0400 Subject: [PATCH 100/187] Adds bindings to fdroid variant --- vector-app/src/fdroid/java/im/vector/app/di/FlavorModule.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/vector-app/src/fdroid/java/im/vector/app/di/FlavorModule.kt b/vector-app/src/fdroid/java/im/vector/app/di/FlavorModule.kt index 5355aa3cd9..6f76913649 100644 --- a/vector-app/src/fdroid/java/im/vector/app/di/FlavorModule.kt +++ b/vector-app/src/fdroid/java/im/vector/app/di/FlavorModule.kt @@ -59,4 +59,10 @@ abstract class FlavorModule { @Binds abstract fun bindsFcmHelper(fcmHelper: FdroidFcmHelper): FcmHelper + + @Binds + abstract fun bindsLocaleProvider(localeProvider: DefaultLocaleProvider): LocaleProvider + + @Binds + abstract fun bindsAppNameProvider(appNameProvider: DefaultAppNameProvider): AppNameProvider } From 3bd3283d431b0ca9d988a1bf12512652eeafa80d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Oct 2022 23:15:43 +0000 Subject: [PATCH 101/187] Bump danger/danger-js from 11.1.2 to 11.1.3 Bumps [danger/danger-js](https://github.com/danger/danger-js) from 11.1.2 to 11.1.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.1.2...11.1.3) --- updated-dependencies: - dependency-name: danger/danger-js dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> --- .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 8a892b9b15..5698a696b6 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.1.2 + uses: danger/danger-js@11.1.3 with: args: "--dangerfile tools/danger/dangerfile.js" env: diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index da70d13a86..1692e2e281 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.1.2 + uses: danger/danger-js@11.1.3 with: args: "--dangerfile tools/danger/dangerfile-lint.js" env: From 8eea2ef9231d6d648cd48ebd46ef1893d450913d Mon Sep 17 00:00:00 2001 From: Nikita Fedrunov <66663241+fedrunov@users.noreply.github.com> Date: Tue, 4 Oct 2022 11:29:34 +0200 Subject: [PATCH 102/187] fixing bug when room list is not updated being on background (#7278) --- .../home/room/list/home/HomeRoomListViewModel.kt | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt index 734fc102bf..33b293497e 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt @@ -18,7 +18,7 @@ package im.vector.app.features.home.room.list.home import android.widget.ImageView import androidx.lifecycle.LiveData -import androidx.lifecycle.MediatorLiveData +import androidx.lifecycle.MutableLiveData import androidx.lifecycle.Observer import androidx.paging.PagedList import arrow.core.toOption @@ -93,7 +93,7 @@ class HomeRoomListViewModel @AssistedInject constructor( .setEnablePlaceholders(true) .build() - private val _roomsLivePagedList = MediatorLiveData<PagedList<RoomSummary>>() + private val _roomsLivePagedList = MutableLiveData<PagedList<RoomSummary>>() val roomsLivePagedList: LiveData<PagedList<RoomSummary>> = _roomsLivePagedList private val internalPagedListObserver = Observer<PagedList<RoomSummary>> { @@ -240,9 +240,7 @@ class HomeRoomListViewModel @AssistedInject constructor( } private fun observeRooms(currentFilter: HomeRoomFilter, isAZOrdering: Boolean) { - filteredPagedRoomSummariesLive?.livePagedList?.let { livePagedList -> - _roomsLivePagedList.removeSource(livePagedList) - } + filteredPagedRoomSummariesLive?.livePagedList?.removeObserver(internalPagedListObserver) val builder = RoomSummaryQueryParams.Builder().also { it.memberships = listOf(Membership.JOIN) it.spaceFilter = spaceStateHandler.getCurrentSpace()?.roomId.toActiveSpaceOrNoFilter() @@ -260,7 +258,7 @@ class HomeRoomListViewModel @AssistedInject constructor( ).also { filteredPagedRoomSummariesLive = it } - _roomsLivePagedList.addSource(liveResults.livePagedList, internalPagedListObserver) + liveResults.livePagedList.observeForever(internalPagedListObserver) } private fun observeOrderPreferences() { @@ -343,9 +341,7 @@ class HomeRoomListViewModel @AssistedInject constructor( } override fun onCleared() { - filteredPagedRoomSummariesLive?.livePagedList?.let { livePagedList -> - _roomsLivePagedList.removeSource(livePagedList) - } + filteredPagedRoomSummariesLive?.livePagedList?.removeObserver(internalPagedListObserver) super.onCleared() } From 8d54a0d759c3b069ec904c3284f548b0ac20da31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= <jorgem@element.io> Date: Tue, 4 Oct 2022 13:58:17 +0200 Subject: [PATCH 103/187] Remove Robolectric, make Robolectric test an instrumentation test --- dependencies.gradle | 1 - dependencies_groups.gradle | 1 - vector/build.gradle | 1 - .../features/RoomMemberListControllerTest.kt | 18 +++++------------- 4 files changed, 5 insertions(+), 16 deletions(-) rename vector/src/{test => androidTest}/java/im/vector/app/features/RoomMemberListControllerTest.kt (92%) diff --git a/dependencies.gradle b/dependencies.gradle index f5d64a78d1..6b2e62245c 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -168,7 +168,6 @@ ext.libs = [ 'kluent' : "org.amshove.kluent:kluent-android:1.68", 'timberJunitRule' : "net.lachlanmckee:timber-junit-rule:1.0.1", 'junit' : "junit:junit:4.13.2", - 'robolectric' : "org.robolectric:robolectric:4.8", ] ] diff --git a/dependencies_groups.gradle b/dependencies_groups.gradle index a97d80bc7f..149a1fbac5 100644 --- a/dependencies_groups.gradle +++ b/dependencies_groups.gradle @@ -210,7 +210,6 @@ ext.groups = [ 'org.ow2.asm', 'org.ow2.asm', 'org.reactivestreams', - 'org.robolectric', 'org.slf4j', 'org.sonatype.oss', 'org.testng', diff --git a/vector/build.gradle b/vector/build.gradle index 7bc97b1a57..763d968642 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -290,7 +290,6 @@ dependencies { testImplementation libs.tests.kluent testImplementation libs.mockk.mockk testImplementation libs.androidx.coreTesting - testImplementation libs.tests.robolectric // Plant Timber tree for test testImplementation libs.tests.timberJunitRule testImplementation libs.airbnb.mavericksTesting diff --git a/vector/src/test/java/im/vector/app/features/RoomMemberListControllerTest.kt b/vector/src/androidTest/java/im/vector/app/features/RoomMemberListControllerTest.kt similarity index 92% rename from vector/src/test/java/im/vector/app/features/RoomMemberListControllerTest.kt rename to vector/src/androidTest/java/im/vector/app/features/RoomMemberListControllerTest.kt index d32c3b5532..d282a8c223 100644 --- a/vector/src/test/java/im/vector/app/features/RoomMemberListControllerTest.kt +++ b/vector/src/androidTest/java/im/vector/app/features/RoomMemberListControllerTest.kt @@ -16,36 +16,26 @@ package im.vector.app.features -import androidx.arch.core.executor.testing.InstantTaskExecutorRule import com.airbnb.mvrx.Success -import com.airbnb.mvrx.test.MvRxTestRule import im.vector.app.core.epoxy.profiles.ProfileMatrixItemWithPowerLevelWithPresence +import im.vector.app.core.utils.waitUntil import im.vector.app.features.roomprofile.members.RoomMemberListCategories import im.vector.app.features.roomprofile.members.RoomMemberListController import im.vector.app.features.roomprofile.members.RoomMemberListViewState import io.mockk.every import io.mockk.mockk +import kotlinx.coroutines.test.runTest import org.amshove.kluent.shouldBeEqualTo -import org.junit.Rule import org.junit.Test -import org.junit.runner.RunWith import org.matrix.android.sdk.api.session.crypto.model.UserVerificationLevel import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary -import org.robolectric.RobolectricTestRunner -@RunWith(RobolectricTestRunner::class) class RoomMemberListControllerTest { - @get:Rule - val mvrxTestRule = MvRxTestRule() - - @get:Rule - val instantExecutorRule = InstantTaskExecutorRule() - @Test - fun testControllerUserVerificationLevel() { + fun testControllerUserVerificationLevel() = runTest { val roomListController = RoomMemberListController( avatarRenderer = mockk { }, @@ -109,6 +99,8 @@ class RoomMemberListControllerTest { roomListController.setData(state) + waitUntil { !roomListController.hasPendingModelBuild() } + val models = roomListController.adapter.copyOfModels val profileItems = models.filterIsInstance<ProfileMatrixItemWithPowerLevelWithPresence>() From d205202e52ae6941fca0cbd43520481e013cfb2b Mon Sep 17 00:00:00 2001 From: Paul <pamat@protonmail.com> Date: Tue, 4 Oct 2022 13:34:13 +0000 Subject: [PATCH 104/187] [Bugfix] Fix crash on previewing image to upload on Android P (#7184) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix crash on image upload preview on Android P Using hardware bitmap allocation on Android framework versions prior to Android Q causes a crash when decoding a bitmap if GL context wasn't initialised. The issue is not documented in ImageDecoder reference but it is mentioned in the comments of glide[1] with a link to internal google discussion. [1] https://github.com/bumptech/glide/blob/f83cc274b42cf02af6611d2a8c21e4a9b1f5d592/library/src/main/java/com/bumptech/glide/load/resource/bitmap/HardwareConfigState.java#L22 Signed-off-by: Paweł Matuszewski <pamat@protonmail.com> --- changelog.d/7184.bugfix | 1 + .../java/im/vector/lib/multipicker/utils/ImageUtils.kt | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 changelog.d/7184.bugfix diff --git a/changelog.d/7184.bugfix b/changelog.d/7184.bugfix new file mode 100644 index 0000000000..50d7beedd6 --- /dev/null +++ b/changelog.d/7184.bugfix @@ -0,0 +1 @@ +Fix crash on previewing images to upload on Android Pie. diff --git a/library/multipicker/src/main/java/im/vector/lib/multipicker/utils/ImageUtils.kt b/library/multipicker/src/main/java/im/vector/lib/multipicker/utils/ImageUtils.kt index a3d69ae8cf..705223c55e 100644 --- a/library/multipicker/src/main/java/im/vector/lib/multipicker/utils/ImageUtils.kt +++ b/library/multipicker/src/main/java/im/vector/lib/multipicker/utils/ImageUtils.kt @@ -30,7 +30,15 @@ object ImageUtils { fun getBitmap(context: Context, uri: Uri): Bitmap? { return try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - ImageDecoder.decodeBitmap(ImageDecoder.createSource(context.contentResolver, uri)) + val source = ImageDecoder.createSource(context.contentResolver, uri) + val listener = ImageDecoder.OnHeaderDecodedListener { decoder, _, _ -> + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.P) { + // Allocating hardware bitmap may cause a crash on framework versions prior to Android Q + decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE + } + } + + ImageDecoder.decodeBitmap(source, listener) } else { context.contentResolver.openInputStream(uri)?.use { inputStream -> BitmapFactory.decodeStream(inputStream) From c35fa978a73e5d2848203f60a74990716caa2ce3 Mon Sep 17 00:00:00 2001 From: Benoit Marty <benoit@matrix.org> Date: Tue, 4 Oct 2022 15:49:23 +0200 Subject: [PATCH 105/187] Use American English by default. --- .../src/main/res/values/strings.xml | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 2ad3fdfb58..7bc514c73c 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -284,8 +284,8 @@ <string name="notice_end_to_end_ok">%1$s turned on end-to-end encryption.</string> <string name="notice_end_to_end_ok_by_you">You turned on end-to-end encryption.</string> - <string name="notice_end_to_end_unknown_algorithm">%1$s turned on end-to-end encryption (unrecognised algorithm %2$s).</string> - <string name="notice_end_to_end_unknown_algorithm_by_you">You turned on end-to-end encryption (unrecognised algorithm %1$s).</string> + <string name="notice_end_to_end_unknown_algorithm">%1$s turned on end-to-end encryption (unrecognized algorithm %2$s).</string> + <string name="notice_end_to_end_unknown_algorithm_by_you">You turned on end-to-end encryption (unrecognized algorithm %1$s).</string> <!-- theme --> <string name="system_theme">System Default</string> @@ -424,7 +424,7 @@ <!-- Bottom navigation buttons --> <string name="bottom_action_notification">Notifications</string> - <string name="bottom_action_favourites">Favourites</string> + <string name="bottom_action_favourites">Favorites</string> <string name="bottom_action_people">People</string> <string name="bottom_action_rooms">Rooms</string> @@ -774,7 +774,7 @@ <string name="thread_list_modal_all_threads_subtitle">Shows all threads from current room</string> <string name="thread_list_modal_my_threads_title">My Threads</string> <string name="thread_list_modal_my_threads_subtitle">Shows all threads you’ve participated in</string> - <string name="thread_list_empty_title">Keep discussions organised with threads</string> + <string name="thread_list_empty_title">Keep discussions organized with threads</string> <string name="thread_list_empty_subtitle">Threads help keep your conversations on-topic and easy to track.</string> <!-- Parameter %s will be replaced by the value of string reply_in_thread --> <string name="thread_list_empty_notice">Tip: Long tap a message and use “%s”.</string> @@ -819,7 +819,7 @@ <string name="settings_app_info_link_summary">Show the application info in the system settings.</string> <string name="settings_emails">Email addresses</string> - <string name="settings_emails_empty">No email has been added to your account</string> + <string name="settings_emails_empty">No email address has been added to your account</string> <string name="settings_phone_numbers">Phone numbers</string> <string name="settings_remove_three_pid_confirmation_content">Remove %s?</string> <string name="error_threepid_auth_failed">Ensure that you have clicked on the link in the email we have sent to you.</string> @@ -828,7 +828,7 @@ <string name="settings_notification_by_event">Notification importance by event</string> <string name="settings_notification_emails_category">Email notification</string> - <string name="settings_notification_emails_no_emails">To receive email with notification, please associate an email to your Matrix account</string> + <string name="settings_notification_emails_no_emails">To receive email with notification, please associate an email address to your Matrix account</string> <!-- The variable is a single email address, eg Enable email notifications for example@matrix.org --> <string name="settings_notification_emails_enable_for_email">Enable email notifications for %s</string> @@ -1094,7 +1094,7 @@ <string name="settings_unignore_user">Show all messages from %s?</string> <string name="settings_emails_and_phone_numbers_title">Emails and phone numbers</string> - <string name="settings_emails_and_phone_numbers_summary">Manage emails and phone numbers linked to your Matrix account</string> + <string name="settings_emails_and_phone_numbers_summary">Manage emails addresses and phone numbers linked to your Matrix account</string> <string name="settings_select_country">Choose a country</string> @@ -1642,7 +1642,7 @@ <string name="room_list_filter_all">All</string> <string name="room_list_filter_unreads">Unreads</string> - <string name="room_list_filter_favourites">Favourites</string> + <string name="room_list_filter_favourites">Favorites</string> <string name="room_list_filter_people">People</string> <string name="title_activity_emoji_reaction_picker">Reactions</string> @@ -1801,20 +1801,20 @@ <string name="settings_discovery_identity_server_info">You are currently using %1$s to discover and be discoverable by existing contacts you know.</string> <string name="settings_discovery_identity_server_info_none">You are not currently using an identity server. To discover and be discoverable by existing contacts you know, configure one below.</string> <string name="settings_discovery_emails_title">Discoverable email addresses</string> - <string name="settings_discovery_no_mails">Discovery options will appear once you have added an email.</string> + <string name="settings_discovery_no_mails">Discovery options will appear once you have added an email address.</string> <string name="settings_discovery_no_msisdn">Discovery options will appear once you have added a phone number.</string> <string name="settings_discovery_disconnect_identity_server_info">Disconnecting from your identity server will mean you won’t be discoverable by other users and you won’t be able to invite others by email or phone.</string> <string name="settings_discovery_msisdn_title">Discoverable phone numbers</string> - <string name="settings_discovery_confirm_mail">We sent you a confirm email to %s, check your email and click on the confirmation link</string> - <string name="settings_discovery_confirm_mail_not_clicked">We sent you a confirm email to %s, please first check your email and click on the confirmation link</string> + <string name="settings_discovery_confirm_mail">We sent an email to %s, check your email and click on the confirmation link</string> + <string name="settings_discovery_confirm_mail_not_clicked">We sent an email to %s, please first check your email and click on the confirmation link</string> <string name="settings_discovery_consent_title">Send emails and phone numbers</string> - <string name="settings_discovery_consent_notice_on">You have given your consent to send emails and phone numbers to this identity server to discover other users from your contacts.</string> + <string name="settings_discovery_consent_notice_on">You have given your consent to send email addresses and phone numbers to this identity server to discover other users from your contacts.</string> <string name="settings_discovery_consent_notice_off_2">Your contacts are private. To discover users from your contacts, we need your permission to send contact info to your identity server.</string> <string name="settings_discovery_consent_action_revoke">Revoke my consent</string> <string name="settings_discovery_consent_action_give_consent">Give consent</string> - <string name="identity_server_consent_dialog_title_2">Send emails and phone numbers to %s</string> - <string name="identity_server_consent_dialog_content_3">To discover existing contacts, you need to send contact info (emails and phone numbers) to your identity server. We hash your data before sending for privacy.</string> + <string name="identity_server_consent_dialog_title_2">Send email addresses and phone numbers to %s</string> + <string name="identity_server_consent_dialog_content_3">To discover existing contacts, you need to send contact info (email addresses and phone numbers) to your identity server. We hash your data before sending for privacy.</string> <string name="identity_server_consent_dialog_content_question">Do you agree to send this info?</string> <string name="settings_discovery_enter_identity_server">Enter an identity server URL</string> @@ -2039,7 +2039,7 @@ <string name="login_splash_title">It\'s your conversation. Own it.</string> <string name="login_splash_text1">Chat with people directly or in groups</string> <string name="login_splash_text2">Keep conversations private with encryption</string> - <string name="login_splash_text3">Extend & customise your experience</string> + <string name="login_splash_text3">Extend & customize your experience</string> <string name="login_splash_submit">Get started</string> <string name="login_splash_create_account">Create account</string> <string name="login_splash_already_have_account">I already have an account</string> @@ -2082,7 +2082,7 @@ <string name="login_registration_disabled">Sorry, this server isn’t accepting new accounts.</string> <string name="login_registration_not_supported">The application is not able to create an account on this homeserver.\n\nDo you want to signup using a web client?</string> - <string name="login_login_with_email_error">This email is not associated to any account.</string> + <string name="login_login_with_email_error">This email address is not associated to any account.</string> <!-- Replaced string is the homeserver url --> <string name="login_reset_password_on">Reset password on %1$s</string> @@ -2095,7 +2095,7 @@ <string name="login_reset_password_warning_content">Changing your password will reset any end-to-end encryption keys on all of your sessions, making encrypted chat history unreadable. Set up Key Backup or export your room keys from another session before resetting your password.</string> <string name="login_reset_password_warning_submit">Continue</string> - <string name="login_reset_password_error_not_found">This email is not linked to any account</string> + <string name="login_reset_password_error_not_found">This email address is not linked to any account</string> <string name="login_reset_password_mail_confirmation_title">Check your inbox</string> <!-- Replaced string is an email --> @@ -2112,7 +2112,7 @@ <string name="login_reset_password_cancel_confirmation_content">Your password is not yet changed.\n\nStop the password change process?</string> <string name="login_set_email_title">Set email address</string> - <string name="login_set_email_notice">Set an email to recover your account. Later, you can optionally allow people you know to discover you by your email.</string> + <string name="login_set_email_notice">Set an email address to recover your account. Later, you can optionally allow people you know to discover you by your this address.</string> <string name="login_set_email_mandatory_hint">Email</string> <string name="login_set_email_optional_hint">Email (optional)</string> <string name="login_set_email_submit">Next</string> @@ -2263,8 +2263,8 @@ <string name="sent_live_location">Shared their live location</string> <string name="verification_request_waiting">Waiting…</string> - <string name="verification_request_other_cancelled">%s cancelled</string> - <string name="verification_request_you_cancelled">You cancelled</string> + <string name="verification_request_other_cancelled">%s canceled</string> + <string name="verification_request_you_cancelled">You canceled</string> <string name="verification_request_other_accepted">%s accepted</string> <string name="verification_request_you_accepted">You accepted</string> <string name="verification_sent">Verification Sent</string> @@ -2406,7 +2406,7 @@ <string name="verification_profile_device_verified_because">This session is trusted for secure messaging because %1$s (%2$s) verified it:</string> <string name="verification_profile_device_new_signing">%1$s (%2$s) signed in using a new session:</string> - <string name="verification_profile_device_untrust_info">Until this user trusts this session, messages sent to and from it are labelled with warnings. Alternatively, you can manually verify it.</string> + <string name="verification_profile_device_untrust_info">Until this user trusts this session, messages sent to and from it are labeled with warnings. Alternatively, you can manually verify it.</string> <string name="initialize_cross_signing">Initialize CrossSigning</string> @@ -2475,9 +2475,9 @@ One of the following may be compromised:\n\n- Your password\n- Your homeserver\n- This device, or the other device\n- The internet connection either device is using\n\nWe recommend you change your password & recovery key in Settings immediately. </string> - <string name="verify_cancelled_notice">Verification has been cancelled. You can start verification again.</string> + <string name="verify_cancelled_notice">Verification has been canceled. You can start verification again.</string> <string name="verify_invalid_qr_notice">This QR code looks malformed. Please try to verify with another method.</string> - <string name="verification_cancelled">Verification Cancelled</string> + <string name="verification_cancelled">Verification Canceled</string> <string name="recovery_passphrase">Recovery Passphrase</string> <string name="message_key">Message Key</string> @@ -2674,7 +2674,7 @@ <string name="identity_server_error_no_identity_server_configured">Please first configure an identity server.</string> <string name="identity_server_error_terms_not_signed">Please first accepts the terms of the identity server in the settings.</string> <!-- Note to translators: the translation MUST contain the string "${app_name}", which will be replaced by the application name --> - <string name="identity_server_error_bulk_sha256_not_supported">For your privacy, ${app_name} only supports sending hashed user emails and phone number.</string> + <string name="identity_server_error_bulk_sha256_not_supported">For your privacy, ${app_name} only supports sending hashed user email addresses and phone numbers.</string> <string name="identity_server_error_binding_error">The association has failed.</string> <string name="identity_server_error_no_current_binding_error">There is no current association with this identifier.</string> <string name="identity_server_user_consent_not_provided">The user consent has not been provided.</string> @@ -2913,7 +2913,7 @@ <string name="create_spaces_who_are_you_working_with">Who are you working with?</string> <string name="create_spaces_make_sure_access">Make sure the right people have access to %s.</string> <string name="create_spaces_just_me">Just me</string> - <string name="create_spaces_organise_rooms">A private space to organise your rooms</string> + <string name="create_spaces_organise_rooms">A private space to organize your rooms</string> <string name="create_spaces_me_and_teammates">Me and teammates</string> <string name="create_spaces_private_teammates">A private space for you & your teammates</string> <string name="space_type_public">Public</string> @@ -3071,7 +3071,7 @@ <!-- %s will be replaced by an email at runtime --> <string name="this_invite_to_this_space_was_sent">This invite to this space was sent to %s which is not associated with your account</string> - <string name="link_this_email_settings_link">Link this email with your account</string> + <string name="link_this_email_settings_link">Link this email address with your account</string> <!-- %s will be replaced by the value of link_this_email_settings_link and styled as a link --> <string name="link_this_email_with_your_account">%s in Settings to receive invites directly in ${app_name}.</string> From fe3540f6eb08cc11823b4d5007f2050b4df2e444 Mon Sep 17 00:00:00 2001 From: Benoit Marty <benoit@matrix.org> Date: Tue, 4 Oct 2022 15:51:29 +0200 Subject: [PATCH 106/187] Changelog --- changelog.d/7287.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7287.misc diff --git a/changelog.d/7287.misc b/changelog.d/7287.misc new file mode 100644 index 0000000000..4826bf7e9c --- /dev/null +++ b/changelog.d/7287.misc @@ -0,0 +1 @@ +Fix typo in strings.xml and make sure this is American English. From af9548dfdd4edb46642677035a3ad0c1f9e468ed Mon Sep 17 00:00:00 2001 From: SpiritCroc <dev@spiritcroc.de> Date: Tue, 4 Oct 2022 17:59:52 +0200 Subject: [PATCH 107/187] Support inline images in the timeline (#5877) * Support inline images in the timeline Co-authored-by: Benoit Marty <benoitm@matrix.org> --- changelog.d/351.feature | 1 + dependencies.gradle | 1 + vector/build.gradle | 1 + .../app/features/html/EventHtmlRenderer.kt | 28 ++++++++++++++++++- 4 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 changelog.d/351.feature diff --git a/changelog.d/351.feature b/changelog.d/351.feature new file mode 100644 index 0000000000..af86d2fb39 --- /dev/null +++ b/changelog.d/351.feature @@ -0,0 +1 @@ +Render inline images in the timeline diff --git a/dependencies.gradle b/dependencies.gradle index 7792c4ac48..610f8b97aa 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -119,6 +119,7 @@ ext.libs = [ markwon : [ 'core' : "io.noties.markwon:core:$markwon", 'extLatex' : "io.noties.markwon:ext-latex:$markwon", + 'imageGlide' : "io.noties.markwon:image-glide:$markwon", 'inlineParser' : "io.noties.markwon:inline-parser:$markwon", 'html' : "io.noties.markwon:html:$markwon" ], diff --git a/vector/build.gradle b/vector/build.gradle index 13d021e37e..e10d2a3436 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -183,6 +183,7 @@ dependencies { } implementation libs.markwon.core implementation libs.markwon.extLatex + implementation libs.markwon.imageGlide implementation libs.markwon.inlineParser implementation libs.markwon.html implementation 'com.googlecode.htmlcompressor:htmlcompressor:1.5.2' 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 725f23cddd..9e869ecde1 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 @@ -27,8 +27,13 @@ package im.vector.app.features.html import android.content.Context import android.content.res.Resources +import android.graphics.drawable.Drawable import android.text.Spannable import androidx.core.text.toSpannable +import com.bumptech.glide.Glide +import com.bumptech.glide.RequestBuilder +import com.bumptech.glide.request.target.Target +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 @@ -39,12 +44,15 @@ import io.noties.markwon.PrecomputedFutureTextSetterCompat import io.noties.markwon.ext.latex.JLatexMathPlugin import io.noties.markwon.ext.latex.JLatexMathTheme import io.noties.markwon.html.HtmlPlugin +import io.noties.markwon.image.AsyncDrawable +import io.noties.markwon.image.glide.GlideImagesPlugin import io.noties.markwon.inlineparser.EntityInlineProcessor import io.noties.markwon.inlineparser.HtmlInlineProcessor import io.noties.markwon.inlineparser.MarkwonInlineParser import io.noties.markwon.inlineparser.MarkwonInlineParserPlugin import org.commonmark.node.Node import org.commonmark.parser.Parser +import org.matrix.android.sdk.api.MatrixUrls.isMxcUrl import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton @@ -53,7 +61,8 @@ import javax.inject.Singleton class EventHtmlRenderer @Inject constructor( htmlConfigure: MatrixHtmlPluginConfigure, context: Context, - vectorPreferences: VectorPreferences + vectorPreferences: VectorPreferences, + private val activeSessionHolder: ActiveSessionHolder ) { interface PostProcessor { @@ -62,6 +71,23 @@ class EventHtmlRenderer @Inject constructor( private val builder = Markwon.builder(context) .usePlugin(HtmlPlugin.create(htmlConfigure)) + .usePlugin(GlideImagesPlugin.create(object : GlideImagesPlugin.GlideStore { + override fun load(drawable: AsyncDrawable): RequestBuilder<Drawable> { + 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) + } + })) private val markwon = if (vectorPreferences.latexMathsIsEnabled()) { // If latex maths is enabled in app preferences, refomat it so Markwon recognises it From 0b7e52e60b3b072329ad9c72d756c355fed72c32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= <jorgem@element.io> Date: Tue, 4 Oct 2022 21:27:52 +0200 Subject: [PATCH 108/187] Fix EventHtmlRendererTest --- .../im/vector/app/features/html/EventHtmlRendererTest.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 41c0f51322..a2e489dd70 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 @@ -18,6 +18,7 @@ package im.vector.app.features.html import androidx.core.text.toSpannable import androidx.test.platform.app.InstrumentationRegistry +import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.resources.ColorProvider import im.vector.app.core.utils.toTestSpan import im.vector.app.features.settings.VectorPreferences @@ -36,11 +37,13 @@ class EventHtmlRendererTest { private val fakeVectorPreferences = mockk<VectorPreferences>().also { every { it.latexMathsIsEnabled() } returns false } + private val fakeSessionHolder = mockk<ActiveSessionHolder>() private val renderer = EventHtmlRenderer( MatrixHtmlPluginConfigure(ColorProvider(context), context.resources), context, - fakeVectorPreferences + fakeVectorPreferences, + fakeSessionHolder, ) @Test From ed545c7e2bf6b94b2bf0b09c3594e2ad7beeab7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= <jorgem@element.io> Date: Wed, 5 Oct 2022 08:49:56 +0200 Subject: [PATCH 109/187] Try to fix flaky RoomMemberListControllerTest --- .../app/features/RoomMemberListControllerTest.kt | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/vector/src/androidTest/java/im/vector/app/features/RoomMemberListControllerTest.kt b/vector/src/androidTest/java/im/vector/app/features/RoomMemberListControllerTest.kt index d282a8c223..73174e4b34 100644 --- a/vector/src/androidTest/java/im/vector/app/features/RoomMemberListControllerTest.kt +++ b/vector/src/androidTest/java/im/vector/app/features/RoomMemberListControllerTest.kt @@ -18,7 +18,6 @@ package im.vector.app.features import com.airbnb.mvrx.Success import im.vector.app.core.epoxy.profiles.ProfileMatrixItemWithPowerLevelWithPresence -import im.vector.app.core.utils.waitUntil import im.vector.app.features.roomprofile.members.RoomMemberListCategories import im.vector.app.features.roomprofile.members.RoomMemberListController import im.vector.app.features.roomprofile.members.RoomMemberListViewState @@ -31,6 +30,8 @@ import org.matrix.android.sdk.api.session.crypto.model.UserVerificationLevel import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine class RoomMemberListControllerTest { @@ -97,9 +98,12 @@ class RoomMemberListControllerTest { ) ) - roomListController.setData(state) - - waitUntil { !roomListController.hasPendingModelBuild() } + suspendCoroutine { continuation -> + roomListController.setData(state) + roomListController.addModelBuildListener { + continuation.resume(it) + } + } val models = roomListController.adapter.copyOfModels From 5c95cee1c6635d792920d503e250633d8880c9d7 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Mon, 3 Oct 2022 17:27:24 +0200 Subject: [PATCH 110/187] Adding changelog entry --- changelog.d/7277.wip | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7277.wip diff --git a/changelog.d/7277.wip b/changelog.d/7277.wip new file mode 100644 index 0000000000..168d10b809 --- /dev/null +++ b/changelog.d/7277.wip @@ -0,0 +1 @@ +[Device Management] Show correct device type icons From bf502f4f3d3517765f15a8416bef6f5be41302ac Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Tue, 4 Oct 2022 16:01:09 +0200 Subject: [PATCH 111/187] Set the corresponding deviceType icon in list item and in overview view --- .../v2/VectorSettingsDevicesFragment.kt | 5 ++- .../devices/v2/list/OtherSessionItem.kt | 21 ++------- .../v2/list/OtherSessionsController.kt | 2 +- .../devices/v2/list/SessionInfoView.kt | 16 ++++--- .../v2/list/SetDeviceTypeIconUseCase.kt | 45 +++++++++++++++++++ .../v2/overview/SessionOverviewFragment.kt | 5 ++- 6 files changed, 68 insertions(+), 26 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SetDeviceTypeIconUseCase.kt diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt index 0fdbd40178..47ea96c09d 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt @@ -33,6 +33,7 @@ import im.vector.app.core.dialogs.ManuallyVerifyDialog import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.DrawableProvider +import im.vector.app.core.resources.StringProvider import im.vector.app.databinding.FragmentSettingsDevicesBinding import im.vector.app.features.crypto.recover.SetupMode import im.vector.app.features.crypto.verification.VerificationBottomSheet @@ -61,6 +62,8 @@ class VectorSettingsDevicesFragment : @Inject lateinit var colorProvider: ColorProvider + @Inject lateinit var stringProvider: StringProvider + private val viewModel: DevicesViewModel by fragmentViewModel() override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentSettingsDevicesBinding { @@ -237,7 +240,7 @@ class VectorSettingsDevicesFragment : isCurrentSession = true, deviceFullInfo = it ) - views.deviceListCurrentSession.render(viewState, dateFormatter, drawableProvider, colorProvider) + views.deviceListCurrentSession.render(viewState, dateFormatter, drawableProvider, colorProvider, stringProvider) views.deviceListCurrentSession.debouncedClicks { currentDeviceInfo.deviceInfo.deviceId?.let { deviceId -> navigateToSessionOverview(deviceId) } } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionItem.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionItem.kt index 283e64fffe..f83f069a9f 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionItem.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionItem.kt @@ -59,6 +59,8 @@ abstract class OtherSessionItem : VectorEpoxyModel<OtherSessionItem.Holder>(R.la @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var clickListener: ClickListener? = null + private val setDeviceTypeIconUseCase = SetDeviceTypeIconUseCase() + override fun bind(holder: Holder) { super.bind(holder) holder.view.onClick(clickListener) @@ -66,24 +68,7 @@ abstract class OtherSessionItem : VectorEpoxyModel<OtherSessionItem.Holder>(R.la holder.view.isClickable = false } - when (deviceType) { - DeviceType.MOBILE -> { - holder.otherSessionDeviceTypeImageView.setImageResource(R.drawable.ic_device_type_mobile) - holder.otherSessionDeviceTypeImageView.contentDescription = stringProvider.getString(R.string.a11y_device_manager_device_type_mobile) - } - DeviceType.WEB -> { - holder.otherSessionDeviceTypeImageView.setImageResource(R.drawable.ic_device_type_web) - holder.otherSessionDeviceTypeImageView.contentDescription = stringProvider.getString(R.string.a11y_device_manager_device_type_web) - } - DeviceType.DESKTOP -> { - holder.otherSessionDeviceTypeImageView.setImageResource(R.drawable.ic_device_type_desktop) - holder.otherSessionDeviceTypeImageView.contentDescription = stringProvider.getString(R.string.a11y_device_manager_device_type_desktop) - } - DeviceType.UNKNOWN -> { - holder.otherSessionDeviceTypeImageView.setImageResource(R.drawable.ic_device_type_unknown) - holder.otherSessionDeviceTypeImageView.contentDescription = stringProvider.getString(R.string.a11y_device_manager_device_type_unknown) - } - } + setDeviceTypeIconUseCase.execute(deviceType, holder.otherSessionDeviceTypeImageView, stringProvider) holder.otherSessionVerificationStatusImageView.render(roomEncryptionTrustLevel) holder.otherSessionNameTextView.text = sessionName holder.otherSessionDescriptionTextView.text = sessionDescription diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt index afa640fb9a..b0ba8baa1a 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt @@ -64,7 +64,7 @@ class OtherSessionsController @Inject constructor( otherSessionItem { id(device.deviceInfo.deviceId) - deviceType(DeviceType.UNKNOWN) // TODO. We don't have this info yet. Update accordingly. + deviceType(device.deviceExtendedInfo.deviceType) roomEncryptionTrustLevel(device.roomEncryptionTrustLevel) sessionName(device.deviceInfo.displayName) sessionDescription(description) diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoView.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoView.kt index 340a4f3c3a..6f6c5b24e2 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoView.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoView.kt @@ -28,6 +28,7 @@ import im.vector.app.core.extensions.setTextOrHide import im.vector.app.core.extensions.setTextWithColoredPart import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.DrawableProvider +import im.vector.app.core.resources.StringProvider import im.vector.app.databinding.ViewSessionInfoBinding import im.vector.app.features.themes.ThemeUtils import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo @@ -51,13 +52,20 @@ class SessionInfoView @JvmOverloads constructor( val viewDetailsButton = views.sessionInfoViewDetailsButton val viewVerifyButton = views.sessionInfoVerifySessionButton + private val setDeviceTypeIconUseCase = SetDeviceTypeIconUseCase() + fun render( sessionInfoViewState: SessionInfoViewState, dateFormatter: VectorDateFormatter, drawableProvider: DrawableProvider, colorProvider: ColorProvider, + stringProvider: StringProvider, ) { - renderDeviceInfo(sessionInfoViewState.deviceFullInfo.deviceInfo.displayName.orEmpty()) + renderDeviceInfo( + sessionInfoViewState.deviceFullInfo.deviceInfo.displayName.orEmpty(), + sessionInfoViewState.deviceFullInfo.deviceExtendedInfo.deviceType, + stringProvider, + ) renderVerificationStatus( sessionInfoViewState.deviceFullInfo.roomEncryptionTrustLevel, sessionInfoViewState.isCurrentSession, @@ -134,10 +142,8 @@ class SessionInfoView @JvmOverloads constructor( views.sessionInfoVerifySessionButton.isVisible = isVerifyButtonVisible } - // TODO. We don't have this info yet. Update later accordingly. - private fun renderDeviceInfo(sessionName: String) { - views.sessionInfoDeviceTypeImageView.setImageResource(R.drawable.ic_device_type_mobile) - views.sessionInfoDeviceTypeImageView.contentDescription = context.getString(R.string.a11y_device_manager_device_type_mobile) + private fun renderDeviceInfo(sessionName: String, deviceType: DeviceType, stringProvider: StringProvider) { + setDeviceTypeIconUseCase.execute(deviceType, views.sessionInfoDeviceTypeImageView, stringProvider) views.sessionInfoNameTextView.text = sessionName } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SetDeviceTypeIconUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SetDeviceTypeIconUseCase.kt new file mode 100644 index 0000000000..49ff46779e --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SetDeviceTypeIconUseCase.kt @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.settings.devices.v2.list + +import android.widget.ImageView +import im.vector.app.R +import im.vector.app.core.resources.StringProvider + +class SetDeviceTypeIconUseCase { + + fun execute(deviceType: DeviceType, imageView: ImageView, stringProvider: StringProvider) { + when (deviceType) { + DeviceType.MOBILE -> { + imageView.setImageResource(R.drawable.ic_device_type_mobile) + imageView.contentDescription = stringProvider.getString(R.string.a11y_device_manager_device_type_mobile) + } + DeviceType.WEB -> { + imageView.setImageResource(R.drawable.ic_device_type_web) + imageView.contentDescription = stringProvider.getString(R.string.a11y_device_manager_device_type_web) + } + DeviceType.DESKTOP -> { + imageView.setImageResource(R.drawable.ic_device_type_desktop) + imageView.contentDescription = stringProvider.getString(R.string.a11y_device_manager_device_type_desktop) + } + DeviceType.UNKNOWN -> { + imageView.setImageResource(R.drawable.ic_device_type_unknown) + imageView.contentDescription = stringProvider.getString(R.string.a11y_device_manager_device_type_unknown) + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt index 8c3b907070..58b0a13706 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt @@ -37,6 +37,7 @@ 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.resources.DrawableProvider +import im.vector.app.core.resources.StringProvider import im.vector.app.databinding.FragmentSessionOverviewBinding import im.vector.app.features.auth.ReAuthActivity import im.vector.app.features.crypto.recover.SetupMode @@ -64,6 +65,8 @@ class SessionOverviewFragment : @Inject lateinit var colorProvider: ColorProvider + @Inject lateinit var stringProvider: StringProvider + private val viewModel: SessionOverviewViewModel by fragmentViewModel() override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentSessionOverviewBinding { @@ -205,7 +208,7 @@ class SessionOverviewFragment : isLearnMoreLinkVisible = true, isLastSeenDetailsVisible = true, ) - views.sessionOverviewInfo.render(infoViewState, dateFormatter, drawableProvider, colorProvider) + views.sessionOverviewInfo.render(infoViewState, dateFormatter, drawableProvider, colorProvider, stringProvider) views.sessionOverviewInfo.onLearnMoreClickListener = { showLearnMoreInfoVerificationStatus(deviceInfo.roomEncryptionTrustLevel == RoomEncryptionTrustLevel.Trusted) } From 3be1513e0fe64b776f4a1095f187b2c122de3541 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Tue, 4 Oct 2022 17:03:32 +0200 Subject: [PATCH 112/187] Adding unit tests --- .../v2/list/SetDeviceTypeIconUseCaseTest.kt | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 vector/src/test/java/im/vector/app/features/settings/devices/v2/list/SetDeviceTypeIconUseCaseTest.kt diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/list/SetDeviceTypeIconUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/list/SetDeviceTypeIconUseCaseTest.kt new file mode 100644 index 0000000000..30456c596c --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/list/SetDeviceTypeIconUseCaseTest.kt @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.settings.devices.v2.list + +import android.widget.ImageView +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import im.vector.app.R +import im.vector.app.test.fakes.FakeStringProvider +import io.mockk.mockk +import io.mockk.verifyAll +import org.junit.Test + +private const val A_DESCRIPTION = "description" + +class SetDeviceTypeIconUseCaseTest { + + private val fakeStringProvider = FakeStringProvider() + + private val setDeviceTypeIconUseCase = SetDeviceTypeIconUseCase() + + @Test + fun `given a device type when execute then correct icon and description is set to the ImageView`() { + testType( + deviceType = DeviceType.UNKNOWN, + drawableResId = R.drawable.ic_device_type_unknown, + descriptionResId = R.string.a11y_device_manager_device_type_unknown + ) + + testType( + deviceType = DeviceType.MOBILE, + drawableResId = R.drawable.ic_device_type_mobile, + descriptionResId = R.string.a11y_device_manager_device_type_mobile + ) + + testType( + deviceType = DeviceType.WEB, + drawableResId = R.drawable.ic_device_type_web, + descriptionResId = R.string.a11y_device_manager_device_type_web + ) + + testType( + deviceType = DeviceType.DESKTOP, + drawableResId = R.drawable.ic_device_type_desktop, + descriptionResId = R.string.a11y_device_manager_device_type_desktop + ) + } + + private fun testType(deviceType: DeviceType, @DrawableRes drawableResId: Int, @StringRes descriptionResId: Int) { + // Given + val imageView = mockk<ImageView>(relaxUnitFun = true) + fakeStringProvider.given(descriptionResId, A_DESCRIPTION) + + // When + setDeviceTypeIconUseCase.execute(deviceType, imageView, fakeStringProvider.instance) + + // Then + verifyAll { + imageView.setImageResource(drawableResId) + imageView.contentDescription = A_DESCRIPTION + } + } +} From b23520ea40e785d46ec75d9ffc57661f0f01bc0b Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Tue, 4 Oct 2022 17:51:03 +0200 Subject: [PATCH 113/187] Adding new field for last seen user agent in DB with migration --- .../crypto/store/db/RealmCryptoStore.kt | 9 +----- .../store/db/RealmCryptoStoreMigration.kt | 4 ++- .../MyDeviceLastSeenInfoEntityMapper.kt | 14 +++++++- .../store/db/migration/MigrateCryptoTo019.kt | 2 +- .../store/db/migration/MigrateCryptoTo020.kt | 32 +++++++++++++++++++ .../db/model/MyDeviceLastSeenInfoEntity.kt | 4 ++- 6 files changed, 53 insertions(+), 12 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo020.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index 6a2ef3bde1..952afc10f5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -624,14 +624,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun saveMyDevicesInfo(info: List<DeviceInfo>) { - val entities = info.map { - MyDeviceLastSeenInfoEntity( - lastSeenTs = it.lastSeenTs, - lastSeenIp = it.lastSeenIp, - displayName = it.displayName, - deviceId = it.deviceId - ) - } + val entities = info.map { myDeviceLastSeenInfoEntityMapper.map(it) } doRealmTransactionAsync(realmConfiguration) { realm -> realm.where<MyDeviceLastSeenInfoEntity>().findAll().deleteAllFromRealm() entities.forEach { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt index de2b74308d..9129453c8a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt @@ -36,6 +36,7 @@ import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo017 import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo018 import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo019 +import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo020 import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration import org.matrix.android.sdk.internal.util.time.Clock import javax.inject.Inject @@ -50,7 +51,7 @@ internal class RealmCryptoStoreMigration @Inject constructor( private val clock: Clock, ) : MatrixRealmMigration( dbName = "Crypto", - schemaVersion = 19L, + schemaVersion = 20L, ) { /** * Forces all RealmCryptoStoreMigration instances to be equal. @@ -79,5 +80,6 @@ internal class RealmCryptoStoreMigration @Inject constructor( if (oldVersion < 17) MigrateCryptoTo017(realm).perform() if (oldVersion < 18) MigrateCryptoTo018(realm).perform() if (oldVersion < 19) MigrateCryptoTo019(realm).perform() + if (oldVersion < 20) MigrateCryptoTo020(realm).perform() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/MyDeviceLastSeenInfoEntityMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/MyDeviceLastSeenInfoEntityMapper.kt index 38a7569aab..e1cb5d78bd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/MyDeviceLastSeenInfoEntityMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/MyDeviceLastSeenInfoEntityMapper.kt @@ -22,12 +22,24 @@ import javax.inject.Inject internal class MyDeviceLastSeenInfoEntityMapper @Inject constructor() { + // TODO add unit tests fun map(entity: MyDeviceLastSeenInfoEntity): DeviceInfo { return DeviceInfo( deviceId = entity.deviceId, lastSeenIp = entity.lastSeenIp, lastSeenTs = entity.lastSeenTs, - displayName = entity.displayName + displayName = entity.displayName, + unstableLastSeenUserAgent = entity.lastSeenUserAgent, + ) + } + + fun map(deviceInfo: DeviceInfo): MyDeviceLastSeenInfoEntity { + return MyDeviceLastSeenInfoEntity( + deviceId = deviceInfo.deviceId, + lastSeenIp = deviceInfo.lastSeenIp, + lastSeenTs = deviceInfo.lastSeenTs, + displayName = deviceInfo.displayName, + lastSeenUserAgent = deviceInfo.getBestLastSeenUserAgent(), ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo019.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo019.kt index 9d2eb60a60..65280300ab 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo019.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo019.kt @@ -30,7 +30,7 @@ import org.matrix.android.sdk.internal.util.database.RealmMigrator * mark existing keys as safe. * This migration can take long depending on the account */ -internal class MigrateCryptoTo019(realm: DynamicRealm) : RealmMigrator(realm, 18) { +internal class MigrateCryptoTo019(realm: DynamicRealm) : RealmMigrator(realm, 19) { override fun doMigrate(realm: DynamicRealm) { realm.schema.get("CrossSigningInfoEntity") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo020.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo020.kt new file mode 100644 index 0000000000..44d07ab538 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo020.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2022 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.crypto.store.db.migration + +import io.realm.DynamicRealm +import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntityFields +import org.matrix.android.sdk.internal.util.database.RealmMigrator + +/** + * This migration adds a new field into MyDeviceLastSeenInfoEntity corresponding to the last seen user agent. + */ +internal class MigrateCryptoTo020(realm: DynamicRealm) : RealmMigrator(realm, 20) { + + override fun doMigrate(realm: DynamicRealm) { + realm.schema.get("MyDeviceLastSeenInfoEntity") + ?.addField(MyDeviceLastSeenInfoEntityFields.LAST_SEEN_USER_AGENT, String::class.java) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/MyDeviceLastSeenInfoEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/MyDeviceLastSeenInfoEntity.kt index 74a81d5b01..3e6dc2de16 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/MyDeviceLastSeenInfoEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/MyDeviceLastSeenInfoEntity.kt @@ -27,7 +27,9 @@ internal open class MyDeviceLastSeenInfoEntity( /** The last time this device has been seen. */ var lastSeenTs: Long? = null, /** The last ip address. */ - var lastSeenIp: String? = null + var lastSeenIp: String? = null, + /** The last user agent. */ + var lastSeenUserAgent: String? = null, ) : RealmObject() { companion object From f02b689ce095f803f658ff9d30076c223a808cc0 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Tue, 4 Oct 2022 18:00:14 +0200 Subject: [PATCH 114/187] Adding unit tests for mapper --- .../MyDeviceLastSeenInfoEntityMapper.kt | 1 - .../MyDeviceLastSeenInfoEntityMapperTest.kt | 39 ++++++++++++++++++- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/MyDeviceLastSeenInfoEntityMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/MyDeviceLastSeenInfoEntityMapper.kt index e1cb5d78bd..b81883fb38 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/MyDeviceLastSeenInfoEntityMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/MyDeviceLastSeenInfoEntityMapper.kt @@ -22,7 +22,6 @@ import javax.inject.Inject internal class MyDeviceLastSeenInfoEntityMapper @Inject constructor() { - // TODO add unit tests fun map(entity: MyDeviceLastSeenInfoEntity): DeviceInfo { return DeviceInfo( deviceId = entity.deviceId, diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/MyDeviceLastSeenInfoEntityMapperTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/MyDeviceLastSeenInfoEntityMapperTest.kt index a27f430edc..8515427e8e 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/MyDeviceLastSeenInfoEntityMapperTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/MyDeviceLastSeenInfoEntityMapperTest.kt @@ -25,6 +25,7 @@ private const val A_DEVICE_ID = "device-id" private const val AN_IP_ADDRESS = "ip-address" private const val A_TIMESTAMP = 123L private const val A_DISPLAY_NAME = "display-name" +private const val A_USER_AGENT = "user-agent" class MyDeviceLastSeenInfoEntityMapperTest { @@ -32,21 +33,55 @@ class MyDeviceLastSeenInfoEntityMapperTest { @Test fun `given an entity when mapping to model then all fields are correctly mapped`() { + // Given val entity = MyDeviceLastSeenInfoEntity( deviceId = A_DEVICE_ID, lastSeenIp = AN_IP_ADDRESS, lastSeenTs = A_TIMESTAMP, - displayName = A_DISPLAY_NAME + displayName = A_DISPLAY_NAME, + lastSeenUserAgent = A_USER_AGENT, ) val expectedDeviceInfo = DeviceInfo( deviceId = A_DEVICE_ID, lastSeenIp = AN_IP_ADDRESS, lastSeenTs = A_TIMESTAMP, - displayName = A_DISPLAY_NAME + displayName = A_DISPLAY_NAME, + unstableLastSeenUserAgent = A_USER_AGENT, ) + // When val deviceInfo = myDeviceLastSeenInfoEntityMapper.map(entity) + // Then deviceInfo shouldBeEqualTo expectedDeviceInfo } + + @Test + fun `given a device info when mapping to entity then all fields are correctly mapped`() { + // Given + val deviceInfo = DeviceInfo( + deviceId = A_DEVICE_ID, + lastSeenIp = AN_IP_ADDRESS, + lastSeenTs = A_TIMESTAMP, + displayName = A_DISPLAY_NAME, + unstableLastSeenUserAgent = A_USER_AGENT, + ) + val expectedEntity = MyDeviceLastSeenInfoEntity( + deviceId = A_DEVICE_ID, + lastSeenIp = AN_IP_ADDRESS, + lastSeenTs = A_TIMESTAMP, + displayName = A_DISPLAY_NAME, + lastSeenUserAgent = A_USER_AGENT + ) + + // When + val entity = myDeviceLastSeenInfoEntityMapper.map(deviceInfo) + + // Then + entity.deviceId shouldBeEqualTo expectedEntity.deviceId + entity.lastSeenIp shouldBeEqualTo expectedEntity.lastSeenIp + entity.lastSeenTs shouldBeEqualTo expectedEntity.lastSeenTs + entity.displayName shouldBeEqualTo expectedEntity.displayName + entity.lastSeenUserAgent shouldBeEqualTo expectedEntity.lastSeenUserAgent + } } From e9b33f6234b2fce18ebef47e98e68e3d3b16ac33 Mon Sep 17 00:00:00 2001 From: Valere <valeref@matrix.org> Date: Wed, 3 Aug 2022 14:28:36 +0200 Subject: [PATCH 115/187] Per room block unverified devices --- changelog.d/6725.bugfix | 1 + .../src/main/res/values/strings.xml | 3 + .../sdk/internal/crypto/E2eeTestConfig.kt | 132 ++++++++++++++++++ .../sdk/api/session/crypto/CryptoService.kt | 7 +- .../api/session/crypto/GlobalCryptoConfig.kt | 23 +++ .../internal/crypto/DefaultCryptoService.kt | 31 ++-- .../algorithms/megolm/MXMegolmEncryption.kt | 2 +- .../internal/crypto/store/IMXCryptoStore.kt | 25 +++- .../crypto/store/db/RealmCryptoStore.kt | 83 ++++++++--- .../roomprofile/RoomProfileViewState.kt | 2 +- .../settings/RoomSettingsAction.kt | 1 + .../settings/RoomSettingsController.kt | 52 +++++++ .../settings/RoomSettingsFragment.kt | 4 + .../settings/RoomSettingsViewModel.kt | 47 +++++++ .../settings/RoomSettingsViewState.kt | 6 +- .../src/main/res/layout/item_form_switch.xml | 2 + 16 files changed, 376 insertions(+), 45 deletions(-) create mode 100644 changelog.d/6725.bugfix create mode 100644 matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeTestConfig.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/GlobalCryptoConfig.kt diff --git a/changelog.d/6725.bugfix b/changelog.d/6725.bugfix new file mode 100644 index 0000000000..f05ddbc69d --- /dev/null +++ b/changelog.d/6725.bugfix @@ -0,0 +1 @@ +Add option to only send to verified devices per room (web parity) diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 5aeac44a55..cc339f91d9 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -1234,6 +1234,9 @@ <string name="encryption_import_import">Import</string> <string name="encryption_never_send_to_unverified_devices_title">Encrypt to verified sessions only</string> <string name="encryption_never_send_to_unverified_devices_summary">Never send encrypted messages to unverified sessions from this session.</string> + <string name="encryption_never_send_to_unverified_devices_in_room">Never send encrypted messages to unverified sessions in this room.</string> + <string name="some_devices_will_not_be_able_to_decrypt">⚠ There are unverified devices in this room, they won’t be able to decrypt messages you send.</string> + <string name="room_settings_global_blacklist_unverified_info_text">🔒 You have enable encrypt to verified sessions only for all rooms in Security Settings.</string> <plurals name="encryption_import_room_keys_success"> <item quantity="one">%1$d/%2$d key imported with success.</item> <item quantity="other">%1$d/%2$d keys imported with success.</item> diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeTestConfig.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeTestConfig.kt new file mode 100644 index 0000000000..7a03276d34 --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeTestConfig.kt @@ -0,0 +1,132 @@ +/* + * Copyright 2022 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.crypto + +import androidx.test.filters.LargeTest +import org.amshove.kluent.shouldBe +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.runners.MethodSorters +import org.matrix.android.sdk.InstrumentedTest +import org.matrix.android.sdk.api.session.crypto.MXCryptoError +import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent +import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest + +@RunWith(JUnit4::class) +@FixMethodOrder(MethodSorters.JVM) +@LargeTest +class E2eeTestConfig : InstrumentedTest { + + @Test + fun testBlacklistUnverifiedDefault() = runCryptoTest(context()) { cryptoTestHelper, _ -> + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true) + + cryptoTestData.firstSession.cryptoService().getGlobalBlacklistUnverifiedDevices() shouldBe false + cryptoTestData.firstSession.cryptoService().isRoomBlacklistUnverifiedDevices(cryptoTestData.roomId) shouldBe false + cryptoTestData.secondSession!!.cryptoService().getGlobalBlacklistUnverifiedDevices() shouldBe false + cryptoTestData.secondSession!!.cryptoService().isRoomBlacklistUnverifiedDevices(cryptoTestData.roomId) shouldBe false + } + + @Test + fun testCantDecryptIfGlobalUnverified() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true) + + cryptoTestData.firstSession.cryptoService().setGlobalBlacklistUnverifiedDevices(true) + + val roomAlicePOV = cryptoTestData.firstSession.roomService().getRoom(cryptoTestData.roomId)!! + + val sentMessage = testHelper.sendTextMessage(roomAlicePOV, "you are blocked", 1).first() + + val roomBobPOV = cryptoTestData.secondSession!!.roomService().getRoom(cryptoTestData.roomId)!! + // ensure other received + testHelper.waitWithLatch { latch -> + testHelper.retryPeriodicallyWithLatch(latch) { + roomBobPOV.timelineService().getTimelineEvent(sentMessage.eventId) != null + } + } + + cryptoTestHelper.ensureCannotDecrypt(listOf(sentMessage.eventId), cryptoTestData.secondSession!!, cryptoTestData.roomId) + } + + @Test + fun testCanDecryptIfGlobalUnverifiedAndUserTrusted() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true) + + cryptoTestHelper.initializeCrossSigning(cryptoTestData.firstSession) + cryptoTestHelper.initializeCrossSigning(cryptoTestData.secondSession!!) + + cryptoTestHelper.verifySASCrossSign(cryptoTestData.firstSession, cryptoTestData.secondSession!!, cryptoTestData.roomId) + + cryptoTestData.firstSession.cryptoService().setGlobalBlacklistUnverifiedDevices(true) + + val roomAlicePOV = cryptoTestData.firstSession.roomService().getRoom(cryptoTestData.roomId)!! + + val sentMessage = testHelper.sendTextMessage(roomAlicePOV, "you can read", 1).first() + + val roomBobPOV = cryptoTestData.secondSession!!.roomService().getRoom(cryptoTestData.roomId)!! + // ensure other received + testHelper.waitWithLatch { latch -> + testHelper.retryPeriodicallyWithLatch(latch) { + roomBobPOV.timelineService().getTimelineEvent(sentMessage.eventId) != null + } + } + + cryptoTestHelper.ensureCanDecrypt( + listOf(sentMessage.eventId), + cryptoTestData.secondSession!!, + cryptoTestData.roomId, + listOf(sentMessage.getLastMessageContent()!!.body) + ) + } + + @Test + fun testCantDecryptIfPerRoomUnverified() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true) + + val roomAlicePOV = cryptoTestData.firstSession.roomService().getRoom(cryptoTestData.roomId)!! + + val beforeMessage = testHelper.sendTextMessage(roomAlicePOV, "you can read", 1).first() + + val roomBobPOV = cryptoTestData.secondSession!!.roomService().getRoom(cryptoTestData.roomId)!! + // ensure other received + testHelper.waitWithLatch { latch -> + testHelper.retryPeriodicallyWithLatch(latch) { + roomBobPOV.timelineService().getTimelineEvent(beforeMessage.eventId) != null + } + } + + cryptoTestHelper.ensureCanDecrypt( + listOf(beforeMessage.eventId), + cryptoTestData.secondSession!!, + cryptoTestData.roomId, + listOf(beforeMessage.getLastMessageContent()!!.body) + ) + + cryptoTestData.firstSession.cryptoService().setRoomBlacklistUnverifiedDevices(cryptoTestData.roomId, true) + + val afterMessage = testHelper.sendTextMessage(roomAlicePOV, "you are blocked", 1).first() + + cryptoTestHelper.ensureCannotDecrypt( + listOf(afterMessage.eventId), + cryptoTestData.secondSession!!, + cryptoTestData.roomId, + MXCryptoError.ErrorType.KEYS_WITHHELD + ) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt index e0e662c789..5e0c087c08 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt @@ -42,6 +42,7 @@ import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.crypto.model.SessionInfo +import org.matrix.android.sdk.api.session.crypto.GlobalCryptoConfig interface CryptoService { @@ -61,6 +62,8 @@ interface CryptoService { fun isRoomBlacklistUnverifiedDevices(roomId: String?): Boolean + fun getLiveBlacklistUnverifiedDevices(roomId: String): LiveData<Boolean> + fun setWarnOnUnknownDevices(warn: Boolean) fun setDeviceVerification(trustLevel: DeviceTrustLevel, userId: String, deviceId: String) @@ -77,6 +80,8 @@ interface CryptoService { fun setGlobalBlacklistUnverifiedDevices(block: Boolean) + fun getLiveGlobalCryptoConfig(): LiveData<GlobalCryptoConfig> + /** * Enable or disable key gossiping. * Default is true. @@ -112,7 +117,7 @@ interface CryptoService { suspend fun exportRoomKeys(password: String): ByteArray - fun setRoomBlacklistUnverifiedDevices(roomId: String) + fun setRoomBlacklistUnverifiedDevices(roomId: String, enable: Boolean) fun getCryptoDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo? diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/GlobalCryptoConfig.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/GlobalCryptoConfig.kt new file mode 100644 index 0000000000..a6afe6087b --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/GlobalCryptoConfig.kt @@ -0,0 +1,23 @@ +/* + * 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 org.matrix.android.sdk.api.session.crypto + +data class GlobalCryptoConfig( + val globalBlacklistUnverifiedDevices: Boolean, + val globalEnableKeyGossiping: Boolean, + val enableKeyForwardingOnInvite: Boolean, +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index 901700cac6..f37be4ff62 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -87,6 +87,7 @@ import org.matrix.android.sdk.internal.crypto.model.MXKey.Companion.KEY_SIGNED_C import org.matrix.android.sdk.internal.crypto.model.SessionInfo import org.matrix.android.sdk.internal.crypto.model.toRest import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository +import org.matrix.android.sdk.api.session.crypto.GlobalCryptoConfig import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.tasks.DeleteDeviceTask import org.matrix.android.sdk.internal.crypto.tasks.GetDeviceInfoTask @@ -1163,6 +1164,10 @@ internal class DefaultCryptoService @Inject constructor( return cryptoStore.getGlobalBlacklistUnverifiedDevices() } + override fun getLiveGlobalCryptoConfig(): LiveData<GlobalCryptoConfig> { + return cryptoStore.getLiveGlobalCryptoConfig() + } + /** * Tells whether the client should encrypt messages only for the verified devices * in this room. @@ -1171,30 +1176,18 @@ internal class DefaultCryptoService @Inject constructor( * @param roomId the room id * @return true if the client should encrypt messages only for the verified devices. */ -// TODO add this info in CryptoRoomEntity? override fun isRoomBlacklistUnverifiedDevices(roomId: String?): Boolean { - return roomId?.let { cryptoStore.getRoomsListBlacklistUnverifiedDevices().contains(it) } + return roomId?.let { cryptoStore.getBlacklistUnverifiedDevices(roomId) } ?: false } /** - * Manages the room black-listing for unverified devices. + * A live status regarding sharing keys for unverified devices in this room. * - * @param roomId the room id - * @param add true to add the room id to the list, false to remove it. + * @return Live status */ - private fun setRoomBlacklistUnverifiedDevices(roomId: String, add: Boolean) { - val roomIds = cryptoStore.getRoomsListBlacklistUnverifiedDevices().toMutableList() - - if (add) { - if (roomId !in roomIds) { - roomIds.add(roomId) - } - } else { - roomIds.remove(roomId) - } - - cryptoStore.setRoomsListBlacklistUnverifiedDevices(roomIds) + override fun getLiveBlacklistUnverifiedDevices(roomId: String): LiveData<Boolean> { + return cryptoStore.getLiveBlacklistUnverifiedDevices(roomId) } /** @@ -1202,8 +1195,8 @@ internal class DefaultCryptoService @Inject constructor( * * @param roomId the room id */ - override fun setRoomBlacklistUnverifiedDevices(roomId: String) { - setRoomBlacklistUnverifiedDevices(roomId, true) + override fun setRoomBlacklistUnverifiedDevices(roomId: String, enable: Boolean) { + cryptoStore.blackListUnverifiedDevicesInRoom(roomId, enable) } /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt index fca6fab66c..e19e513b63 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt @@ -424,7 +424,7 @@ internal class MXMegolmEncryption( // an m.new_device. val keys = deviceListManager.downloadKeys(userIds, false) val encryptToVerifiedDevicesOnly = cryptoStore.getGlobalBlacklistUnverifiedDevices() || - cryptoStore.getRoomsListBlacklistUnverifiedDevices().contains(roomId) + cryptoStore.getBlacklistUnverifiedDevices(roomId) val devicesInRoom = DeviceInRoomInfo() val unknownDevices = MXUsersDevicesMap<CryptoDeviceInfo>() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt index 56eba25249..193b53ec4e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.crypto.store import androidx.lifecycle.LiveData import androidx.paging.PagedList +import org.matrix.android.sdk.api.session.crypto.GlobalCryptoConfig import org.matrix.android.sdk.api.session.crypto.NewSessionListener import org.matrix.android.sdk.api.session.crypto.OutgoingKeyRequest import org.matrix.android.sdk.api.session.crypto.OutgoingRoomKeyRequestState @@ -120,11 +121,26 @@ internal interface IMXCryptoStore { fun getRoomsListBlacklistUnverifiedDevices(): List<String> /** - * Updates the rooms ids list in which the messages are not encrypted for the unverified devices. + * A live status regarding sharing keys for unverified devices in this room. * - * @param roomIds the room ids list + * @return Live status */ - fun setRoomsListBlacklistUnverifiedDevices(roomIds: List<String>) + fun getLiveBlacklistUnverifiedDevices(roomId: String): LiveData<Boolean> + + /** + * Tell if unverified devices should be blacklisted when sending keys. + * + * @return true if should not send keys to unverified devices + */ + fun getBlacklistUnverifiedDevices(roomId: String): Boolean + + /** + * Define if encryption keys should be sent to unverified devices in this room. + * + * @param roomId the roomId + * @param blacklist if true will not send keys to unverified devices + */ + fun blackListUnverifiedDevicesInRoom(roomId: String, blacklist: Boolean) /** * Get the current keys backup version. @@ -516,6 +532,9 @@ internal interface IMXCryptoStore { fun getCrossSigningPrivateKeys(): PrivateKeysInfo? fun getLiveCrossSigningPrivateKeys(): LiveData<Optional<PrivateKeysInfo>> + fun getGlobalCryptoConfig(): GlobalCryptoConfig + fun getLiveGlobalCryptoConfig(): LiveData<GlobalCryptoConfig> + fun saveBackupRecoveryKey(recoveryKey: String?, version: String?) fun getKeyBackupRecoveryKeyInfo(): SavedKeyBackupKeyInfo? diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index 6a2ef3bde1..801d012385 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -53,6 +53,7 @@ import org.matrix.android.sdk.api.util.toOptional import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper import org.matrix.android.sdk.internal.crypto.model.OutboundGroupSessionWrapper +import org.matrix.android.sdk.api.session.crypto.GlobalCryptoConfig import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.store.db.mapper.CrossSigningKeysMapper import org.matrix.android.sdk.internal.crypto.store.db.mapper.MyDeviceLastSeenInfoEntityMapper @@ -445,6 +446,38 @@ internal class RealmCryptoStore @Inject constructor( } } + override fun getGlobalCryptoConfig(): GlobalCryptoConfig { + return doWithRealm(realmConfiguration) { realm -> + realm.where<CryptoMetadataEntity>().findFirst() + ?.let { + GlobalCryptoConfig( + globalBlacklistUnverifiedDevices = it.globalBlacklistUnverifiedDevices, + globalEnableKeyGossiping = it.globalEnableKeyGossiping, + enableKeyForwardingOnInvite = it.enableKeyForwardingOnInvite + ) + } ?: GlobalCryptoConfig(false, false, false) + } + } + + override fun getLiveGlobalCryptoConfig(): LiveData<GlobalCryptoConfig> { + val liveData = monarchy.findAllMappedWithChanges( + { realm: Realm -> + realm + .where<CryptoMetadataEntity>() + }, + { + GlobalCryptoConfig( + globalBlacklistUnverifiedDevices = it.globalBlacklistUnverifiedDevices, + globalEnableKeyGossiping = it.globalEnableKeyGossiping, + enableKeyForwardingOnInvite = it.enableKeyForwardingOnInvite + ) + } + ) + return Transformations.map(liveData) { + it.firstOrNull() ?: GlobalCryptoConfig(false, false, false) + } + } + override fun storePrivateKeysInfo(msk: String?, usk: String?, ssk: String?) { Timber.v("## CRYPTO | *** storePrivateKeysInfo ${msk != null}, ${usk != null}, ${ssk != null}") doRealmTransaction(realmConfiguration) { realm -> @@ -1053,25 +1086,6 @@ internal class RealmCryptoStore @Inject constructor( } ?: false } - override fun setRoomsListBlacklistUnverifiedDevices(roomIds: List<String>) { - doRealmTransaction(realmConfiguration) { - // Reset all - it.where<CryptoRoomEntity>() - .findAll() - .forEach { room -> - room.blacklistUnverifiedDevices = false - } - - // Enable those in the list - it.where<CryptoRoomEntity>() - .`in`(CryptoRoomEntityFields.ROOM_ID, roomIds.toTypedArray()) - .findAll() - .forEach { room -> - room.blacklistUnverifiedDevices = true - } - } - } - override fun getRoomsListBlacklistUnverifiedDevices(): List<String> { return doWithRealm(realmConfiguration) { it.where<CryptoRoomEntity>() @@ -1083,6 +1097,37 @@ internal class RealmCryptoStore @Inject constructor( } } + override fun getLiveBlacklistUnverifiedDevices(roomId: String): LiveData<Boolean> { + val liveData = monarchy.findAllMappedWithChanges( + { realm: Realm -> + realm.where<CryptoRoomEntity>() + .equalTo(CryptoRoomEntityFields.ROOM_ID, roomId) + }, + { + it.blacklistUnverifiedDevices + } + ) + return Transformations.map(liveData) { + it.firstOrNull() ?: false + } + } + + override fun getBlacklistUnverifiedDevices(roomId: String): Boolean { + return doWithRealm(realmConfiguration) { realm -> + realm.where<CryptoRoomEntity>() + .equalTo(CryptoRoomEntityFields.ROOM_ID, roomId) + .findFirst() + ?.blacklistUnverifiedDevices ?: false + } + } + + override fun blackListUnverifiedDevicesInRoom(roomId: String, blacklist: Boolean) { + doRealmTransaction(realmConfiguration) { realm -> + CryptoRoomEntity.getById(realm, roomId) + ?.blacklistUnverifiedDevices = blacklist + } + } + override fun getDeviceTrackingStatuses(): Map<String, Int> { return doWithRealm(realmConfiguration) { it.where<UserEntity>() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewState.kt index 87db15ea3b..c457a01750 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewState.kt @@ -35,7 +35,7 @@ data class RoomProfileViewState( val recommendedRoomVersion: String? = null, val canUpgradeRoom: Boolean = false, val isTombstoned: Boolean = false, - val canUpdateRoomState: Boolean = false + val canUpdateRoomState: Boolean = false, ) : MavericksState { constructor(args: RoomProfileArgs) : this(roomId = args.roomId) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsAction.kt index eb601605e0..bb2dc08e76 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsAction.kt @@ -28,6 +28,7 @@ sealed class RoomSettingsAction : VectorViewModelAction { data class SetRoomHistoryVisibility(val visibility: RoomHistoryVisibility) : RoomSettingsAction() data class SetRoomJoinRule(val roomJoinRule: RoomJoinRules) : RoomSettingsAction() data class SetRoomGuestAccess(val guestAccess: GuestAccess) : RoomSettingsAction() + data class SetEncryptToVerifiedDeviceOnly(val enable: Boolean) : RoomSettingsAction() object Save : RoomSettingsAction() object Cancel : RoomSettingsAction() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt index f8efe73ebf..936ebbd861 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt @@ -22,6 +22,7 @@ import im.vector.app.core.epoxy.dividerItem import im.vector.app.core.epoxy.profiles.buildProfileAction import im.vector.app.core.epoxy.profiles.buildProfileSection import im.vector.app.core.resources.StringProvider +import im.vector.app.core.ui.list.genericFooterItem import im.vector.app.core.ui.list.verticalMarginItem import im.vector.app.core.utils.DimensionConverter import im.vector.app.features.form.formEditTextItem @@ -30,6 +31,8 @@ import im.vector.app.features.form.formSwitchItem import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.timeline.format.RoomHistoryVisibilityFormatter import im.vector.app.features.settings.VectorPreferences +import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence +import me.gujun.android.span.span import org.matrix.android.sdk.api.session.room.model.GuestAccess import org.matrix.android.sdk.api.session.room.model.RoomJoinRules import org.matrix.android.sdk.api.util.toMatrixItem @@ -52,6 +55,7 @@ class RoomSettingsController @Inject constructor( fun onHistoryVisibilityClicked() fun onJoinRuleClicked() fun onToggleGuestAccess() + fun setEncryptedToVerifiedDevicesOnly(enabled: Boolean) } var callback: Callback? = null @@ -145,5 +149,53 @@ class RoomSettingsController @Inject constructor( id("guestAccessDivider") } } + + // Security + buildProfileSection(stringProvider.getString(R.string.room_profile_section_security)) + + data.globalCryptoConfig.invoke()?.let { globalConfig -> + if (globalConfig.globalBlacklistUnverifiedDevices) { + genericFooterItem { + id("globalConfig") + centered(false) + text( + span { + +host.stringProvider.getString(R.string.room_settings_global_blacklist_unverified_info_text) + apply { + if (data.unverifiedDevicesInTheRoom.invoke() == true) { + +"\n" + +host.stringProvider.getString(R.string.some_devices_will_not_be_able_to_decrypt) + } + } + }.toEpoxyCharSequence() + ) + itemClickAction { + } + } + } else { + // per room setting is available + val shouldBlockUnverified = data.encryptToVerifiedDeviceOnly.invoke() + formSwitchItem { + id("send_to_unverified") + enabled(shouldBlockUnverified != null) + title(host.stringProvider.getString(R.string.encryption_never_send_to_unverified_devices_in_room)) + + switchChecked(shouldBlockUnverified ?: false) + + apply { + if (shouldBlockUnverified == true && data.unverifiedDevicesInTheRoom.invoke() == true) { + summary( + host.stringProvider.getString(R.string.some_devices_will_not_be_able_to_decrypt) + ) + } else { + summary(null) + } + } + listener { value -> + host.callback?.setEncryptedToVerifiedDevicesOnly(value) + } + } + } + } } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt index ba50890db3..b7d8f13343 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt @@ -199,6 +199,10 @@ class RoomSettingsFragment : viewModel.handle(RoomSettingsAction.SetRoomGuestAccess(toggled)) } + override fun setEncryptedToVerifiedDevicesOnly(enabled: Boolean) { + viewModel.handle(RoomSettingsAction.SetEncryptToVerifiedDeviceOnly(enabled)) + } + override fun onImageReady(uri: Uri?) { uri ?: return viewModel.handle( diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt index 501ff7553a..cb5809d96a 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt @@ -17,6 +17,7 @@ package im.vector.app.features.roomprofile.settings import androidx.core.net.toFile +import androidx.lifecycle.asFlow import com.airbnb.mvrx.MavericksViewModelFactory import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -25,8 +26,12 @@ 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.powerlevel.PowerLevelsFlowFactory +import im.vector.app.features.session.coroutineScope import im.vector.app.features.settings.VectorPreferences +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch @@ -37,6 +42,8 @@ import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities +import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams +import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomAvatarContent import org.matrix.android.sdk.api.session.room.model.RoomGuestAccessContent import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent @@ -83,6 +90,39 @@ class RoomSettingsViewModel @AssistedInject constructor( canUpgradeToRestricted = couldUpgradeToRestricted ) } + + session.cryptoService().getLiveBlacklistUnverifiedDevices(initialState.roomId) + .asFlow() + .execute { + copy(encryptToVerifiedDeviceOnly = it) + } + + session.cryptoService().getLiveGlobalCryptoConfig() + .asFlow() + .execute { + copy(globalCryptoConfig = it) + } + + val flowRoom = room.flow() + session.cryptoService().getLiveBlacklistUnverifiedDevices(initialState.roomId) + .asFlow() + .flatMapLatest { + if (it) { + flowRoom.liveRoomMembers(roomMemberQueryParams { memberships = Membership.activeMemberships() }) + .map { it.map { it.userId } } + .flatMapLatest { + session.cryptoService().getLiveCryptoDeviceInfo(it).asFlow() + } + } else { + flowOf(emptyList()) + } + }.map { + it.isNotEmpty() + }.execute { + copy( + unverifiedDevicesInTheRoom = it + ) + } } private fun observeState() { @@ -212,6 +252,7 @@ class RoomSettingsViewModel @AssistedInject constructor( is RoomSettingsAction.SetRoomGuestAccess -> handleSetGuestAccess(action) is RoomSettingsAction.Save -> saveSettings() is RoomSettingsAction.Cancel -> cancel() + is RoomSettingsAction.SetEncryptToVerifiedDeviceOnly -> setEncryptToVerifiedDeviceOnly(action.enable) } } @@ -233,6 +274,12 @@ class RoomSettingsViewModel @AssistedInject constructor( } } + private fun setEncryptToVerifiedDeviceOnly(enabled: Boolean) { + session.coroutineScope.launch { + session.cryptoService().setRoomBlacklistUnverifiedDevices(room.roomId, enabled) + } + } + private fun handleSetAvatarAction(action: RoomSettingsAction.SetAvatarAction) { setState { deletePendingAvatar(this) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewState.kt index 81e98335c0..e3c6cd9b03 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewState.kt @@ -27,6 +27,7 @@ import org.matrix.android.sdk.api.session.room.model.GuestAccess import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomJoinRules import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.session.crypto.GlobalCryptoConfig data class RoomSettingsViewState( val roomId: String, @@ -45,7 +46,10 @@ data class RoomSettingsViewState( val showSaveAction: Boolean = false, val actionPermissions: ActionPermissions = ActionPermissions(), val supportsRestricted: Boolean = false, - val canUpgradeToRestricted: Boolean = false + val canUpgradeToRestricted: Boolean = false, + val encryptToVerifiedDeviceOnly: Async<Boolean> = Uninitialized, + val globalCryptoConfig: Async<GlobalCryptoConfig> = Uninitialized, + val unverifiedDevicesInTheRoom: Async<Boolean> = Uninitialized, ) : MavericksState { constructor(args: RoomProfileArgs) : this(roomId = args.roomId) diff --git a/vector/src/main/res/layout/item_form_switch.xml b/vector/src/main/res/layout/item_form_switch.xml index a637c8f52e..67d286a917 100644 --- a/vector/src/main/res/layout/item_form_switch.xml +++ b/vector/src/main/res/layout/item_form_switch.xml @@ -7,6 +7,8 @@ android:background="?android:colorBackground" android:foreground="?attr/selectableItemBackground" android:minHeight="@dimen/item_form_min_height" + android:paddingBottom="8dp" + android:paddingTop="8dp" tools:viewBindingIgnore="true"> <TextView From 92a72cb1e9842e953575db2d138979f37464e0ed Mon Sep 17 00:00:00 2001 From: Valere <valeref@matrix.org> Date: Wed, 3 Aug 2022 15:05:33 +0200 Subject: [PATCH 116/187] cleaning --- .../org/matrix/android/sdk/api/session/crypto/CryptoService.kt | 1 - .../matrix/android/sdk/internal/crypto/DefaultCryptoService.kt | 3 ++- .../android/sdk/internal/crypto/store/db/RealmCryptoStore.kt | 2 +- .../app/features/roomprofile/settings/RoomSettingsViewState.kt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt index 5e0c087c08..c2151ab6aa 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt @@ -42,7 +42,6 @@ import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.crypto.model.SessionInfo -import org.matrix.android.sdk.api.session.crypto.GlobalCryptoConfig interface CryptoService { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index f37be4ff62..bdbd326fe3 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -40,6 +40,7 @@ import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.listeners.ProgressListener import org.matrix.android.sdk.api.logger.LoggerTag import org.matrix.android.sdk.api.session.crypto.CryptoService +import org.matrix.android.sdk.api.session.crypto.GlobalCryptoConfig import org.matrix.android.sdk.api.session.crypto.MXCryptoError import org.matrix.android.sdk.api.session.crypto.NewSessionListener import org.matrix.android.sdk.api.session.crypto.OutgoingKeyRequest @@ -87,7 +88,6 @@ import org.matrix.android.sdk.internal.crypto.model.MXKey.Companion.KEY_SIGNED_C import org.matrix.android.sdk.internal.crypto.model.SessionInfo import org.matrix.android.sdk.internal.crypto.model.toRest import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository -import org.matrix.android.sdk.api.session.crypto.GlobalCryptoConfig import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.tasks.DeleteDeviceTask import org.matrix.android.sdk.internal.crypto.tasks.GetDeviceInfoTask @@ -1194,6 +1194,7 @@ internal class DefaultCryptoService @Inject constructor( * Add this room to the ones which don't encrypt messages to unverified devices. * * @param roomId the room id + * @param if true will block sending keys to unverified devices */ override fun setRoomBlacklistUnverifiedDevices(roomId: String, enable: Boolean) { cryptoStore.blackListUnverifiedDevicesInRoom(roomId, enable) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index 801d012385..b5b2a2392d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -29,6 +29,7 @@ import io.realm.kotlin.where import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.logger.LoggerTag +import org.matrix.android.sdk.api.session.crypto.GlobalCryptoConfig import org.matrix.android.sdk.api.session.crypto.NewSessionListener import org.matrix.android.sdk.api.session.crypto.OutgoingKeyRequest import org.matrix.android.sdk.api.session.crypto.OutgoingRoomKeyRequestState @@ -53,7 +54,6 @@ import org.matrix.android.sdk.api.util.toOptional import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper import org.matrix.android.sdk.internal.crypto.model.OutboundGroupSessionWrapper -import org.matrix.android.sdk.api.session.crypto.GlobalCryptoConfig import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.store.db.mapper.CrossSigningKeysMapper import org.matrix.android.sdk.internal.crypto.store.db.mapper.MyDeviceLastSeenInfoEntityMapper diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewState.kt index e3c6cd9b03..26f4c6bdad 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewState.kt @@ -23,11 +23,11 @@ import com.airbnb.mvrx.Uninitialized import im.vector.app.R import im.vector.app.core.resources.StringProvider import im.vector.app.features.roomprofile.RoomProfileArgs +import org.matrix.android.sdk.api.session.crypto.GlobalCryptoConfig import org.matrix.android.sdk.api.session.room.model.GuestAccess import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomJoinRules import org.matrix.android.sdk.api.session.room.model.RoomSummary -import org.matrix.android.sdk.api.session.crypto.GlobalCryptoConfig data class RoomSettingsViewState( val roomId: String, From f7d83563278cb627a0ba13a1c46e2b5be2aa0db1 Mon Sep 17 00:00:00 2001 From: Valere <valeref@matrix.org> Date: Fri, 30 Sep 2022 17:37:33 +0200 Subject: [PATCH 117/187] post rebase fix & cleaning --- .../{E2eeTestConfig.kt => E2eeConfigTest.kt} | 28 +++++++++---------- .../sdk/api/session/crypto/CryptoService.kt | 4 +-- .../api/session/crypto/GlobalCryptoConfig.kt | 4 +-- .../internal/crypto/DefaultCryptoService.kt | 10 +++---- .../internal/crypto/store/IMXCryptoStore.kt | 4 +-- .../crypto/store/db/RealmCryptoStore.kt | 8 +++--- .../settings/RoomSettingsAction.kt | 2 +- .../settings/RoomSettingsController.kt | 2 +- .../settings/RoomSettingsViewModel.kt | 4 +-- 9 files changed, 33 insertions(+), 33 deletions(-) rename matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/{E2eeTestConfig.kt => E2eeConfigTest.kt} (86%) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeTestConfig.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeConfigTest.kt similarity index 86% rename from matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeTestConfig.kt rename to matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeConfigTest.kt index 7a03276d34..8b12092b79 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeTestConfig.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeConfigTest.kt @@ -25,13 +25,14 @@ import org.junit.runners.JUnit4 import org.junit.runners.MethodSorters import org.matrix.android.sdk.InstrumentedTest import org.matrix.android.sdk.api.session.crypto.MXCryptoError +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest @RunWith(JUnit4::class) @FixMethodOrder(MethodSorters.JVM) @LargeTest -class E2eeTestConfig : InstrumentedTest { +class E2eeConfigTest : InstrumentedTest { @Test fun testBlacklistUnverifiedDefault() = runCryptoTest(context()) { cryptoTestHelper, _ -> @@ -55,10 +56,8 @@ class E2eeTestConfig : InstrumentedTest { val roomBobPOV = cryptoTestData.secondSession!!.roomService().getRoom(cryptoTestData.roomId)!! // ensure other received - testHelper.waitWithLatch { latch -> - testHelper.retryPeriodicallyWithLatch(latch) { - roomBobPOV.timelineService().getTimelineEvent(sentMessage.eventId) != null - } + testHelper.retryPeriodically { + roomBobPOV.timelineService().getTimelineEvent(sentMessage.eventId) != null } cryptoTestHelper.ensureCannotDecrypt(listOf(sentMessage.eventId), cryptoTestData.secondSession!!, cryptoTestData.roomId) @@ -81,10 +80,8 @@ class E2eeTestConfig : InstrumentedTest { val roomBobPOV = cryptoTestData.secondSession!!.roomService().getRoom(cryptoTestData.roomId)!! // ensure other received - testHelper.waitWithLatch { latch -> - testHelper.retryPeriodicallyWithLatch(latch) { - roomBobPOV.timelineService().getTimelineEvent(sentMessage.eventId) != null - } + testHelper.retryPeriodically { + roomBobPOV.timelineService().getTimelineEvent(sentMessage.eventId) != null } cryptoTestHelper.ensureCanDecrypt( @@ -105,10 +102,8 @@ class E2eeTestConfig : InstrumentedTest { val roomBobPOV = cryptoTestData.secondSession!!.roomService().getRoom(cryptoTestData.roomId)!! // ensure other received - testHelper.waitWithLatch { latch -> - testHelper.retryPeriodicallyWithLatch(latch) { - roomBobPOV.timelineService().getTimelineEvent(beforeMessage.eventId) != null - } + testHelper.retryPeriodically { + roomBobPOV.timelineService().getTimelineEvent(beforeMessage.eventId) != null } cryptoTestHelper.ensureCanDecrypt( @@ -118,10 +113,15 @@ class E2eeTestConfig : InstrumentedTest { listOf(beforeMessage.getLastMessageContent()!!.body) ) - cryptoTestData.firstSession.cryptoService().setRoomBlacklistUnverifiedDevices(cryptoTestData.roomId, true) + cryptoTestData.firstSession.cryptoService().setRoomBlockUnverifiedDevices(cryptoTestData.roomId, true) val afterMessage = testHelper.sendTextMessage(roomAlicePOV, "you are blocked", 1).first() + // ensure received + testHelper.retryPeriodically { + cryptoTestData.secondSession?.getRoom(cryptoTestData.roomId)?.timelineService()?.getTimelineEvent(afterMessage.eventId)?.root != null + } + cryptoTestHelper.ensureCannotDecrypt( listOf(afterMessage.eventId), cryptoTestData.secondSession!!, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt index c2151ab6aa..c383e05707 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt @@ -104,7 +104,7 @@ interface CryptoService { */ fun isShareKeysOnInviteEnabled(): Boolean - fun setRoomUnBlacklistUnverifiedDevices(roomId: String) + fun setRoomUnBlockUnverifiedDevices(roomId: String) fun getDeviceTrackingStatus(userId: String): Int @@ -116,7 +116,7 @@ interface CryptoService { suspend fun exportRoomKeys(password: String): ByteArray - fun setRoomBlacklistUnverifiedDevices(roomId: String, enable: Boolean) + fun setRoomBlockUnverifiedDevices(roomId: String, block: Boolean) fun getCryptoDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo? diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/GlobalCryptoConfig.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/GlobalCryptoConfig.kt index a6afe6087b..6405652a68 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/GlobalCryptoConfig.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/GlobalCryptoConfig.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 New Vector Ltd + * Copyright (c) 2022 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. @@ -17,7 +17,7 @@ package org.matrix.android.sdk.api.session.crypto data class GlobalCryptoConfig( - val globalBlacklistUnverifiedDevices: Boolean, + val globalBlockUnverifiedDevices: Boolean, val globalEnableKeyGossiping: Boolean, val enableKeyForwardingOnInvite: Boolean, ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index bdbd326fe3..4a160f07b2 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -1194,10 +1194,10 @@ internal class DefaultCryptoService @Inject constructor( * Add this room to the ones which don't encrypt messages to unverified devices. * * @param roomId the room id - * @param if true will block sending keys to unverified devices + * @param block if true will block sending keys to unverified devices */ - override fun setRoomBlacklistUnverifiedDevices(roomId: String, enable: Boolean) { - cryptoStore.blackListUnverifiedDevicesInRoom(roomId, enable) + override fun setRoomBlockUnverifiedDevices(roomId: String, block: Boolean) { + cryptoStore.blockUnverifiedDevicesInRoom(roomId, block) } /** @@ -1205,8 +1205,8 @@ internal class DefaultCryptoService @Inject constructor( * * @param roomId the room id */ - override fun setRoomUnBlacklistUnverifiedDevices(roomId: String) { - setRoomBlacklistUnverifiedDevices(roomId, false) + override fun setRoomUnBlockUnverifiedDevices(roomId: String) { + setRoomBlockUnverifiedDevices(roomId, false) } /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt index 193b53ec4e..7c5dd6c585 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt @@ -138,9 +138,9 @@ internal interface IMXCryptoStore { * Define if encryption keys should be sent to unverified devices in this room. * * @param roomId the roomId - * @param blacklist if true will not send keys to unverified devices + * @param block if true will not send keys to unverified devices */ - fun blackListUnverifiedDevicesInRoom(roomId: String, blacklist: Boolean) + fun blockUnverifiedDevicesInRoom(roomId: String, block: Boolean) /** * Get the current keys backup version. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index b5b2a2392d..81a4a9d427 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -451,7 +451,7 @@ internal class RealmCryptoStore @Inject constructor( realm.where<CryptoMetadataEntity>().findFirst() ?.let { GlobalCryptoConfig( - globalBlacklistUnverifiedDevices = it.globalBlacklistUnverifiedDevices, + globalBlockUnverifiedDevices = it.globalBlacklistUnverifiedDevices, globalEnableKeyGossiping = it.globalEnableKeyGossiping, enableKeyForwardingOnInvite = it.enableKeyForwardingOnInvite ) @@ -467,7 +467,7 @@ internal class RealmCryptoStore @Inject constructor( }, { GlobalCryptoConfig( - globalBlacklistUnverifiedDevices = it.globalBlacklistUnverifiedDevices, + globalBlockUnverifiedDevices = it.globalBlacklistUnverifiedDevices, globalEnableKeyGossiping = it.globalEnableKeyGossiping, enableKeyForwardingOnInvite = it.enableKeyForwardingOnInvite ) @@ -1121,10 +1121,10 @@ internal class RealmCryptoStore @Inject constructor( } } - override fun blackListUnverifiedDevicesInRoom(roomId: String, blacklist: Boolean) { + override fun blockUnverifiedDevicesInRoom(roomId: String, block: Boolean) { doRealmTransaction(realmConfiguration) { realm -> CryptoRoomEntity.getById(realm, roomId) - ?.blacklistUnverifiedDevices = blacklist + ?.blacklistUnverifiedDevices = block } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsAction.kt index bb2dc08e76..13662c1f9b 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsAction.kt @@ -28,7 +28,7 @@ sealed class RoomSettingsAction : VectorViewModelAction { data class SetRoomHistoryVisibility(val visibility: RoomHistoryVisibility) : RoomSettingsAction() data class SetRoomJoinRule(val roomJoinRule: RoomJoinRules) : RoomSettingsAction() data class SetRoomGuestAccess(val guestAccess: GuestAccess) : RoomSettingsAction() - data class SetEncryptToVerifiedDeviceOnly(val enable: Boolean) : RoomSettingsAction() + data class SetEncryptToVerifiedDeviceOnly(val enabled: Boolean) : RoomSettingsAction() object Save : RoomSettingsAction() object Cancel : RoomSettingsAction() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt index 936ebbd861..04727eca2c 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt @@ -154,7 +154,7 @@ class RoomSettingsController @Inject constructor( buildProfileSection(stringProvider.getString(R.string.room_profile_section_security)) data.globalCryptoConfig.invoke()?.let { globalConfig -> - if (globalConfig.globalBlacklistUnverifiedDevices) { + if (globalConfig.globalBlockUnverifiedDevices) { genericFooterItem { id("globalConfig") centered(false) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt index cb5809d96a..06a859a384 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt @@ -252,7 +252,7 @@ class RoomSettingsViewModel @AssistedInject constructor( is RoomSettingsAction.SetRoomGuestAccess -> handleSetGuestAccess(action) is RoomSettingsAction.Save -> saveSettings() is RoomSettingsAction.Cancel -> cancel() - is RoomSettingsAction.SetEncryptToVerifiedDeviceOnly -> setEncryptToVerifiedDeviceOnly(action.enable) + is RoomSettingsAction.SetEncryptToVerifiedDeviceOnly -> setEncryptToVerifiedDeviceOnly(action.enabled) } } @@ -276,7 +276,7 @@ class RoomSettingsViewModel @AssistedInject constructor( private fun setEncryptToVerifiedDeviceOnly(enabled: Boolean) { session.coroutineScope.launch { - session.cryptoService().setRoomBlacklistUnverifiedDevices(room.roomId, enabled) + session.cryptoService().setRoomBlockUnverifiedDevices(room.roomId, enabled) } } From 8c7e7a8f3571beb477b60878ee3c166bed0834cc Mon Sep 17 00:00:00 2001 From: Valere <valeref@matrix.org> Date: Mon, 3 Oct 2022 14:22:13 +0200 Subject: [PATCH 118/187] open global settings from room settings --- .../features/roomprofile/settings/RoomSettingsController.kt | 2 ++ .../features/roomprofile/settings/RoomSettingsFragment.kt | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt index 04727eca2c..c54c6d5db8 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt @@ -56,6 +56,7 @@ class RoomSettingsController @Inject constructor( fun onJoinRuleClicked() fun onToggleGuestAccess() fun setEncryptedToVerifiedDevicesOnly(enabled: Boolean) + fun openGlobalBlockSettings() } var callback: Callback? = null @@ -170,6 +171,7 @@ class RoomSettingsController @Inject constructor( }.toEpoxyCharSequence() ) itemClickAction { + host.callback?.openGlobalBlockSettings() } } } else { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt index b7d8f13343..093f146a53 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt @@ -43,6 +43,7 @@ import im.vector.app.core.utils.toast import im.vector.app.databinding.FragmentRoomSettingGenericBinding import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.home.AvatarRenderer +import im.vector.app.features.navigation.SettingsActivityPayload import im.vector.app.features.roomprofile.RoomProfileArgs import im.vector.app.features.roomprofile.RoomProfileSharedActionViewModel import im.vector.app.features.roomprofile.settings.historyvisibility.RoomHistoryVisibilityBottomSheet @@ -203,6 +204,10 @@ class RoomSettingsFragment : viewModel.handle(RoomSettingsAction.SetEncryptToVerifiedDeviceOnly(enabled)) } + override fun openGlobalBlockSettings() { + navigator.openSettings(requireContext(), SettingsActivityPayload.SecurityPrivacy) + } + override fun onImageReady(uri: Uri?) { uri ?: return viewModel.handle( From 8de2fe891740cc1e1da7192c18b5c14aa3af661b Mon Sep 17 00:00:00 2001 From: Valere <valeref@matrix.org> Date: Tue, 4 Oct 2022 15:59:28 +0200 Subject: [PATCH 119/187] Update library/ui-strings/src/main/res/values/strings.xml Co-authored-by: Benoit Marty <benoitm@matrix.org> --- library/ui-strings/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index cc339f91d9..1126d511b2 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -1236,7 +1236,7 @@ <string name="encryption_never_send_to_unverified_devices_summary">Never send encrypted messages to unverified sessions from this session.</string> <string name="encryption_never_send_to_unverified_devices_in_room">Never send encrypted messages to unverified sessions in this room.</string> <string name="some_devices_will_not_be_able_to_decrypt">⚠ There are unverified devices in this room, they won’t be able to decrypt messages you send.</string> - <string name="room_settings_global_blacklist_unverified_info_text">🔒 You have enable encrypt to verified sessions only for all rooms in Security Settings.</string> + <string name="room_settings_global_blacklist_unverified_info_text">🔒 You have enabled encrypt to verified sessions only for all rooms in Security Settings.</string> <plurals name="encryption_import_room_keys_success"> <item quantity="one">%1$d/%2$d key imported with success.</item> <item quantity="other">%1$d/%2$d keys imported with success.</item> From 68d4ac34c75149d9e1a5d8cd28ebae33dba8b74f Mon Sep 17 00:00:00 2001 From: Valere <valeref@matrix.org> Date: Tue, 4 Oct 2022 17:17:01 +0200 Subject: [PATCH 120/187] Move setting to room profile --- .../src/main/res/values/strings.xml | 2 +- .../sdk/api/session/crypto/CryptoService.kt | 2 +- .../internal/crypto/DefaultCryptoService.kt | 6 +- .../algorithms/megolm/MXMegolmEncryption.kt | 2 +- .../internal/crypto/store/IMXCryptoStore.kt | 4 +- .../crypto/store/db/RealmCryptoStore.kt | 4 +- .../features/roomprofile/RoomProfileAction.kt | 1 + .../roomprofile/RoomProfileController.kt | 50 +++++++++ .../roomprofile/RoomProfileFragment.kt | 9 ++ .../roomprofile/RoomProfileViewModel.kt | 51 +++++++++ .../roomprofile/RoomProfileViewState.kt | 4 + .../settings/RoomSettingsAction.kt | 1 - .../settings/RoomSettingsController.kt | 101 +++++++++--------- .../settings/RoomSettingsFragment.kt | 9 -- .../settings/RoomSettingsViewModel.kt | 47 -------- .../settings/RoomSettingsViewState.kt | 4 - 16 files changed, 173 insertions(+), 124 deletions(-) diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 1126d511b2..5ddcbf324f 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -1236,7 +1236,7 @@ <string name="encryption_never_send_to_unverified_devices_summary">Never send encrypted messages to unverified sessions from this session.</string> <string name="encryption_never_send_to_unverified_devices_in_room">Never send encrypted messages to unverified sessions in this room.</string> <string name="some_devices_will_not_be_able_to_decrypt">⚠ There are unverified devices in this room, they won’t be able to decrypt messages you send.</string> - <string name="room_settings_global_blacklist_unverified_info_text">🔒 You have enabled encrypt to verified sessions only for all rooms in Security Settings.</string> + <string name="room_settings_global_block_unverified_info_text">🔒 You have enabled encrypt to verified sessions only for all rooms in Security Settings.</string> <plurals name="encryption_import_room_keys_success"> <item quantity="one">%1$d/%2$d key imported with success.</item> <item quantity="other">%1$d/%2$d keys imported with success.</item> diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt index c383e05707..d2aa8020e8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt @@ -61,7 +61,7 @@ interface CryptoService { fun isRoomBlacklistUnverifiedDevices(roomId: String?): Boolean - fun getLiveBlacklistUnverifiedDevices(roomId: String): LiveData<Boolean> + fun getLiveBlockUnverifiedDevices(roomId: String): LiveData<Boolean> fun setWarnOnUnknownDevices(warn: Boolean) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index 4a160f07b2..9c3e0ba1c5 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -1177,7 +1177,7 @@ internal class DefaultCryptoService @Inject constructor( * @return true if the client should encrypt messages only for the verified devices. */ override fun isRoomBlacklistUnverifiedDevices(roomId: String?): Boolean { - return roomId?.let { cryptoStore.getBlacklistUnverifiedDevices(roomId) } + return roomId?.let { cryptoStore.getBlockUnverifiedDevices(roomId) } ?: false } @@ -1186,8 +1186,8 @@ internal class DefaultCryptoService @Inject constructor( * * @return Live status */ - override fun getLiveBlacklistUnverifiedDevices(roomId: String): LiveData<Boolean> { - return cryptoStore.getLiveBlacklistUnverifiedDevices(roomId) + override fun getLiveBlockUnverifiedDevices(roomId: String): LiveData<Boolean> { + return cryptoStore.getLiveBlockUnverifiedDevices(roomId) } /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt index e19e513b63..7b6051932a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt @@ -424,7 +424,7 @@ internal class MXMegolmEncryption( // an m.new_device. val keys = deviceListManager.downloadKeys(userIds, false) val encryptToVerifiedDevicesOnly = cryptoStore.getGlobalBlacklistUnverifiedDevices() || - cryptoStore.getBlacklistUnverifiedDevices(roomId) + cryptoStore.getBlockUnverifiedDevices(roomId) val devicesInRoom = DeviceInRoomInfo() val unknownDevices = MXUsersDevicesMap<CryptoDeviceInfo>() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt index 7c5dd6c585..21e3342365 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt @@ -125,14 +125,14 @@ internal interface IMXCryptoStore { * * @return Live status */ - fun getLiveBlacklistUnverifiedDevices(roomId: String): LiveData<Boolean> + fun getLiveBlockUnverifiedDevices(roomId: String): LiveData<Boolean> /** * Tell if unverified devices should be blacklisted when sending keys. * * @return true if should not send keys to unverified devices */ - fun getBlacklistUnverifiedDevices(roomId: String): Boolean + fun getBlockUnverifiedDevices(roomId: String): Boolean /** * Define if encryption keys should be sent to unverified devices in this room. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index 81a4a9d427..e97cf437c6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -1097,7 +1097,7 @@ internal class RealmCryptoStore @Inject constructor( } } - override fun getLiveBlacklistUnverifiedDevices(roomId: String): LiveData<Boolean> { + override fun getLiveBlockUnverifiedDevices(roomId: String): LiveData<Boolean> { val liveData = monarchy.findAllMappedWithChanges( { realm: Realm -> realm.where<CryptoRoomEntity>() @@ -1112,7 +1112,7 @@ internal class RealmCryptoStore @Inject constructor( } } - override fun getBlacklistUnverifiedDevices(roomId: String): Boolean { + override fun getBlockUnverifiedDevices(roomId: String): Boolean { return doWithRealm(realmConfiguration) { realm -> realm.where<CryptoRoomEntity>() .equalTo(CryptoRoomEntityFields.ROOM_ID, roomId) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileAction.kt index 22b040b4c0..44bac1c8a0 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileAction.kt @@ -27,4 +27,5 @@ sealed class RoomProfileAction : VectorViewModelAction { object ShareRoomProfile : RoomProfileAction() object CreateShortcut : RoomProfileAction() object RestoreEncryptionState : RoomProfileAction() + data class SetEncryptToVerifiedDeviceOnly(val enabled: Boolean) : RoomProfileAction() } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt index 06f56bff89..eb43a345f2 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt @@ -27,6 +27,7 @@ import im.vector.app.core.resources.DrawableProvider import im.vector.app.core.resources.StringProvider import im.vector.app.core.ui.list.genericFooterItem import im.vector.app.core.ui.list.genericPositiveButtonItem +import im.vector.app.features.form.formSwitchItem import im.vector.app.features.home.ShortcutCreator import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.home.room.detail.timeline.tools.createLinkMovementMethod @@ -66,6 +67,8 @@ class RoomProfileController @Inject constructor( fun onUrlInTopicLongClicked(url: String) fun doMigrateToVersion(newVersion: String) fun restoreEncryptionState() + fun setEncryptedToVerifiedDevicesOnly(enabled: Boolean) + fun openGlobalBlockSettings() } override fun buildModels(data: RoomProfileViewState?) { @@ -175,6 +178,53 @@ class RoomProfileController @Inject constructor( } buildEncryptionAction(data.actionPermissions, roomSummary) + if (roomSummary.isEncrypted && !encryptionMisconfigured) { + data.globalCryptoConfig.invoke()?.let { globalConfig -> + if (globalConfig.globalBlockUnverifiedDevices) { + genericFooterItem { + id("globalConfig") + centered(false) + text( + span { + +host.stringProvider.getString(R.string.room_settings_global_block_unverified_info_text) + apply { + if (data.unverifiedDevicesInTheRoom.invoke() == true) { + +"\n" + +host.stringProvider.getString(R.string.some_devices_will_not_be_able_to_decrypt) + } + } + }.toEpoxyCharSequence() + ) + itemClickAction { + host.callback?.openGlobalBlockSettings() + } + } + } else { + // per room setting is available + val shouldBlockUnverified = data.encryptToVerifiedDeviceOnly.invoke() + formSwitchItem { + id("send_to_unverified") + enabled(shouldBlockUnverified != null) + title(host.stringProvider.getString(R.string.encryption_never_send_to_unverified_devices_in_room)) + + switchChecked(shouldBlockUnverified ?: false) + + apply { + if (shouldBlockUnverified == true && data.unverifiedDevicesInTheRoom.invoke() == true) { + summary( + host.stringProvider.getString(R.string.some_devices_will_not_be_able_to_decrypt) + ) + } else { + summary(null) + } + } + listener { value -> + host.callback?.setEncryptedToVerifiedDevicesOnly(value) + } + } + } + } + } // More buildProfileSection(stringProvider.getString(R.string.room_profile_section_more)) buildProfileAction( 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 4135ab3d1c..f4394111ab 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 @@ -53,6 +53,7 @@ import im.vector.app.features.home.room.detail.RoomDetailPendingActionStore import im.vector.app.features.home.room.detail.upgrade.MigrateRoomBottomSheet import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedAction import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel +import im.vector.app.features.navigation.SettingsActivityPayload import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.parcelize.Parcelize @@ -346,6 +347,14 @@ class RoomProfileFragment : ) } + override fun setEncryptedToVerifiedDevicesOnly(enabled: Boolean) { + roomProfileViewModel.handle(RoomProfileAction.SetEncryptToVerifiedDeviceOnly(enabled)) + } + + override fun openGlobalBlockSettings() { + navigator.openSettings(requireContext(), SettingsActivityPayload.SecurityPrivacy) + } + private fun onAvatarClicked(view: View) = withState(roomProfileViewModel) { state -> state.roomSummary()?.toMatrixItem()?.let { matrixItem -> navigator.openBigImageViewer(requireActivity(), view, matrixItem) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt index 30664c5618..215a1e1e9c 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt @@ -17,6 +17,7 @@ package im.vector.app.features.roomprofile +import androidx.lifecycle.asFlow import com.airbnb.mvrx.MavericksViewModelFactory import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -32,7 +33,11 @@ import im.vector.app.features.home.ShortcutCreator import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import im.vector.app.features.session.coroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import org.matrix.android.sdk.api.query.QueryStringValue @@ -76,6 +81,45 @@ class RoomProfileViewModel @AssistedInject constructor( observeBannedRoomMembers(flowRoom) observePermissions() observePowerLevels() + observeCryptoSettings(flowRoom) + } + + private fun observeCryptoSettings(flowRoom: FlowRoom) { + val perRoomBlockStatus = session.cryptoService().getLiveBlockUnverifiedDevices(initialState.roomId) + .asFlow() + + perRoomBlockStatus + .execute { + copy(encryptToVerifiedDeviceOnly = it) + } + + val globalBlockStatus = session.cryptoService().getLiveGlobalCryptoConfig() + .asFlow() + + globalBlockStatus + .execute { + copy(globalCryptoConfig = it) + } + + perRoomBlockStatus.combine(globalBlockStatus) { perRoom, global -> + perRoom || global.globalBlockUnverifiedDevices + }.flatMapLatest { + if (it) { + flowRoom.liveRoomMembers(roomMemberQueryParams { memberships = Membership.activeMemberships() }) + .map { it.map { it.userId } } + .flatMapLatest { + session.cryptoService().getLiveCryptoDeviceInfo(it).asFlow() + } + } else { + flowOf(emptyList()) + } + }.map { + it.isNotEmpty() + }.execute { + copy( + unverifiedDevicesInTheRoom = it + ) + } } private fun observePowerLevels() { @@ -141,6 +185,7 @@ class RoomProfileViewModel @AssistedInject constructor( is RoomProfileAction.ShareRoomProfile -> handleShareRoomProfile() RoomProfileAction.CreateShortcut -> handleCreateShortcut() RoomProfileAction.RestoreEncryptionState -> restoreEncryptionState() + is RoomProfileAction.SetEncryptToVerifiedDeviceOnly -> setEncryptToVerifiedDeviceOnly(action.enabled) } } @@ -212,6 +257,12 @@ class RoomProfileViewModel @AssistedInject constructor( } } + private fun setEncryptToVerifiedDeviceOnly(enabled: Boolean) { + session.coroutineScope.launch { + session.cryptoService().setRoomBlockUnverifiedDevices(room.roomId, enabled) + } + } + private fun restoreEncryptionState() { _viewEvents.post(RoomProfileViewEvents.Loading()) session.coroutineScope.launch { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewState.kt index c457a01750..5393ceb152 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewState.kt @@ -20,6 +20,7 @@ package im.vector.app.features.roomprofile import com.airbnb.mvrx.Async import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized +import org.matrix.android.sdk.api.session.crypto.GlobalCryptoConfig import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent @@ -36,6 +37,9 @@ data class RoomProfileViewState( val canUpgradeRoom: Boolean = false, val isTombstoned: Boolean = false, val canUpdateRoomState: Boolean = false, + val encryptToVerifiedDeviceOnly: Async<Boolean> = Uninitialized, + val globalCryptoConfig: Async<GlobalCryptoConfig> = Uninitialized, + val unverifiedDevicesInTheRoom: Async<Boolean> = Uninitialized, ) : MavericksState { constructor(args: RoomProfileArgs) : this(roomId = args.roomId) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsAction.kt index 13662c1f9b..eb601605e0 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsAction.kt @@ -28,7 +28,6 @@ sealed class RoomSettingsAction : VectorViewModelAction { data class SetRoomHistoryVisibility(val visibility: RoomHistoryVisibility) : RoomSettingsAction() data class SetRoomJoinRule(val roomJoinRule: RoomJoinRules) : RoomSettingsAction() data class SetRoomGuestAccess(val guestAccess: GuestAccess) : RoomSettingsAction() - data class SetEncryptToVerifiedDeviceOnly(val enabled: Boolean) : RoomSettingsAction() object Save : RoomSettingsAction() object Cancel : RoomSettingsAction() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt index c54c6d5db8..91d6ac76ed 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt @@ -22,7 +22,6 @@ import im.vector.app.core.epoxy.dividerItem import im.vector.app.core.epoxy.profiles.buildProfileAction import im.vector.app.core.epoxy.profiles.buildProfileSection import im.vector.app.core.resources.StringProvider -import im.vector.app.core.ui.list.genericFooterItem import im.vector.app.core.ui.list.verticalMarginItem import im.vector.app.core.utils.DimensionConverter import im.vector.app.features.form.formEditTextItem @@ -31,8 +30,6 @@ import im.vector.app.features.form.formSwitchItem import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.timeline.format.RoomHistoryVisibilityFormatter import im.vector.app.features.settings.VectorPreferences -import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence -import me.gujun.android.span.span import org.matrix.android.sdk.api.session.room.model.GuestAccess import org.matrix.android.sdk.api.session.room.model.RoomJoinRules import org.matrix.android.sdk.api.util.toMatrixItem @@ -55,8 +52,6 @@ class RoomSettingsController @Inject constructor( fun onHistoryVisibilityClicked() fun onJoinRuleClicked() fun onToggleGuestAccess() - fun setEncryptedToVerifiedDevicesOnly(enabled: Boolean) - fun openGlobalBlockSettings() } var callback: Callback? = null @@ -150,54 +145,54 @@ class RoomSettingsController @Inject constructor( id("guestAccessDivider") } } +// +// // Security +// buildProfileSection(stringProvider.getString(R.string.room_profile_section_security)) - // Security - buildProfileSection(stringProvider.getString(R.string.room_profile_section_security)) - - data.globalCryptoConfig.invoke()?.let { globalConfig -> - if (globalConfig.globalBlockUnverifiedDevices) { - genericFooterItem { - id("globalConfig") - centered(false) - text( - span { - +host.stringProvider.getString(R.string.room_settings_global_blacklist_unverified_info_text) - apply { - if (data.unverifiedDevicesInTheRoom.invoke() == true) { - +"\n" - +host.stringProvider.getString(R.string.some_devices_will_not_be_able_to_decrypt) - } - } - }.toEpoxyCharSequence() - ) - itemClickAction { - host.callback?.openGlobalBlockSettings() - } - } - } else { - // per room setting is available - val shouldBlockUnverified = data.encryptToVerifiedDeviceOnly.invoke() - formSwitchItem { - id("send_to_unverified") - enabled(shouldBlockUnverified != null) - title(host.stringProvider.getString(R.string.encryption_never_send_to_unverified_devices_in_room)) - - switchChecked(shouldBlockUnverified ?: false) - - apply { - if (shouldBlockUnverified == true && data.unverifiedDevicesInTheRoom.invoke() == true) { - summary( - host.stringProvider.getString(R.string.some_devices_will_not_be_able_to_decrypt) - ) - } else { - summary(null) - } - } - listener { value -> - host.callback?.setEncryptedToVerifiedDevicesOnly(value) - } - } - } - } +// data.globalCryptoConfig.invoke()?.let { globalConfig -> +// if (globalConfig.globalBlockUnverifiedDevices) { +// genericFooterItem { +// id("globalConfig") +// centered(false) +// text( +// span { +// +host.stringProvider.getString(R.string.room_settings_global_block_unverified_info_text) +// apply { +// if (data.unverifiedDevicesInTheRoom.invoke() == true) { +// +"\n" +// +host.stringProvider.getString(R.string.some_devices_will_not_be_able_to_decrypt) +// } +// } +// }.toEpoxyCharSequence() +// ) +// itemClickAction { +// host.callback?.openGlobalBlockSettings() +// } +// } +// } else { +// // per room setting is available +// val shouldBlockUnverified = data.encryptToVerifiedDeviceOnly.invoke() +// formSwitchItem { +// id("send_to_unverified") +// enabled(shouldBlockUnverified != null) +// title(host.stringProvider.getString(R.string.encryption_never_send_to_unverified_devices_in_room)) +// +// switchChecked(shouldBlockUnverified ?: false) +// +// apply { +// if (shouldBlockUnverified == true && data.unverifiedDevicesInTheRoom.invoke() == true) { +// summary( +// host.stringProvider.getString(R.string.some_devices_will_not_be_able_to_decrypt) +// ) +// } else { +// summary(null) +// } +// } +// listener { value -> +// host.callback?.setEncryptedToVerifiedDevicesOnly(value) +// } +// } +// } +// } } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt index 093f146a53..ba50890db3 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt @@ -43,7 +43,6 @@ import im.vector.app.core.utils.toast import im.vector.app.databinding.FragmentRoomSettingGenericBinding import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.home.AvatarRenderer -import im.vector.app.features.navigation.SettingsActivityPayload import im.vector.app.features.roomprofile.RoomProfileArgs import im.vector.app.features.roomprofile.RoomProfileSharedActionViewModel import im.vector.app.features.roomprofile.settings.historyvisibility.RoomHistoryVisibilityBottomSheet @@ -200,14 +199,6 @@ class RoomSettingsFragment : viewModel.handle(RoomSettingsAction.SetRoomGuestAccess(toggled)) } - override fun setEncryptedToVerifiedDevicesOnly(enabled: Boolean) { - viewModel.handle(RoomSettingsAction.SetEncryptToVerifiedDeviceOnly(enabled)) - } - - override fun openGlobalBlockSettings() { - navigator.openSettings(requireContext(), SettingsActivityPayload.SecurityPrivacy) - } - override fun onImageReady(uri: Uri?) { uri ?: return viewModel.handle( diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt index 06a859a384..501ff7553a 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewModel.kt @@ -17,7 +17,6 @@ package im.vector.app.features.roomprofile.settings import androidx.core.net.toFile -import androidx.lifecycle.asFlow import com.airbnb.mvrx.MavericksViewModelFactory import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -26,12 +25,8 @@ 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.powerlevel.PowerLevelsFlowFactory -import im.vector.app.features.session.coroutineScope import im.vector.app.features.settings.VectorPreferences -import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch @@ -42,8 +37,6 @@ import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities -import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams -import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomAvatarContent import org.matrix.android.sdk.api.session.room.model.RoomGuestAccessContent import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent @@ -90,39 +83,6 @@ class RoomSettingsViewModel @AssistedInject constructor( canUpgradeToRestricted = couldUpgradeToRestricted ) } - - session.cryptoService().getLiveBlacklistUnverifiedDevices(initialState.roomId) - .asFlow() - .execute { - copy(encryptToVerifiedDeviceOnly = it) - } - - session.cryptoService().getLiveGlobalCryptoConfig() - .asFlow() - .execute { - copy(globalCryptoConfig = it) - } - - val flowRoom = room.flow() - session.cryptoService().getLiveBlacklistUnverifiedDevices(initialState.roomId) - .asFlow() - .flatMapLatest { - if (it) { - flowRoom.liveRoomMembers(roomMemberQueryParams { memberships = Membership.activeMemberships() }) - .map { it.map { it.userId } } - .flatMapLatest { - session.cryptoService().getLiveCryptoDeviceInfo(it).asFlow() - } - } else { - flowOf(emptyList()) - } - }.map { - it.isNotEmpty() - }.execute { - copy( - unverifiedDevicesInTheRoom = it - ) - } } private fun observeState() { @@ -252,7 +212,6 @@ class RoomSettingsViewModel @AssistedInject constructor( is RoomSettingsAction.SetRoomGuestAccess -> handleSetGuestAccess(action) is RoomSettingsAction.Save -> saveSettings() is RoomSettingsAction.Cancel -> cancel() - is RoomSettingsAction.SetEncryptToVerifiedDeviceOnly -> setEncryptToVerifiedDeviceOnly(action.enabled) } } @@ -274,12 +233,6 @@ class RoomSettingsViewModel @AssistedInject constructor( } } - private fun setEncryptToVerifiedDeviceOnly(enabled: Boolean) { - session.coroutineScope.launch { - session.cryptoService().setRoomBlockUnverifiedDevices(room.roomId, enabled) - } - } - private fun handleSetAvatarAction(action: RoomSettingsAction.SetAvatarAction) { setState { deletePendingAvatar(this) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewState.kt index 26f4c6bdad..10465b03ea 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsViewState.kt @@ -23,7 +23,6 @@ import com.airbnb.mvrx.Uninitialized import im.vector.app.R import im.vector.app.core.resources.StringProvider import im.vector.app.features.roomprofile.RoomProfileArgs -import org.matrix.android.sdk.api.session.crypto.GlobalCryptoConfig import org.matrix.android.sdk.api.session.room.model.GuestAccess import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomJoinRules @@ -47,9 +46,6 @@ data class RoomSettingsViewState( val actionPermissions: ActionPermissions = ActionPermissions(), val supportsRestricted: Boolean = false, val canUpgradeToRestricted: Boolean = false, - val encryptToVerifiedDeviceOnly: Async<Boolean> = Uninitialized, - val globalCryptoConfig: Async<GlobalCryptoConfig> = Uninitialized, - val unverifiedDevicesInTheRoom: Async<Boolean> = Uninitialized, ) : MavericksState { constructor(args: RoomProfileArgs) : this(roomId = args.roomId) From b567fc5be6e78b213d8abf03da576c3acb522daa Mon Sep 17 00:00:00 2001 From: Valere <valeref@matrix.org> Date: Tue, 4 Oct 2022 19:03:16 +0200 Subject: [PATCH 121/187] remove commented code --- .../settings/RoomSettingsController.kt | 49 ------------------- 1 file changed, 49 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt index 91d6ac76ed..f8efe73ebf 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsController.kt @@ -145,54 +145,5 @@ class RoomSettingsController @Inject constructor( id("guestAccessDivider") } } -// -// // Security -// buildProfileSection(stringProvider.getString(R.string.room_profile_section_security)) - -// data.globalCryptoConfig.invoke()?.let { globalConfig -> -// if (globalConfig.globalBlockUnverifiedDevices) { -// genericFooterItem { -// id("globalConfig") -// centered(false) -// text( -// span { -// +host.stringProvider.getString(R.string.room_settings_global_block_unverified_info_text) -// apply { -// if (data.unverifiedDevicesInTheRoom.invoke() == true) { -// +"\n" -// +host.stringProvider.getString(R.string.some_devices_will_not_be_able_to_decrypt) -// } -// } -// }.toEpoxyCharSequence() -// ) -// itemClickAction { -// host.callback?.openGlobalBlockSettings() -// } -// } -// } else { -// // per room setting is available -// val shouldBlockUnverified = data.encryptToVerifiedDeviceOnly.invoke() -// formSwitchItem { -// id("send_to_unverified") -// enabled(shouldBlockUnverified != null) -// title(host.stringProvider.getString(R.string.encryption_never_send_to_unverified_devices_in_room)) -// -// switchChecked(shouldBlockUnverified ?: false) -// -// apply { -// if (shouldBlockUnverified == true && data.unverifiedDevicesInTheRoom.invoke() == true) { -// summary( -// host.stringProvider.getString(R.string.some_devices_will_not_be_able_to_decrypt) -// ) -// } else { -// summary(null) -// } -// } -// listener { value -> -// host.callback?.setEncryptedToVerifiedDevicesOnly(value) -// } -// } -// } -// } } } From 37458d41f227880af5ec5f872503b267dfa457d7 Mon Sep 17 00:00:00 2001 From: Valere <valeref@matrix.org> Date: Tue, 4 Oct 2022 23:54:27 +0200 Subject: [PATCH 122/187] E2ee dos not hinder verification --- changelog.d/6723.bugfix | 1 + .../android/sdk/common/CryptoTestHelper.kt | 2 +- .../sdk/internal/crypto/E2eeSanityTests.kt | 86 +++++++++++++++++++ .../sdk/api/session/events/model/EventType.kt | 13 +++ .../algorithms/megolm/MXMegolmEncryption.kt | 24 +++++- 5 files changed, 122 insertions(+), 4 deletions(-) create mode 100644 changelog.d/6723.bugfix diff --git a/changelog.d/6723.bugfix b/changelog.d/6723.bugfix new file mode 100644 index 0000000000..08b1d1fe2e --- /dev/null +++ b/changelog.d/6723.bugfix @@ -0,0 +1 @@ +Can't verify user when option to send keys to verified devices only is selected diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt index cbaa3153df..74292daf15 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt @@ -313,7 +313,7 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) { val incomingRequest = bobVerificationService.getExistingVerificationRequests(alice.myUserId).first { it.requestInfo?.fromDevice == alice.sessionParams.deviceId } - bobVerificationService.readyPendingVerification(listOf(VerificationMethod.SAS), alice.myUserId, incomingRequest.transactionId!!) + bobVerificationService.readyPendingVerificationInDMs(listOf(VerificationMethod.SAS), alice.myUserId, roomId, incomingRequest.transactionId!!) var requestID: String? = null // wait for it to be readied diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt index 544fe90a73..a36ba8ac02 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt @@ -33,6 +33,10 @@ import org.junit.runner.RunWith import org.junit.runners.JUnit4 import org.junit.runners.MethodSorters import org.matrix.android.sdk.InstrumentedTest +import org.matrix.android.sdk.api.auth.UIABaseAuth +import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor +import org.matrix.android.sdk.api.auth.UserPasswordAuth +import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse import org.matrix.android.sdk.api.crypto.MXCryptoConfig import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.crypto.MXCryptoError @@ -61,7 +65,10 @@ import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest import org.matrix.android.sdk.common.CommonTestHelper.Companion.runSessionTest import org.matrix.android.sdk.common.SessionTestParams +import org.matrix.android.sdk.common.TestConstants import org.matrix.android.sdk.mustFail +import timber.log.Timber +import kotlin.coroutines.Continuation import kotlin.coroutines.resume // @Ignore("This test fails with an unhandled exception thrown from a coroutine which terminates the entire test run.") @@ -607,6 +614,85 @@ class E2eeSanityTests : InstrumentedTest { ) } + @Test + fun test_EncryptionDoesNotHinderVerification() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() + + val aliceSession = cryptoTestData.firstSession + val bobSession = cryptoTestData.secondSession + + val aliceAuthParams = UserPasswordAuth( + user = aliceSession.myUserId, + password = TestConstants.PASSWORD + ) + val bobAuthParams = UserPasswordAuth( + user = bobSession!!.myUserId, + password = TestConstants.PASSWORD + ) + + testHelper.waitForCallback { + aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor { + override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) { + promise.resume(aliceAuthParams) + } + }, it) + } + + testHelper.waitForCallback { + bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor { + override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) { + promise.resume(bobAuthParams) + } + }, it) + } + + // add a second session for bob but not cross signed + + val secondBobSession = testHelper.logIntoAccount(bobSession.myUserId, SessionTestParams(true)) + + aliceSession.cryptoService().setGlobalBlacklistUnverifiedDevices(true) + + // The two bob session should not be able to decrypt any message + + val roomFromAlicePOV = aliceSession.getRoom(cryptoTestData.roomId)!! + Timber.v("#TEST: Send a first message that should be withheld") + val sentEvent = sendMessageInRoom(testHelper, roomFromAlicePOV, "Hello")!! + + // wait for it to be synced back the other side + Timber.v("#TEST: Wait for message to be synced back") + testHelper.retryPeriodically { + bobSession.roomService().getRoom(cryptoTestData.roomId)?.timelineService()?.getTimelineEvent(sentEvent) != null + } + + testHelper.retryPeriodically { + secondBobSession.roomService().getRoom(cryptoTestData.roomId)?.timelineService()?.getTimelineEvent(sentEvent) != null + } + + // bob should not be able to decrypt + Timber.v("#TEST: Ensure cannot be decrytped") + cryptoTestHelper.ensureCannotDecrypt(listOf(sentEvent), bobSession, cryptoTestData.roomId) + cryptoTestHelper.ensureCannotDecrypt(listOf(sentEvent), secondBobSession, cryptoTestData.roomId) + + // let's try to verify, it should work even if bob devices are untrusted + Timber.v("#TEST: Do the verification") + cryptoTestHelper.verifySASCrossSign(aliceSession, bobSession, cryptoTestData.roomId) + + Timber.v("#TEST: Send a second message, outbound session should have rotated and only bob 1rst session should decrypt") + + val secondEvent = sendMessageInRoom(testHelper, roomFromAlicePOV, "World")!! + Timber.v("#TEST: Wait for message to be synced back") + testHelper.retryPeriodically { + bobSession.roomService().getRoom(cryptoTestData.roomId)?.timelineService()?.getTimelineEvent(secondEvent) != null + } + + testHelper.retryPeriodically { + secondBobSession.roomService().getRoom(cryptoTestData.roomId)?.timelineService()?.getTimelineEvent(secondEvent) != null + } + + cryptoTestHelper.ensureCanDecrypt(listOf(secondEvent), bobSession, cryptoTestData.roomId, listOf("World")) + cryptoTestHelper.ensureCannotDecrypt(listOf(secondEvent), secondBobSession, cryptoTestData.roomId) + } + private suspend fun VerificationService.readOldVerificationCodeAsync(scope: CoroutineScope, userId: String): Deferred<String> { return scope.async { suspendCancellableCoroutine { continuation -> diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt index 84c25776e7..3ad4f3a87f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt @@ -128,4 +128,17 @@ object EventType { type == CALL_REJECT || type == CALL_REPLACES } + + fun isVerificationEvent(type: String): Boolean { + return when (type) { + KEY_VERIFICATION_START, + KEY_VERIFICATION_ACCEPT, + KEY_VERIFICATION_KEY, + KEY_VERIFICATION_MAC, + KEY_VERIFICATION_CANCEL, + KEY_VERIFICATION_DONE, + KEY_VERIFICATION_READY -> true + else -> false + } + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt index fca6fab66c..2cb90eee4a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt @@ -31,6 +31,8 @@ import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode +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.internal.crypto.DeviceListManager import org.matrix.android.sdk.internal.crypto.InboundGroupSessionHolder import org.matrix.android.sdk.internal.crypto.MXOlmDevice @@ -92,7 +94,18 @@ internal class MXMegolmEncryption( ): Content { val ts = clock.epochMillis() Timber.tag(loggerTag.value).v("encryptEventContent : getDevicesInRoom") - val devices = getDevicesInRoom(userIds) + + /** + * When using in-room messages and the room has encryption enabled, + * clients should ensure that encryption does not hinder the verification. + * For example, if the verification messages are encrypted, clients must ensure that all the recipient’s + * unverified devices receive the keys necessary to decrypt the messages, + * even if they would normally not be given the keys to decrypt messages in the room. + */ + val shouldSendToUnverified = isVerificationEvent(eventType, eventContent) + + val devices = getDevicesInRoom(userIds, forceDistributeToUnverified = shouldSendToUnverified) + Timber.tag(loggerTag.value).d("encrypt event in room=$roomId - devices count in room ${devices.allowedDevices.toDebugCount()}") Timber.tag(loggerTag.value).v("encryptEventContent ${clock.epochMillis() - ts}: getDevicesInRoom ${devices.allowedDevices.toDebugString()}") val outboundSession = ensureOutboundSession(devices.allowedDevices) @@ -107,6 +120,11 @@ internal class MXMegolmEncryption( } } + private fun isVerificationEvent(eventType: String, eventContent: Content) = + EventType.isVerificationEvent(eventType) || + (eventType == EventType.MESSAGE && + eventContent.get(MessageContent.MSG_TYPE_JSON_KEY) == MessageType.MSGTYPE_VERIFICATION_REQUEST) + private fun notifyWithheldForSession(devices: MXUsersDevicesMap<WithHeldCode>, outboundSession: MXOutboundSessionInfo) { // offload to computation thread cryptoCoroutineScope.launch(coroutineDispatchers.computation) { @@ -417,7 +435,7 @@ internal class MXMegolmEncryption( * * @param userIds the user ids whose devices must be checked. */ - private suspend fun getDevicesInRoom(userIds: List<String>): DeviceInRoomInfo { + private suspend fun getDevicesInRoom(userIds: List<String>, forceDistributeToUnverified: Boolean = false): DeviceInRoomInfo { // We are happy to use a cached version here: we assume that if we already // have a list of the user's devices, then we already share an e2e room // with them, which means that they will have announced any new devices via @@ -444,7 +462,7 @@ internal class MXMegolmEncryption( continue } - if (!deviceInfo.isVerified && encryptToVerifiedDevicesOnly) { + if (!deviceInfo.isVerified && encryptToVerifiedDevicesOnly && !forceDistributeToUnverified) { devicesInRoom.withHeldDevices.setObject(userId, deviceId, WithHeldCode.UNVERIFIED) continue } From fddeddacc7e6684cef6df0a80b713b364eb0ce91 Mon Sep 17 00:00:00 2001 From: Valere <valeref@matrix.org> Date: Wed, 5 Oct 2022 09:11:10 +0200 Subject: [PATCH 123/187] fix outdated doc --- .../sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt index 2cb90eee4a..a90eedeff9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt @@ -434,6 +434,8 @@ internal class MXMegolmEncryption( * This method must be called in getDecryptingThreadHandler() thread. * * @param userIds the user ids whose devices must be checked. + * @param forceDistributeToUnverified If true the unverified devices will be included in valid recipients even if + * such devices are blocked in crypto settings */ private suspend fun getDevicesInRoom(userIds: List<String>, forceDistributeToUnverified: Boolean = false): DeviceInRoomInfo { // We are happy to use a cached version here: we assume that if we already From 2cd78282d94262559f624ef096b3206b72d47589 Mon Sep 17 00:00:00 2001 From: Benoit Marty <benoitm@matrix.org> Date: Wed, 5 Oct 2022 11:02:33 +0200 Subject: [PATCH 124/187] Fix typo Co-authored-by: Andrew Morgan <1342360+anoadragon453@users.noreply.github.com> --- library/ui-strings/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 7bc514c73c..60bb08a314 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -1094,7 +1094,7 @@ <string name="settings_unignore_user">Show all messages from %s?</string> <string name="settings_emails_and_phone_numbers_title">Emails and phone numbers</string> - <string name="settings_emails_and_phone_numbers_summary">Manage emails addresses and phone numbers linked to your Matrix account</string> + <string name="settings_emails_and_phone_numbers_summary">Manage email addresses and phone numbers linked to your Matrix account</string> <string name="settings_select_country">Choose a country</string> From baf527ec9dec5dbe559d13bdb8399ff3c5546c62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= <jorgem@element.io> Date: Thu, 29 Sep 2022 15:26:03 +0200 Subject: [PATCH 125/187] Extract MessageComposerFragment and VoiceRecorderFragment from TimelineFragment --- .../home/room/detail/TimelineFragment.kt | 793 +---------------- .../detail/composer/MessageComposerAction.kt | 2 +- .../composer/MessageComposerFragment.kt | 818 ++++++++++++++++++ .../detail/composer/MessageComposerView.kt | 8 - .../composer/voice/VoiceRecorderFragment.kt | 196 +++++ .../src/main/res/layout/fragment_composer.xml | 13 + .../src/main/res/layout/fragment_timeline.xml | 35 +- .../res/layout/fragment_voice_recorder.xml | 9 + 8 files changed, 1092 insertions(+), 782 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceRecorderFragment.kt create mode 100644 vector/src/main/res/layout/fragment_composer.xml create mode 100644 vector/src/main/res/layout/fragment_voice_recorder.xml 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 8a259b0eea..ae52d36c7e 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 @@ -19,32 +19,23 @@ package im.vector.app.features.home.room.detail import android.annotation.SuppressLint import android.app.Activity import android.content.Intent -import android.content.res.Configuration import android.net.Uri import android.os.Build import android.os.Bundle -import android.text.Spannable -import android.text.format.DateUtils import android.text.method.LinkMovementMethod import android.view.HapticFeedbackConstants -import android.view.KeyEvent import android.view.LayoutInflater import android.view.Menu import android.view.MenuItem import android.view.View import android.view.ViewGroup -import android.view.inputmethod.EditorInfo import android.widget.FrameLayout import android.widget.ImageView import android.widget.TextView -import android.widget.Toast -import androidx.annotation.DrawableRes -import androidx.annotation.StringRes import androidx.appcompat.view.menu.MenuBuilder import androidx.core.content.ContextCompat import androidx.core.graphics.drawable.DrawableCompat import androidx.core.net.toUri -import androidx.core.text.buildSpannedString import androidx.core.text.toSpannable import androidx.core.util.Pair import androidx.core.view.ViewCompat @@ -52,7 +43,6 @@ import androidx.core.view.forEach import androidx.core.view.isInvisible import androidx.core.view.isVisible import androidx.fragment.app.setFragmentResultListener -import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager @@ -63,11 +53,10 @@ import com.airbnb.epoxy.OnModelBuildFinishedListener import com.airbnb.epoxy.addGlidePreloader import com.airbnb.epoxy.glidePreloader import com.airbnb.mvrx.Fail +import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.args -import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder -import com.vanniktech.emoji.EmojiPopup import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.animations.play @@ -75,26 +64,23 @@ import im.vector.app.core.dialogs.ConfirmationDialogBuilder import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper import im.vector.app.core.dialogs.GalleryOrCameraDialogHelperFactory import im.vector.app.core.epoxy.LayoutManagerStateRestorer -import im.vector.app.core.error.fatalError import im.vector.app.core.extensions.cleanup +import im.vector.app.core.extensions.commitTransaction import im.vector.app.core.extensions.containsRtLOverride import im.vector.app.core.extensions.ensureEndsLeftToRight import im.vector.app.core.extensions.filterDirectionOverrides import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.extensions.setTextOrHide -import im.vector.app.core.extensions.showKeyboard +import im.vector.app.core.extensions.toMvRxBundle import im.vector.app.core.extensions.trackItemsVisibilityChange import im.vector.app.core.glide.GlideApp import im.vector.app.core.glide.GlideRequests -import im.vector.app.core.hardware.vibrate import im.vector.app.core.intent.getFilenameFromUri import im.vector.app.core.intent.getMimeTypeFromUri import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorMenuProvider -import im.vector.app.core.platform.lifecycleAwareLazy import im.vector.app.core.platform.showOptimizedSnackbar -import im.vector.app.core.resources.BuildMeta import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.UserPreferencesProvider import im.vector.app.core.time.Clock @@ -106,7 +92,6 @@ import im.vector.app.core.ui.views.NotificationAreaView import im.vector.app.core.utils.Debouncer import im.vector.app.core.utils.DimensionConverter import im.vector.app.core.utils.KeyboardStateUtils -import im.vector.app.core.utils.PERMISSIONS_FOR_VOICE_MESSAGE import im.vector.app.core.utils.PERMISSIONS_FOR_WRITING_FILES import im.vector.app.core.utils.checkPermissions import im.vector.app.core.utils.colorizeMatchingText @@ -116,7 +101,6 @@ import im.vector.app.core.utils.createUIHandler import im.vector.app.core.utils.isAnimationEnabled import im.vector.app.core.utils.isValidUrl import im.vector.app.core.utils.onPermissionDeniedDialog -import im.vector.app.core.utils.onPermissionDeniedSnackbar import im.vector.app.core.utils.openLocation import im.vector.app.core.utils.openUrlInExternalBrowser import im.vector.app.core.utils.registerForPermissionsResult @@ -132,13 +116,7 @@ import im.vector.app.features.VectorFeatures import im.vector.app.features.analytics.extensions.toAnalyticsInteraction import im.vector.app.features.analytics.plan.Interaction import im.vector.app.features.analytics.plan.MobileScreen -import im.vector.app.features.attachments.AttachmentTypeSelectorView -import im.vector.app.features.attachments.AttachmentsHelper -import im.vector.app.features.attachments.ContactAttachment import im.vector.app.features.attachments.ShareIntentHandler -import im.vector.app.features.attachments.preview.AttachmentsPreviewActivity -import im.vector.app.features.attachments.preview.AttachmentsPreviewArgs -import im.vector.app.features.attachments.toGroupedContentAttachmentData import im.vector.app.features.call.SharedKnownCallsViewModel import im.vector.app.features.call.VectorCallActivity import im.vector.app.features.call.conference.ConferenceEvent @@ -146,22 +124,17 @@ import im.vector.app.features.call.conference.ConferenceEventEmitter import im.vector.app.features.call.conference.ConferenceEventObserver import im.vector.app.features.call.conference.JitsiCallViewModel import im.vector.app.features.call.webrtc.WebRtcCallManager -import im.vector.app.features.command.Command -import im.vector.app.features.command.ParsedCommand import im.vector.app.features.crypto.keysbackup.restore.KeysBackupRestoreActivity import im.vector.app.features.crypto.verification.VerificationBottomSheet import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.arguments.TimelineArgs import im.vector.app.features.home.room.detail.composer.CanSendStatus +import im.vector.app.features.home.room.detail.composer.MessageComposer import im.vector.app.features.home.room.detail.composer.MessageComposerAction -import im.vector.app.features.home.room.detail.composer.MessageComposerView -import im.vector.app.features.home.room.detail.composer.MessageComposerViewEvents +import im.vector.app.features.home.room.detail.composer.MessageComposerFragment import im.vector.app.features.home.room.detail.composer.MessageComposerViewModel -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.composer.voice.VoiceMessageRecorderView -import im.vector.app.features.home.room.detail.composer.voice.VoiceMessageRecorderView.RecordingUiState +import im.vector.app.features.home.room.detail.composer.voice.VoiceRecorderFragment import im.vector.app.features.home.room.detail.error.RoomNotFound import im.vector.app.features.home.room.detail.readreceipts.DisplayReadReceiptsBottomSheet import im.vector.app.features.home.room.detail.timeline.TimelineEventController @@ -171,7 +144,6 @@ import im.vector.app.features.home.room.detail.timeline.action.MessageSharedActi import im.vector.app.features.home.room.detail.timeline.edithistory.ViewEditHistoryBottomSheet import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider -import im.vector.app.features.home.room.detail.timeline.image.buildImageContentRendererData import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem import im.vector.app.features.home.room.detail.timeline.item.MessageAudioItem import im.vector.app.features.home.room.detail.timeline.item.MessageFileItem @@ -188,7 +160,6 @@ import im.vector.app.features.home.room.detail.widget.RoomWidgetsBottomSheet import im.vector.app.features.home.room.threads.ThreadsManager import im.vector.app.features.home.room.threads.arguments.ThreadTimelineArgs import im.vector.app.features.html.EventHtmlRenderer -import im.vector.app.features.html.PillImageSpan import im.vector.app.features.html.PillsPostProcessor import im.vector.app.features.invite.VectorInviteView import im.vector.app.features.location.LocationSharingMode @@ -206,25 +177,19 @@ import im.vector.app.features.roomprofile.RoomProfileActivity import im.vector.app.features.session.coroutineScope import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorSettingsActivity -import im.vector.app.features.share.SharedData import im.vector.app.features.spaces.share.ShareSpaceBottomSheet import im.vector.app.features.themes.ThemeUtils -import im.vector.app.features.voice.VoiceFailure 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 kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.billcarsonfr.jsonviewer.JSonViewerDialog -import org.commonmark.parser.Parser import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.session.content.ContentAttachmentData import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode @@ -233,11 +198,8 @@ import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent -import org.matrix.android.sdk.api.session.room.model.message.MessageContent -import org.matrix.android.sdk.api.session.room.model.message.MessageFormat import org.matrix.android.sdk.api.session.room.model.message.MessageImageInfoContent import org.matrix.android.sdk.api.session.room.model.message.MessageLocationContent -import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent import org.matrix.android.sdk.api.session.room.model.message.MessageStickerContent import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationRequestContent @@ -246,14 +208,11 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachme import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.timeline.Timeline import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent -import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent import org.matrix.android.sdk.api.session.widgets.model.Widget import org.matrix.android.sdk.api.session.widgets.model.WidgetType import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.MimeTypes import org.matrix.android.sdk.api.util.toMatrixItem -import reactivecircus.flowbinding.android.view.focusChanges -import reactivecircus.flowbinding.android.widget.textChanges import timber.log.Timber import java.net.URL import java.util.UUID @@ -264,8 +223,6 @@ class TimelineFragment : VectorBaseFragment<FragmentTimelineBinding>(), TimelineEventController.Callback, VectorInviteView.Callback, - AttachmentTypeSelectorView.Callback, - AttachmentsHelper.Callback, GalleryOrCameraDialogHelper.Listener, CurrentCallsView.Callback, VectorMenuProvider { @@ -273,7 +230,6 @@ class TimelineFragment : @Inject lateinit var session: Session @Inject lateinit var avatarRenderer: AvatarRenderer @Inject lateinit var timelineEventController: TimelineEventController - @Inject lateinit var autoCompleterFactory: AutoCompleter.Factory @Inject lateinit var permalinkHandler: PermalinkHandler @Inject lateinit var notificationDrawerManager: NotificationDrawerManager @Inject lateinit var eventHtmlRenderer: EventHtmlRenderer @@ -292,45 +248,18 @@ class TimelineFragment : @Inject lateinit var shareIntentHandler: ShareIntentHandler @Inject lateinit var clock: Clock @Inject lateinit var vectorFeatures: VectorFeatures - @Inject lateinit var buildMeta: BuildMeta @Inject lateinit var galleryOrCameraDialogHelperFactory: GalleryOrCameraDialogHelperFactory companion object { - - /** - * Sanitize the display name. - * - * @param displayName the display name to sanitize - * @return the sanitized display name - */ - private fun sanitizeDisplayName(displayName: String): String { - if (displayName.endsWith(ircPattern)) { - return displayName.substring(0, displayName.length - ircPattern.length) - } - - return displayName - } - const val MAX_TYPING_MESSAGE_USERS_COUNT = 4 - private const val ircPattern = " (IRC)" } private lateinit var galleryOrCameraDialogHelper: GalleryOrCameraDialogHelper private val timelineArgs: TimelineArgs by args() - private val glideRequests by lazy { - GlideApp.with(this) - } - private val pillsPostProcessor by lazy { - pillsPostProcessorFactory.create(timelineArgs.roomId) - } - private val autoCompleter: AutoCompleter by lazy { - autoCompleterFactory.create(timelineArgs.roomId, isThreadTimeLine()) - } - - private val timelineViewModel: TimelineViewModel by fragmentViewModel() - private val messageComposerViewModel: MessageComposerViewModel by fragmentViewModel() + private val timelineViewModel: TimelineViewModel by activityViewModel() + private val messageComposerViewModel: MessageComposerViewModel by activityViewModel() private val debouncer = Debouncer(createUIHandler()) private lateinit var scrollOnNewMessageCallback: ScrollOnNewMessageCallback @@ -351,21 +280,16 @@ class TimelineFragment : private lateinit var jumpToBottomViewVisibilityManager: JumpToBottomViewVisibilityManager private var modelBuildListener: OnModelBuildFinishedListener? = null - private lateinit var attachmentsHelper: AttachmentsHelper private lateinit var keyboardStateUtils: KeyboardStateUtils private lateinit var callActionsHandler: StartCallActionsHandler - private lateinit var attachmentTypeSelector: AttachmentTypeSelectorView - - private var lockSendButton = false private val currentCallsViewPresenter = CurrentCallsViewPresenter() private val isEmojiKeyboardVisible: Boolean get() = vectorPreferences.showEmojiKeyboard() private val lazyLoadedViews = RoomDetailLazyLoadedViews() - private val emojiPopup: EmojiPopup by lifecycleAwareLazy { - createEmojiPopup() - } + + private lateinit var composer: MessageComposer override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -376,6 +300,24 @@ class TimelineFragment : timelineViewModel.handle(RoomDetailAction.RoomUpgradeSuccess(replacementRoomId)) } } + + val composer = childFragmentManager.findFragmentById(R.id.composerContainer) as? MessageComposerFragment ?: run { + val fragment = MessageComposerFragment() + fragment.arguments = timelineArgs.toMvRxBundle() + childFragmentManager.commitTransaction { + replace(R.id.composerContainer, fragment) + } + fragment + } + this.composer = composer + + childFragmentManager.findFragmentById(R.id.voiceMessageRecorderContainer) as? VoiceRecorderFragment ?: run { + childFragmentManager.commitTransaction { + val fragment = VoiceRecorderFragment() + fragment.arguments = timelineArgs.toMvRxBundle() + replace(R.id.voiceMessageRecorderContainer, fragment) + } + } } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -384,7 +326,6 @@ class TimelineFragment : sharedActionViewModel = activityViewModelProvider.get(MessageSharedActionViewModel::class.java) sharedActivityActionViewModel = activityViewModelProvider.get(RoomDetailSharedActionViewModel::class.java) knownCallsViewModel = activityViewModelProvider.get(SharedKnownCallsViewModel::class.java) - attachmentsHelper = AttachmentsHelper(requireContext(), this, buildMeta).register() callActionsHandler = StartCallActionsHandler( roomId = timelineArgs.roomId, fragment = this, @@ -400,14 +341,11 @@ class TimelineFragment : setupToolbar(views.roomToolbar) .allowBack() setupRecyclerView() - setupComposer() setupNotificationView() setupJumpToReadMarkerView() setupActiveCallView() setupJumpToBottomView() - setupEmojiButton() setupRemoveJitsiWidgetView() - setupVoiceMessageView() setupLiveLocationIndicator() views.includeRoomToolbar.roomToolbarContentView.debouncedClicks { @@ -432,19 +370,6 @@ class TimelineFragment : updateJumpToReadMarkerViewVisibility() } - messageComposerViewModel.onEach(MessageComposerViewState::sendMode, MessageComposerViewState::canSendMessage) { mode, canSend -> - if (!canSend.boolean()) { - return@onEach - } - when (mode) { - is SendMode.Regular -> renderRegularMode(mode.text) - is SendMode.Edit -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_edit, R.string.edit, mode.text) - is SendMode.Quote -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_quote, R.string.action_quote, mode.text) - is SendMode.Reply -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_reply, R.string.reply, mode.text) - is SendMode.Voice -> renderVoiceMessageMode(mode.text) - } - } - timelineViewModel.onEach( RoomDetailViewState::syncState, RoomDetailViewState::incrementalSyncRequestState, @@ -458,24 +383,6 @@ class TimelineFragment : ) } - messageComposerViewModel.observeViewEvents { - when (it) { - is MessageComposerViewEvents.JoinRoomCommandSuccess -> handleJoinedToAnotherRoom(it) - is MessageComposerViewEvents.SlashCommandConfirmationRequest -> handleSlashCommandConfirmationRequest(it) - is MessageComposerViewEvents.SendMessageResult -> renderSendMessageResult(it) - is MessageComposerViewEvents.ShowMessage -> showSnackWithMessage(it.message) - is MessageComposerViewEvents.ShowRoomUpgradeDialog -> handleShowRoomUpgradeDialog(it) - is MessageComposerViewEvents.AnimateSendButtonVisibility -> handleSendButtonVisibilityChanged(it) - is MessageComposerViewEvents.OpenRoomMemberProfile -> openRoomMemberProfile(it.userId) - is MessageComposerViewEvents.VoicePlaybackOrRecordingFailure -> { - if (it.throwable is VoiceFailure.UnableToRecord) { - onCannotRecord() - } - showErrorInSnackbar(it.throwable) - } - } - } - timelineViewModel.observeViewEvents { when (it) { is RoomDetailViewEvents.Failure -> displayErrorMessage(it) @@ -515,51 +422,10 @@ class TimelineFragment : } if (savedInstanceState == null) { - handleShareData() handleSpaceShare() } } - private fun handleSlashCommandConfirmationRequest(action: MessageComposerViewEvents.SlashCommandConfirmationRequest) { - when (action.parsedCommand) { - is ParsedCommand.UnignoreUser -> promptUnignoreUser(action.parsedCommand) - else -> TODO("Add case for ${action.parsedCommand.javaClass.simpleName}") - } - lockSendButton = false - } - - private fun promptUnignoreUser(command: ParsedCommand.UnignoreUser) { - MaterialAlertDialogBuilder(requireActivity()) - .setTitle(R.string.room_participants_action_unignore_title) - .setMessage(getString(R.string.settings_unignore_user, command.userId)) - .setPositiveButton(R.string.unignore) { _, _ -> - messageComposerViewModel.handle(MessageComposerAction.SlashCommandConfirmed(command)) - } - .setNegativeButton(R.string.action_cancel, null) - .show() - } - - private fun renderVoiceMessageMode(content: String) { - ContentAttachmentData.fromJsonString(content)?.let { audioAttachmentData -> - views.voiceMessageRecorderView.isVisible = true - messageComposerViewModel.handle(MessageComposerAction.InitializeVoiceRecorder(audioAttachmentData)) - } - } - - private fun handleSendButtonVisibilityChanged(event: MessageComposerViewEvents.AnimateSendButtonVisibility) { - if (event.isVisible) { - views.voiceMessageRecorderView.isVisible = false - views.composerLayout.views.sendButton.alpha = 0f - views.composerLayout.views.sendButton.isVisible = true - views.composerLayout.views.sendButton.animate().alpha(1f).setDuration(150).start() - } else { - views.composerLayout.views.sendButton.isInvisible = true - views.voiceMessageRecorderView.alpha = 0f - views.voiceMessageRecorderView.isVisible = true - views.voiceMessageRecorderView.animate().alpha(1f).setDuration(150).start() - } - } - private fun setupRemoveJitsiWidgetView() { views.removeJitsiWidgetView.onCompleteSliding = { withState(timelineViewModel) { @@ -580,11 +446,6 @@ class TimelineFragment : timelineViewModel.handle(RoomDetailAction.UpdateJoinJitsiCallStatus(conferenceEvent)) } - private fun onCannotRecord() { - // Update the UI, cancel the animation - messageComposerViewModel.handle(MessageComposerAction.OnVoiceRecordingUiStateChanged(RecordingUiState.Idle)) - } - private fun acceptIncomingCall(event: RoomDetailViewEvents.DisplayAndAcceptCall) { val intent = VectorCallActivity.newIntent( context = vectorBaseActivity, @@ -601,12 +462,6 @@ class TimelineFragment : JoinReplacementRoomBottomSheet().show(childFragmentManager, tag) } - private fun handleShowRoomUpgradeDialog(roomDetailViewEvents: MessageComposerViewEvents.ShowRoomUpgradeDialog) { - val tag = MigrateRoomBottomSheet::javaClass.name - MigrateRoomBottomSheet.newInstance(timelineArgs.roomId, roomDetailViewEvents.newVersion) - .show(parentFragmentManager, tag) - } - private fun handleChatEffect(chatEffect: ChatEffect) { if (!requireContext().isAnimationEnabled()) { Timber.d("Do not perform chat effect, animations are disabled.") @@ -723,52 +578,6 @@ class TimelineFragment : ) } - private fun setupEmojiButton() { - views.composerLayout.views.composerEmojiButton.debouncedClicks { - emojiPopup.toggle() - } - } - - private fun createEmojiPopup(): EmojiPopup { - return EmojiPopup( - rootView = views.rootConstraintLayout, - keyboardAnimationStyle = R.style.emoji_fade_animation_style, - onEmojiPopupShownListener = { - views.composerLayout.views.composerEmojiButton.apply { - contentDescription = getString(R.string.a11y_close_emoji_picker) - setImageResource(R.drawable.ic_keyboard) - } - }, - onEmojiPopupDismissListener = lifecycleAwareDismissAction { - views.composerLayout.views.composerEmojiButton.apply { - contentDescription = getString(R.string.a11y_open_emoji_picker) - setImageResource(R.drawable.ic_insert_emoji) - } - }, - editText = views.composerLayout.views.composerEditText - ) - } - - /** - * Ensure dismiss actions only trigger when the fragment is in the started state. - * EmojiPopup by default dismisses onViewDetachedFromWindow, this can cause race conditions with onDestroyView. - */ - private fun lifecycleAwareDismissAction(action: () -> Unit): () -> Unit { - return { - if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) { - action() - } - } - } - - private val permissionVoiceMessageLauncher = registerForPermissionsResult { allGranted, deniedPermanently -> - if (allGranted) { - // In this case, let the user start again the gesture - } else if (deniedPermanently) { - vectorBaseActivity.onPermissionDeniedSnackbar(R.string.denied_permission_voice_message) - } - } - private fun createFailedMessagesWarningCallback(): FailedMessagesWarningView.Callback { return object : FailedMessagesWarningView.Callback { override fun onDeleteAllClicked() { @@ -788,86 +597,6 @@ class TimelineFragment : } } - private fun setupVoiceMessageView() { - audioMessagePlaybackTracker.track(AudioMessagePlaybackTracker.RECORDING_ID, views.voiceMessageRecorderView) - views.voiceMessageRecorderView.callback = object : VoiceMessageRecorderView.Callback { - - override fun onVoiceRecordingStarted() { - if (checkPermissions(PERMISSIONS_FOR_VOICE_MESSAGE, requireActivity(), permissionVoiceMessageLauncher)) { - messageComposerViewModel.handle(MessageComposerAction.StartRecordingVoiceMessage) - vibrate(requireContext()) - updateRecordingUiState(RecordingUiState.Recording(clock.epochMillis())) - } - } - - override fun onVoicePlaybackButtonClicked() { - messageComposerViewModel.handle(MessageComposerAction.PlayOrPauseRecordingPlayback) - } - - override fun onVoiceRecordingCancelled() { - messageComposerViewModel.handle(MessageComposerAction.EndRecordingVoiceMessage(isCancelled = true, rootThreadEventId = getRootThreadEventId())) - vibrate(requireContext()) - updateRecordingUiState(RecordingUiState.Idle) - } - - override fun onVoiceRecordingLocked() { - val startedState = withState(messageComposerViewModel) { it.voiceRecordingUiState as? RecordingUiState.Recording } - val startTime = startedState?.recordingStartTimestamp ?: clock.epochMillis() - updateRecordingUiState(RecordingUiState.Locked(startTime)) - } - - override fun onVoiceRecordingEnded() { - onSendVoiceMessage() - } - - override fun onSendVoiceMessage() { - messageComposerViewModel.handle( - MessageComposerAction.EndRecordingVoiceMessage(isCancelled = false, rootThreadEventId = getRootThreadEventId()) - ) - updateRecordingUiState(RecordingUiState.Idle) - } - - override fun onDeleteVoiceMessage() { - messageComposerViewModel.handle( - MessageComposerAction.EndRecordingVoiceMessage(isCancelled = true, rootThreadEventId = getRootThreadEventId()) - ) - updateRecordingUiState(RecordingUiState.Idle) - } - - override fun onRecordingLimitReached() { - messageComposerViewModel.handle( - MessageComposerAction.PauseRecordingVoiceMessage - ) - updateRecordingUiState(RecordingUiState.Draft) - } - - override fun onRecordingWaveformClicked() { - messageComposerViewModel.handle( - MessageComposerAction.PauseRecordingVoiceMessage - ) - updateRecordingUiState(RecordingUiState.Draft) - } - - override fun onVoiceWaveformTouchedUp(percentage: Float, duration: Int) { - messageComposerViewModel.handle( - MessageComposerAction.VoiceWaveformTouchedUp(AudioMessagePlaybackTracker.RECORDING_ID, duration, percentage) - ) - } - - override fun onVoiceWaveformMoved(percentage: Float, duration: Int) { - messageComposerViewModel.handle( - MessageComposerAction.VoiceWaveformTouchedUp(AudioMessagePlaybackTracker.RECORDING_ID, duration, percentage) - ) - } - - private fun updateRecordingUiState(state: RecordingUiState) { - messageComposerViewModel.handle( - MessageComposerAction.OnVoiceRecordingUiStateChanged(state) - ) - } - } - } - private fun setupLiveLocationIndicator() { views.liveLocationStatusIndicator.stopButton.debouncedClicks { timelineViewModel.handle(RoomDetailAction.StopLiveLocationSharing) @@ -945,25 +674,6 @@ class TimelineFragment : .show() } - private fun handleJoinedToAnotherRoom(action: MessageComposerViewEvents.JoinRoomCommandSuccess) { - views.composerLayout.setTextIfDifferent("") - lockSendButton = false - navigator.openRoom(vectorBaseActivity, action.roomId) - } - - private fun handleShareData() { - when (val sharedData = timelineArgs.sharedData) { - is SharedData.Text -> { - messageComposerViewModel.handle(MessageComposerAction.EnterRegularMode(sharedData.text, fromSharing = true)) - } - is SharedData.Attachments -> { - // open share edition - onContentAttachmentsReady(sharedData.attachmentData) - } - null -> Timber.v("No share data to process") - } - } - private fun handleSpaceShare() { timelineArgs.openShareSpaceForId?.let { spaceId -> ShareSpaceBottomSheet.show(childFragmentManager, spaceId, true) @@ -974,13 +684,11 @@ class TimelineFragment : } override fun onDestroyView() { - messageComposerViewModel.endAllVoiceActions() lazyLoadedViews.unBind() timelineEventController.callback = null timelineEventController.removeModelBuildListener(modelBuildListener) currentCallsViewPresenter.unBind() modelBuildListener = null - autoCompleter.clear() debouncer.cancelAll() views.timelineRecyclerView.cleanup() super.onDestroyView() @@ -1249,87 +957,11 @@ class TimelineFragment : .show() } - private fun renderRegularMode(content: String) { - autoCompleter.exitSpecialMode() - views.composerLayout.collapse() - views.composerLayout.setTextIfDifferent(content) - views.composerLayout.views.sendButton.contentDescription = getString(R.string.action_send) - } - - private fun renderSpecialMode( - event: TimelineEvent, - @DrawableRes iconRes: Int, - @StringRes descriptionRes: Int, - defaultContent: String - ) { - autoCompleter.enterSpecialMode() - // switch to expanded bar - views.composerLayout.views.composerRelatedMessageTitle.apply { - text = event.senderInfo.disambiguatedDisplayName - setTextColor(matrixItemColorProvider.getColor(MatrixItem.UserItem(event.root.senderId ?: "@"))) - } - - val messageContent: MessageContent? = event.getLastMessageContent() - val nonFormattedBody = when (messageContent) { - is MessageAudioContent -> getAudioContentBodyText(messageContent) - is MessagePollContent -> messageContent.getBestPollCreationInfo()?.question?.getBestQuestion() - is MessageBeaconInfoContent -> getString(R.string.live_location_description) - else -> messageContent?.body.orEmpty() - } - var formattedBody: CharSequence? = null - if (messageContent is MessageTextContent && messageContent.format == MessageFormat.FORMAT_MATRIX_HTML) { - val parser = Parser.builder().build() - val document = parser.parse(messageContent.formattedBody ?: messageContent.body) - formattedBody = eventHtmlRenderer.render(document, pillsPostProcessor) - } - views.composerLayout.views.composerRelatedMessageContent.text = (formattedBody ?: nonFormattedBody) - - // Image Event - val data = event.buildImageContentRendererData(dimensionConverter.dpToPx(66)) - val isImageVisible = if (data != null) { - imageContentRenderer.render(data, ImageContentRenderer.Mode.THUMBNAIL, views.composerLayout.views.composerRelatedMessageImage) - true - } else { - imageContentRenderer.clear(views.composerLayout.views.composerRelatedMessageImage) - false - } - - views.composerLayout.views.composerRelatedMessageImage.isVisible = isImageVisible - - views.composerLayout.setTextIfDifferent(defaultContent) - - views.composerLayout.views.composerRelatedMessageActionIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), iconRes)) - views.composerLayout.views.sendButton.contentDescription = getString(descriptionRes) - - avatarRenderer.render(event.senderInfo.toMatrixItem(), views.composerLayout.views.composerRelatedMessageAvatar) - - views.composerLayout.expand { - if (isAdded) { - // need to do it here also when not using quick reply - focusComposerAndShowKeyboard() - views.composerLayout.views.composerRelatedMessageImage.isVisible = isImageVisible - } - } - focusComposerAndShowKeyboard() - } - - private fun getAudioContentBodyText(messageContent: MessageAudioContent): String { - val formattedDuration = DateUtils.formatElapsedTime(((messageContent.audioInfo?.duration ?: 0) / 1000).toLong()) - return if (messageContent.voiceMessageIndicator != null) { - getString(R.string.voice_message_reply_content, formattedDuration) - } else { - getString(R.string.audio_message_reply_content, messageContent.body, formattedDuration) - } - } - override fun onResume() { super.onResume() notificationDrawerManager.setCurrentRoom(timelineArgs.roomId) roomDetailPendingActionStore.data?.let { handlePendingAction(it) } roomDetailPendingActionStore.data = null - - // Removed listeners should be set again - setupVoiceMessageView() } private fun handlePendingAction(roomDetailPendingAction: RoomDetailPendingAction) { @@ -1347,52 +979,6 @@ class TimelineFragment : override fun onPause() { super.onPause() notificationDrawerManager.setCurrentRoom(null) - audioMessagePlaybackTracker.pauseAllPlaybacks() - - if (withState(messageComposerViewModel) { it.isVoiceRecording } && requireActivity().isChangingConfigurations) { - // we're rotating, maintain any active recordings - } else { - messageComposerViewModel.handle(MessageComposerAction.OnEntersBackground(views.composerLayout.text.toString())) - } - } - - private val attachmentFileActivityResultLauncher = registerStartForActivityResult { - if (it.resultCode == Activity.RESULT_OK) { - attachmentsHelper.onFileResult(it.data) - } - } - - private val attachmentContactActivityResultLauncher = registerStartForActivityResult { - if (it.resultCode == Activity.RESULT_OK) { - attachmentsHelper.onContactResult(it.data) - } - } - - private val attachmentMediaActivityResultLauncher = registerStartForActivityResult { - if (it.resultCode == Activity.RESULT_OK) { - attachmentsHelper.onMediaResult(it.data) - } - } - - private val attachmentCameraActivityResultLauncher = registerStartForActivityResult { - if (it.resultCode == Activity.RESULT_OK) { - attachmentsHelper.onCameraResult() - } - } - - private val attachmentCameraVideoActivityResultLauncher = registerStartForActivityResult { - if (it.resultCode == Activity.RESULT_OK) { - attachmentsHelper.onCameraVideoResult() - } - } - - private val contentAttachmentActivityResultLauncher = registerStartForActivityResult { activityResult -> - val data = activityResult.data ?: return@registerStartForActivityResult - if (activityResult.resultCode == Activity.RESULT_OK) { - val sendData = AttachmentsPreviewActivity.getOutput(data) - val keepOriginalSize = AttachmentsPreviewActivity.getKeepOriginalSize(data) - timelineViewModel.handle(RoomDetailAction.SendMedia(sendData, !keepOriginalSize)) - } } private val emojiActivityResultLauncher = registerStartForActivityResult { activityResult -> @@ -1465,7 +1051,7 @@ class TimelineFragment : override fun performQuickReplyOnHolder(model: EpoxyModel<*>) { (model as? AbsMessageItem)?.attributes?.informationData?.let { val eventId = it.eventId - messageComposerViewModel.handle(MessageComposerAction.EnterReplyMode(eventId, views.composerLayout.text.toString())) + messageComposerViewModel.handle(MessageComposerAction.EnterReplyMode(eventId, composer.getCurrentText().toString())) } } @@ -1532,124 +1118,6 @@ class TimelineFragment : } } - private fun setupComposer() { - val composerEditText = views.composerLayout.views.composerEditText - autoCompleter.setup(composerEditText) - - observerUserTyping() - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - composerEditText.setUseIncognitoKeyboard(vectorPreferences.useIncognitoKeyboard()) - } - composerEditText.setSendMessageWithEnter(vectorPreferences.sendMessageWithEnter()) - - composerEditText.setOnEditorActionListener { v, actionId, keyEvent -> - val imeActionId = actionId and EditorInfo.IME_MASK_ACTION - if (EditorInfo.IME_ACTION_DONE == imeActionId || EditorInfo.IME_ACTION_SEND == imeActionId) { - sendTextMessage(v.text) - true - } - // Add external keyboard functionality (to send messages) - else if (null != keyEvent && - !keyEvent.isShiftPressed && - keyEvent.keyCode == KeyEvent.KEYCODE_ENTER && - resources.configuration.keyboard != Configuration.KEYBOARD_NOKEYS) { - sendTextMessage(v.text) - true - } else false - } - - views.composerLayout.views.composerEmojiButton.isVisible = vectorPreferences.showEmojiKeyboard() - - if (isThreadTimeLine() && timelineArgs.threadTimelineArgs?.showKeyboard == true) { - // Show keyboard when the user started a thread - views.composerLayout.views.composerEditText.showKeyboard(andRequestFocus = true) - } - views.composerLayout.callback = object : MessageComposerView.Callback { - override fun onAddAttachment() { - if (!::attachmentTypeSelector.isInitialized) { - attachmentTypeSelector = AttachmentTypeSelectorView(vectorBaseActivity, vectorBaseActivity.layoutInflater, this@TimelineFragment) - attachmentTypeSelector.setAttachmentVisibility( - AttachmentTypeSelectorView.Type.LOCATION, - vectorFeatures.isLocationSharingEnabled(), - ) - attachmentTypeSelector.setAttachmentVisibility( - AttachmentTypeSelectorView.Type.POLL, !isThreadTimeLine() - ) - attachmentTypeSelector.setAttachmentVisibility( - AttachmentTypeSelectorView.Type.VOICE_BROADCAST, - vectorFeatures.isVoiceBroadcastEnabled(), // TODO check user permission - ) - } - attachmentTypeSelector.show(views.composerLayout.views.attachmentButton) - } - - override fun onExpandOrCompactChange() { - views.composerLayout.views.composerEmojiButton.isVisible = isEmojiKeyboardVisible - } - - override fun onSendMessage(text: CharSequence) { - sendTextMessage(text) - } - - override fun onCloseRelatedMessage() { - messageComposerViewModel.handle(MessageComposerAction.EnterRegularMode(views.composerLayout.text.toString(), false)) - } - - override fun onRichContentSelected(contentUri: Uri): Boolean { - return sendUri(contentUri) - } - - override fun onTextChanged(text: CharSequence) { - messageComposerViewModel.handle(MessageComposerAction.OnTextChanged(text)) - } - } - } - - private fun sendTextMessage(text: CharSequence) { - if (lockSendButton) { - Timber.w("Send button is locked") - return - } - if (text.isNotBlank()) { - // We collapse ASAP, if not there will be a slight annoying delay - views.composerLayout.collapse(true) - lockSendButton = true - messageComposerViewModel.handle(MessageComposerAction.SendMessage(text, vectorPreferences.isMarkdownEnabled())) - emojiPopup.dismiss() - } - } - - private fun observerUserTyping() { - if (isThreadTimeLine()) return - views.composerLayout.views.composerEditText.textChanges() - .skipInitialValue() - .debounce(300) - .map { it.isNotEmpty() } - .onEach { - Timber.d("Typing: User is typing: $it") - messageComposerViewModel.handle(MessageComposerAction.UserIsTyping(it)) - } - .launchIn(viewLifecycleOwner.lifecycleScope) - - views.composerLayout.views.composerEditText.focusChanges() - .onEach { - timelineViewModel.handle(RoomDetailAction.ComposerFocusChange(it)) - } - .launchIn(viewLifecycleOwner.lifecycleScope) - } - - private fun sendUri(uri: Uri): Boolean { - val shareIntent = Intent(Intent.ACTION_SEND, uri) - val isHandled = shareIntentHandler.handleIncomingShareIntent(shareIntent, ::onContentAttachmentsReady, onPlainText = { - fatalError("Should not happen as we're generating a File based share Intent", vectorPreferences.failFast()) - }) - if (!isHandled) { - Toast.makeText(requireContext(), R.string.error_handling_incoming_share, Toast.LENGTH_SHORT).show() - } - return isHandled - } - override fun invalidate() = withState(timelineViewModel, messageComposerViewModel) { mainState, messageComposerState -> invalidateOptionsMenu() if (mainState.asyncRoomSummary is Fail) { @@ -1673,12 +1141,6 @@ class TimelineFragment : lazyLoadedViews.inviteView(false)?.isVisible = false if (mainState.tombstoneEvent == null) { - views.composerLayout.isInvisible = !messageComposerState.isComposerVisible - views.voiceMessageRecorderView.isVisible = messageComposerState.isVoiceMessageRecorderVisible - views.composerLayout.views.sendButton.isInvisible = !messageComposerState.isSendButtonVisible - views.voiceMessageRecorderView.render(messageComposerState.voiceRecordingUiState) - views.composerLayout.setRoomEncrypted(summary.isEncrypted) - // views.composerLayout.alwaysShowSendButton = false when (messageComposerState.canSendMessage) { CanSendStatus.Allowed -> { NotificationAreaView.State.Hidden @@ -1733,8 +1195,7 @@ class TimelineFragment : } private fun FragmentTimelineBinding.hideComposerViews() { - composerLayout.isVisible = false - voiceMessageRecorderView.isVisible = false + composerContainer.isVisible = false } private fun renderTypingMessageNotification(roomSummary: RoomSummary?, state: RoomDetailViewState) { @@ -1787,57 +1248,6 @@ class TimelineFragment : } } - private fun renderSendMessageResult(sendMessageResult: MessageComposerViewEvents.SendMessageResult) { - when (sendMessageResult) { - is MessageComposerViewEvents.SlashCommandLoading -> { - showLoading(null) - } - is MessageComposerViewEvents.SlashCommandError -> { - displayCommandError(getString(R.string.command_problem_with_parameters, sendMessageResult.command.command)) - } - is MessageComposerViewEvents.SlashCommandUnknown -> { - displayCommandError(getString(R.string.unrecognized_command, sendMessageResult.command)) - } - is MessageComposerViewEvents.SlashCommandResultOk -> { - handleSlashCommandResultOk(sendMessageResult.parsedCommand) - } - is MessageComposerViewEvents.SlashCommandResultError -> { - dismissLoadingDialog() - displayCommandError(errorFormatter.toHumanReadable(sendMessageResult.throwable)) - } - is MessageComposerViewEvents.SlashCommandNotImplemented -> { - displayCommandError(getString(R.string.not_implemented)) - } - is MessageComposerViewEvents.SlashCommandNotSupportedInThreads -> { - displayCommandError(getString(R.string.command_not_supported_in_threads, sendMessageResult.command.command)) - } - } - - lockSendButton = false - } - - private fun handleSlashCommandResultOk(parsedCommand: ParsedCommand) { - dismissLoadingDialog() - views.composerLayout.setTextIfDifferent("") - when (parsedCommand) { - is ParsedCommand.DevTools -> { - navigator.openDevTools(requireContext(), timelineArgs.roomId) - } - is ParsedCommand.SetMarkdown -> { - showSnackWithMessage(getString(if (parsedCommand.enable) R.string.markdown_has_been_enabled else R.string.markdown_has_been_disabled)) - } - else -> Unit - } - } - - private fun displayCommandError(message: String) { - MaterialAlertDialogBuilder(requireActivity()) - .setTitle(R.string.command_error) - .setMessage(message) - .setPositiveButton(R.string.ok, null) - .show() - } - private fun displayE2eError(withHeldCode: WithHeldCode?) { val msgId = when (withHeldCode) { WithHeldCode.BLACKLISTED -> R.string.crypto_error_withheld_blacklisted @@ -2066,7 +1476,7 @@ class TimelineFragment : inMemory = inMemory ) { pairs -> pairs.add(Pair(views.roomToolbar, ViewCompat.getTransitionName(views.roomToolbar) ?: "")) - pairs.add(Pair(views.composerLayout, ViewCompat.getTransitionName(views.composerLayout) ?: "")) + pairs.add(Pair(views.composerContainer, ViewCompat.getTransitionName(views.composerContainer) ?: "")) } } @@ -2078,16 +1488,10 @@ class TimelineFragment : view = view ) { pairs -> pairs.add(Pair(views.roomToolbar, ViewCompat.getTransitionName(views.roomToolbar) ?: "")) - pairs.add(Pair(views.composerLayout, ViewCompat.getTransitionName(views.composerLayout) ?: "")) + pairs.add(Pair(views.composerContainer, ViewCompat.getTransitionName(views.composerContainer) ?: "")) } } - private fun cleanUpAfterPermissionNotGranted() { - // Reset all pending data - timelineViewModel.pendingAction = null - attachmentsHelper.pendingType = null - } - override fun onLoadMore(direction: Timeline.Direction) { timelineViewModel.handle(RoomDetailAction.LoadMoreTimelineEvents(direction)) } @@ -2284,6 +1688,11 @@ class TimelineFragment : } } + private fun cleanUpAfterPermissionNotGranted() { + // Reset all pending data + timelineViewModel.pendingAction = null + } + private fun onSaveActionClicked(action: EventSharedAction.Save) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q && !checkPermissions(PERMISSIONS_FOR_WRITING_FILES, requireActivity(), saveActionActivityResultLauncher)) { @@ -2361,17 +1770,17 @@ class TimelineFragment : if (action.eventType in EventType.POLL_START) { navigator.openCreatePoll(requireContext(), timelineArgs.roomId, action.eventId, PollMode.EDIT) } else if (withState(messageComposerViewModel) { it.isVoiceMessageIdle }) { - messageComposerViewModel.handle(MessageComposerAction.EnterEditMode(action.eventId, views.composerLayout.text.toString())) + messageComposerViewModel.handle(MessageComposerAction.EnterEditMode(action.eventId)) } else { requireActivity().toast(R.string.error_voice_message_cannot_reply_or_edit) } } is EventSharedAction.Quote -> { - messageComposerViewModel.handle(MessageComposerAction.EnterQuoteMode(action.eventId, views.composerLayout.text.toString())) + messageComposerViewModel.handle(MessageComposerAction.EnterQuoteMode(action.eventId, composer.getCurrentText().toString())) } is EventSharedAction.Reply -> { if (withState(messageComposerViewModel) { it.isVoiceMessageIdle }) { - messageComposerViewModel.handle(MessageComposerAction.EnterReplyMode(action.eventId, views.composerLayout.text.toString())) + messageComposerViewModel.handle(MessageComposerAction.EnterReplyMode(action.eventId, composer.getCurrentText().toString())) } else { requireActivity().toast(R.string.error_voice_message_cannot_reply_or_edit) } @@ -2479,54 +1888,7 @@ class TimelineFragment : */ @SuppressLint("SetTextI18n") private fun insertUserDisplayNameInTextEditor(userId: String) { - val startToCompose = views.composerLayout.text.isNullOrBlank() - - if (startToCompose && - userId == session.myUserId) { - // Empty composer, current user: start an emote - views.composerLayout.views.composerEditText.setText(Command.EMOTE.command + " ") - views.composerLayout.views.composerEditText.setSelection(Command.EMOTE.command.length + 1) - } else { - val roomMember = timelineViewModel.getMember(userId) - // TODO move logic outside of fragment - (roomMember?.displayName ?: userId) - .let { sanitizeDisplayName(it) } - .let { displayName -> - buildSpannedString { - append(displayName) - setSpan( - PillImageSpan( - glideRequests, - avatarRenderer, - requireContext(), - MatrixItem.UserItem(userId, displayName, roomMember?.avatarUrl) - ) - .also { it.bind(views.composerLayout.views.composerEditText) }, - 0, - displayName.length, - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE - ) - append(if (startToCompose) ": " else " ") - }.let { pill -> - if (startToCompose) { - if (displayName.startsWith("/")) { - // Ensure displayName will not be interpreted as a Slash command - views.composerLayout.views.composerEditText.append("\\") - } - views.composerLayout.views.composerEditText.append(pill) - } else { - views.composerLayout.views.composerEditText.text?.insert(views.composerLayout.views.composerEditText.selectionStart, pill) - } - } - } - } - focusComposerAndShowKeyboard() - } - - private fun focusComposerAndShowKeyboard() { - if (views.composerLayout.isVisible) { - views.composerLayout.views.composerEditText.showKeyboard(andRequestFocus = true) - } + composer.insertUserDisplayNameInTextEditor(userId) } private fun showSnackWithMessage(message: String) { @@ -2630,79 +1992,6 @@ class TimelineFragment : } } - // AttachmentTypeSelectorView.Callback - private val typeSelectedActivityResultLauncher = registerForPermissionsResult { allGranted, deniedPermanently -> - if (allGranted) { - val pendingType = attachmentsHelper.pendingType - if (pendingType != null) { - attachmentsHelper.pendingType = null - launchAttachmentProcess(pendingType) - } - } else { - if (deniedPermanently) { - activity?.onPermissionDeniedDialog(R.string.denied_permission_generic) - } - cleanUpAfterPermissionNotGranted() - } - } - - override fun onTypeSelected(type: AttachmentTypeSelectorView.Type) { - if (checkPermissions(type.permissions, requireActivity(), typeSelectedActivityResultLauncher)) { - launchAttachmentProcess(type) - } else { - attachmentsHelper.pendingType = type - } - } - - private fun launchAttachmentProcess(type: AttachmentTypeSelectorView.Type) { - when (type) { - AttachmentTypeSelectorView.Type.CAMERA -> attachmentsHelper.openCamera( - activity = requireActivity(), - vectorPreferences = vectorPreferences, - cameraActivityResultLauncher = attachmentCameraActivityResultLauncher, - cameraVideoActivityResultLauncher = attachmentCameraVideoActivityResultLauncher - ) - AttachmentTypeSelectorView.Type.FILE -> attachmentsHelper.selectFile(attachmentFileActivityResultLauncher) - AttachmentTypeSelectorView.Type.GALLERY -> attachmentsHelper.selectGallery(attachmentMediaActivityResultLauncher) - AttachmentTypeSelectorView.Type.CONTACT -> attachmentsHelper.selectContact(attachmentContactActivityResultLauncher) - AttachmentTypeSelectorView.Type.STICKER -> timelineViewModel.handle(RoomDetailAction.SelectStickerAttachment) - AttachmentTypeSelectorView.Type.POLL -> navigator.openCreatePoll(requireContext(), timelineArgs.roomId, null, PollMode.CREATE) - AttachmentTypeSelectorView.Type.LOCATION -> { - navigator - .openLocationSharing( - context = requireContext(), - roomId = timelineArgs.roomId, - mode = LocationSharingMode.STATIC_SHARING, - initialLocationData = null, - locationOwnerId = session.myUserId - ) - } - AttachmentTypeSelectorView.Type.VOICE_BROADCAST -> timelineViewModel.handle(RoomDetailAction.StartVoiceBroadcast) - } - } - - // AttachmentsHelper.Callback - override fun onContentAttachmentsReady(attachments: List<ContentAttachmentData>) { - val grouped = attachments.toGroupedContentAttachmentData() - if (grouped.notPreviewables.isNotEmpty()) { - // Send the not previewable attachments right now (?) - timelineViewModel.handle(RoomDetailAction.SendMedia(grouped.notPreviewables, false)) - } - if (grouped.previewables.isNotEmpty()) { - val intent = AttachmentsPreviewActivity.newIntent(requireContext(), AttachmentsPreviewArgs(grouped.previewables)) - contentAttachmentActivityResultLauncher.launch(intent) - } - } - - override fun onContactAttachmentReady(contactAttachment: ContactAttachment) { - val formattedContact = contactAttachment.toHumanReadable() - messageComposerViewModel.handle(MessageComposerAction.SendMessage(formattedContact, false)) - } - - override fun onAttachmentError(throwable: Throwable) { - showFailure(throwable) - } - private fun onViewWidgetsClicked() { RoomWidgetsBottomSheet.newInstance() .show(childFragmentManager, "ROOM_WIDGETS_BOTTOM_SHEET") diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerAction.kt index 527f42a67a..ac39e0e915 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerAction.kt @@ -24,7 +24,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent sealed class MessageComposerAction : VectorViewModelAction { data class SendMessage(val text: CharSequence, val autoMarkdown: Boolean) : MessageComposerAction() - data class EnterEditMode(val eventId: String, val text: String) : MessageComposerAction() + data class EnterEditMode(val eventId: String) : MessageComposerAction() data class EnterQuoteMode(val eventId: String, val text: String) : MessageComposerAction() data class EnterReplyMode(val eventId: String, val text: String) : MessageComposerAction() data class EnterRegularMode(val text: String, val fromSharing: Boolean) : MessageComposerAction() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt new file mode 100644 index 0000000000..103950b3b8 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt @@ -0,0 +1,818 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home.room.detail.composer + +import android.app.Activity +import android.content.Intent +import android.content.res.Configuration +import android.net.Uri +import android.os.Build +import android.os.Bundle +import android.text.Editable +import android.text.Spannable +import android.text.format.DateUtils +import android.view.KeyEvent +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.inputmethod.EditorInfo +import android.widget.EditText +import android.widget.Toast +import androidx.annotation.DrawableRes +import androidx.annotation.RequiresApi +import androidx.annotation.StringRes +import androidx.core.content.ContextCompat +import androidx.core.text.buildSpannedString +import androidx.core.view.isInvisible +import androidx.core.view.isVisible +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import com.airbnb.mvrx.args +import com.airbnb.mvrx.existingViewModel +import com.airbnb.mvrx.withState +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.vanniktech.emoji.EmojiPopup +import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.R +import im.vector.app.core.error.fatalError +import im.vector.app.core.extensions.registerStartForActivityResult +import im.vector.app.core.extensions.showKeyboard +import im.vector.app.core.glide.GlideApp +import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.core.platform.lifecycleAwareLazy +import im.vector.app.core.platform.showOptimizedSnackbar +import im.vector.app.core.resources.BuildMeta +import im.vector.app.core.utils.DimensionConverter +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.databinding.FragmentComposerBinding +import im.vector.app.features.VectorFeatures +import im.vector.app.features.attachments.AttachmentTypeSelectorView +import im.vector.app.features.attachments.AttachmentsHelper +import im.vector.app.features.attachments.ContactAttachment +import im.vector.app.features.attachments.ShareIntentHandler +import im.vector.app.features.attachments.preview.AttachmentsPreviewActivity +import im.vector.app.features.attachments.preview.AttachmentsPreviewArgs +import im.vector.app.features.attachments.toGroupedContentAttachmentData +import im.vector.app.features.command.Command +import im.vector.app.features.command.ParsedCommand +import im.vector.app.features.home.AvatarRenderer +import im.vector.app.features.home.room.detail.AutoCompleter +import im.vector.app.features.home.room.detail.RoomDetailAction +import im.vector.app.features.home.room.detail.TimelineViewModel +import im.vector.app.features.home.room.detail.arguments.TimelineArgs +import im.vector.app.features.home.room.detail.composer.voice.VoiceMessageRecorderView +import im.vector.app.features.home.room.detail.timeline.action.MessageSharedActionViewModel +import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider +import im.vector.app.features.home.room.detail.timeline.image.buildImageContentRendererData +import im.vector.app.features.home.room.detail.upgrade.MigrateRoomBottomSheet +import im.vector.app.features.html.EventHtmlRenderer +import im.vector.app.features.html.PillImageSpan +import im.vector.app.features.html.PillsPostProcessor +import im.vector.app.features.location.LocationSharingMode +import im.vector.app.features.media.ImageContentRenderer +import im.vector.app.features.poll.PollMode +import im.vector.app.features.settings.VectorPreferences +import im.vector.app.features.share.SharedData +import im.vector.app.features.voice.VoiceFailure +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import org.commonmark.parser.Parser +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.content.ContentAttachmentData +import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent +import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent +import org.matrix.android.sdk.api.session.room.model.message.MessageContent +import org.matrix.android.sdk.api.session.room.model.message.MessageFormat +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.timeline.TimelineEvent +import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent +import org.matrix.android.sdk.api.util.MatrixItem +import org.matrix.android.sdk.api.util.toMatrixItem +import reactivecircus.flowbinding.android.view.focusChanges +import reactivecircus.flowbinding.android.widget.textChanges +import timber.log.Timber +import javax.inject.Inject + +interface MessageComposer { + fun getCurrentText(): Editable? + fun insertUserDisplayNameInTextEditor(userId: String) +} + +@AndroidEntryPoint +class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), MessageComposer, AttachmentsHelper.Callback, AttachmentTypeSelectorView.Callback { + + companion object { + private const val ircPattern = " (IRC)" + } + + @Inject lateinit var autoCompleterFactory: AutoCompleter.Factory + @Inject lateinit var avatarRenderer: AvatarRenderer + @Inject lateinit var matrixItemColorProvider: MatrixItemColorProvider + @Inject lateinit var eventHtmlRenderer: EventHtmlRenderer + @Inject lateinit var dimensionConverter: DimensionConverter + @Inject lateinit var imageContentRenderer: ImageContentRenderer + @Inject lateinit var shareIntentHandler: ShareIntentHandler + @Inject lateinit var pillsPostProcessorFactory: PillsPostProcessor.Factory + @Inject lateinit var vectorPreferences: VectorPreferences + @Inject lateinit var vectorFeatures: VectorFeatures + @Inject lateinit var buildMeta: BuildMeta + @Inject lateinit var session: Session + + private val timelineArgs: TimelineArgs by args() + + private val autoCompleter: AutoCompleter by lazy { + autoCompleterFactory.create(timelineArgs.roomId, isThreadTimeLine()) + } + + private val pillsPostProcessor by lazy { + pillsPostProcessorFactory.create(timelineArgs.roomId) + } + + private val emojiPopup: EmojiPopup by lifecycleAwareLazy { + createEmojiPopup() + } + + private val glideRequests by lazy { + GlideApp.with(this) + } + + private val isEmojiKeyboardVisible: Boolean + get() = vectorPreferences.showEmojiKeyboard() + + private var lockSendButton = false + + private lateinit var attachmentsHelper: AttachmentsHelper + private lateinit var attachmentTypeSelector: AttachmentTypeSelectorView + + private val timelineViewModel: TimelineViewModel by existingViewModel() + private val messageComposerViewModel: MessageComposerViewModel by existingViewModel() + private lateinit var sharedActionViewModel: MessageSharedActionViewModel + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentComposerBinding { + return FragmentComposerBinding.inflate(inflater, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + sharedActionViewModel = activityViewModelProvider.get(MessageSharedActionViewModel::class.java) + + attachmentsHelper = AttachmentsHelper(requireContext(), this, buildMeta).register() + + setupComposer() + setupEmojiButton() + + messageComposerViewModel.observeViewEvents { + when (it) { + is MessageComposerViewEvents.JoinRoomCommandSuccess -> handleJoinedToAnotherRoom(it) + is MessageComposerViewEvents.SlashCommandConfirmationRequest -> handleSlashCommandConfirmationRequest(it) + is MessageComposerViewEvents.SendMessageResult -> renderSendMessageResult(it) + is MessageComposerViewEvents.ShowMessage -> showSnackWithMessage(it.message) + is MessageComposerViewEvents.ShowRoomUpgradeDialog -> handleShowRoomUpgradeDialog(it) + is MessageComposerViewEvents.AnimateSendButtonVisibility -> handleSendButtonVisibilityChanged(it) + is MessageComposerViewEvents.OpenRoomMemberProfile -> openRoomMemberProfile(it.userId) + is MessageComposerViewEvents.VoicePlaybackOrRecordingFailure -> { + if (it.throwable is VoiceFailure.UnableToRecord) { + onCannotRecord() + } + showErrorInSnackbar(it.throwable) + } + } + } + + messageComposerViewModel.onEach(MessageComposerViewState::sendMode, MessageComposerViewState::canSendMessage) { mode, canSend -> + if (!canSend.boolean()) { + return@onEach + } + when (mode) { + is SendMode.Regular -> renderRegularMode(mode.text) + is SendMode.Edit -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_edit, R.string.edit, mode.text) + is SendMode.Quote -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_quote, R.string.action_quote, mode.text) + is SendMode.Reply -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_reply, R.string.reply, mode.text) + is SendMode.Voice -> renderVoiceMessageMode(mode.text) + } + } + + if (savedInstanceState != null) { + handleShareData() + } + } + + override fun onPause() { + super.onPause() + + if (withState(messageComposerViewModel) { it.isVoiceRecording } && requireActivity().isChangingConfigurations) { + // we're rotating, maintain any active recordings + } else { + messageComposerViewModel.handle(MessageComposerAction.OnEntersBackground(views.composerLayout.text.toString())) + } + } + + override fun onDestroyView() { + super.onDestroyView() + + autoCompleter.clear() + messageComposerViewModel.endAllVoiceActions() + } + + override fun invalidate() = withState(timelineViewModel, messageComposerViewModel) { mainState, messageComposerState -> + if (mainState.tombstoneEvent != null) return@withState + + views.root.isInvisible = !messageComposerState.isComposerVisible + views.composerLayout.views.sendButton.isInvisible = !messageComposerState.isSendButtonVisible + } + + private fun setupComposer() { + val composerEditText = views.composerLayout.views.composerEditText + composerEditText.setHint(R.string.room_message_placeholder) + + autoCompleter.setup(composerEditText) + + observerUserTyping() + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + composerEditText.setUseIncognitoKeyboard(vectorPreferences.useIncognitoKeyboard()) + } + composerEditText.setSendMessageWithEnter(vectorPreferences.sendMessageWithEnter()) + + composerEditText.setOnEditorActionListener { v, actionId, keyEvent -> + val imeActionId = actionId and EditorInfo.IME_MASK_ACTION + if (EditorInfo.IME_ACTION_DONE == imeActionId || EditorInfo.IME_ACTION_SEND == imeActionId) { + sendTextMessage(v.text) + true + } + // Add external keyboard functionality (to send messages) + else if (null != keyEvent && + !keyEvent.isShiftPressed && + keyEvent.keyCode == KeyEvent.KEYCODE_ENTER && + resources.configuration.keyboard != Configuration.KEYBOARD_NOKEYS) { + sendTextMessage(v.text) + true + } else false + } + + views.composerLayout.views.composerEmojiButton.isVisible = vectorPreferences.showEmojiKeyboard() + + if (isThreadTimeLine() && timelineArgs.threadTimelineArgs?.showKeyboard == true) { + // Show keyboard when the user started a thread + views.composerLayout.views.composerEditText.showKeyboard(andRequestFocus = true) + } + views.composerLayout.callback = object : MessageComposerView.Callback { + override fun onAddAttachment() { + if (!::attachmentTypeSelector.isInitialized) { + attachmentTypeSelector = AttachmentTypeSelectorView(vectorBaseActivity, vectorBaseActivity.layoutInflater, this@MessageComposerFragment) + attachmentTypeSelector.setAttachmentVisibility( + AttachmentTypeSelectorView.Type.LOCATION, + vectorFeatures.isLocationSharingEnabled(), + ) + attachmentTypeSelector.setAttachmentVisibility( + AttachmentTypeSelectorView.Type.POLL, !isThreadTimeLine() + ) + attachmentTypeSelector.setAttachmentVisibility( + AttachmentTypeSelectorView.Type.VOICE_BROADCAST, + vectorFeatures.isVoiceBroadcastEnabled(), // TODO check user permission + ) + } + attachmentTypeSelector.show(views.composerLayout.views.attachmentButton) + } + + override fun onExpandOrCompactChange() { + views.composerLayout.views.composerEmojiButton.isVisible = isEmojiKeyboardVisible + } + + override fun onSendMessage(text: CharSequence) { + sendTextMessage(text) + } + + override fun onCloseRelatedMessage() { + messageComposerViewModel.handle(MessageComposerAction.EnterRegularMode(views.composerLayout.text.toString(), false)) + } + + override fun onRichContentSelected(contentUri: Uri): Boolean { + return sendUri(contentUri) + } + + override fun onTextChanged(text: CharSequence) { + messageComposerViewModel.handle(MessageComposerAction.OnTextChanged(text)) + } + } + } + + private fun sendTextMessage(text: CharSequence) { + if (lockSendButton) { + Timber.w("Send button is locked") + return + } + if (text.isNotBlank()) { + // We collapse ASAP, if not there will be a slight annoying delay + views.composerLayout.collapse(true) + lockSendButton = true + messageComposerViewModel.handle(MessageComposerAction.SendMessage(text, vectorPreferences.isMarkdownEnabled())) + emojiPopup.dismiss() + } + } + + private fun sendUri(uri: Uri): Boolean { + val shareIntent = Intent(Intent.ACTION_SEND, uri) + val isHandled = shareIntentHandler.handleIncomingShareIntent(shareIntent, ::onContentAttachmentsReady, onPlainText = { + fatalError("Should not happen as we're generating a File based share Intent", vectorPreferences.failFast()) + }) + if (!isHandled) { + Toast.makeText(requireContext(), R.string.error_handling_incoming_share, Toast.LENGTH_SHORT).show() + } + return isHandled + } + + private fun renderRegularMode(content: String) { + autoCompleter.exitSpecialMode() + views.composerLayout.collapse() + views.composerLayout.setTextIfDifferent(content) + views.composerLayout.views.sendButton.contentDescription = getString(R.string.action_send) + } + + private fun renderSpecialMode( + event: TimelineEvent, + @DrawableRes iconRes: Int, + @StringRes descriptionRes: Int, + defaultContent: String + ) { + autoCompleter.enterSpecialMode() + // switch to expanded bar + views.composerLayout.views.composerRelatedMessageTitle.apply { + text = event.senderInfo.disambiguatedDisplayName + setTextColor(matrixItemColorProvider.getColor(MatrixItem.UserItem(event.root.senderId ?: "@"))) + } + + val messageContent: MessageContent? = event.getLastMessageContent() + val nonFormattedBody = when (messageContent) { + is MessageAudioContent -> getAudioContentBodyText(messageContent) + is MessagePollContent -> messageContent.getBestPollCreationInfo()?.question?.getBestQuestion() + is MessageBeaconInfoContent -> getString(R.string.live_location_description) + else -> messageContent?.body.orEmpty() + } + var formattedBody: CharSequence? = null + if (messageContent is MessageTextContent && messageContent.format == MessageFormat.FORMAT_MATRIX_HTML) { + val parser = Parser.builder().build() + val document = parser.parse(messageContent.formattedBody ?: messageContent.body) + formattedBody = eventHtmlRenderer.render(document, pillsPostProcessor) + } + views.composerLayout.views.composerRelatedMessageContent.text = (formattedBody ?: nonFormattedBody) + + // Image Event + val data = event.buildImageContentRendererData(dimensionConverter.dpToPx(66)) + val isImageVisible = if (data != null) { + imageContentRenderer.render(data, ImageContentRenderer.Mode.THUMBNAIL, views.composerLayout.views.composerRelatedMessageImage) + true + } else { + imageContentRenderer.clear(views.composerLayout.views.composerRelatedMessageImage) + false + } + + views.composerLayout.views.composerRelatedMessageImage.isVisible = isImageVisible + + views.composerLayout.setTextIfDifferent(defaultContent) + + views.composerLayout.views.composerRelatedMessageActionIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), iconRes)) + views.composerLayout.views.sendButton.contentDescription = getString(descriptionRes) + + avatarRenderer.render(event.senderInfo.toMatrixItem(), views.composerLayout.views.composerRelatedMessageAvatar) + + views.composerLayout.expand { + if (isAdded) { + // need to do it here also when not using quick reply + focusComposerAndShowKeyboard() + views.composerLayout.views.composerRelatedMessageImage.isVisible = isImageVisible + } + } + focusComposerAndShowKeyboard() + } + + private fun observerUserTyping() { + if (isThreadTimeLine()) return + views.composerLayout.views.composerEditText.textChanges() + .skipInitialValue() + .debounce(300) + .map { it.isNotEmpty() } + .onEach { + Timber.d("Typing: User is typing: $it") + messageComposerViewModel.handle(MessageComposerAction.UserIsTyping(it)) + } + .launchIn(viewLifecycleOwner.lifecycleScope) + + views.composerLayout.views.composerEditText.focusChanges() + .onEach { + timelineViewModel.handle(RoomDetailAction.ComposerFocusChange(it)) + } + .launchIn(viewLifecycleOwner.lifecycleScope) + } + + private fun focusComposerAndShowKeyboard() { + if (views.composerLayout.isVisible) { + views.composerLayout.views.composerEditText.showKeyboard(andRequestFocus = true) + } + } + + private fun handleSendButtonVisibilityChanged(event: MessageComposerViewEvents.AnimateSendButtonVisibility) { + if (event.isVisible) { + views.root.views.sendButton.alpha = 0f + views.root.views.sendButton.isVisible = true + views.root.views.sendButton.animate().alpha(1f).setDuration(150).start() + } else { + views.root.views.sendButton.isInvisible = true + } + } + + private fun renderVoiceMessageMode(content: String) { + ContentAttachmentData.fromJsonString(content)?.let { audioAttachmentData -> + // TODO: review this behaviour +// views.voiceMessageRecorderView.isVisible = true + messageComposerViewModel.handle(MessageComposerAction.InitializeVoiceRecorder(audioAttachmentData)) + } + } + + private fun getAudioContentBodyText(messageContent: MessageAudioContent): String { + val formattedDuration = DateUtils.formatElapsedTime(((messageContent.audioInfo?.duration ?: 0) / 1000).toLong()) + return if (messageContent.voiceMessageIndicator != null) { + getString(R.string.voice_message_reply_content, formattedDuration) + } else { + getString(R.string.audio_message_reply_content, messageContent.body, formattedDuration) + } + } + + private fun createEmojiPopup(): EmojiPopup { + return EmojiPopup( + rootView = views.root, + keyboardAnimationStyle = R.style.emoji_fade_animation_style, + onEmojiPopupShownListener = { + views.composerLayout.views.composerEmojiButton.apply { + contentDescription = getString(R.string.a11y_close_emoji_picker) + setImageResource(R.drawable.ic_keyboard) + } + }, + onEmojiPopupDismissListener = lifecycleAwareDismissAction { + views.composerLayout.views.composerEmojiButton.apply { + contentDescription = getString(R.string.a11y_open_emoji_picker) + setImageResource(R.drawable.ic_insert_emoji) + } + }, + editText = views.composerLayout.views.composerEditText + ) + } + + /** + * Ensure dismiss actions only trigger when the fragment is in the started state. + * EmojiPopup by default dismisses onViewDetachedFromWindow, this can cause race conditions with onDestroyView. + */ + private fun lifecycleAwareDismissAction(action: () -> Unit): () -> Unit { + return { + if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) { + action() + } + } + } + + private fun setupEmojiButton() { + views.composerLayout.views.composerEmojiButton.debouncedClicks { + emojiPopup.toggle() + } + } + + private fun onCannotRecord() { + // Update the UI, cancel the animation + messageComposerViewModel.handle(MessageComposerAction.OnVoiceRecordingUiStateChanged(VoiceMessageRecorderView.RecordingUiState.Idle)) + } + + private fun handleJoinedToAnotherRoom(action: MessageComposerViewEvents.JoinRoomCommandSuccess) { + views.composerLayout.setTextIfDifferent("") + lockSendButton = false + navigator.openRoom(vectorBaseActivity, action.roomId) + } + + private fun handleSlashCommandConfirmationRequest(action: MessageComposerViewEvents.SlashCommandConfirmationRequest) { + when (action.parsedCommand) { + is ParsedCommand.UnignoreUser -> promptUnignoreUser(action.parsedCommand) + else -> TODO("Add case for ${action.parsedCommand.javaClass.simpleName}") + } + lockSendButton = false + } + + private fun promptUnignoreUser(command: ParsedCommand.UnignoreUser) { + MaterialAlertDialogBuilder(requireActivity()) + .setTitle(R.string.room_participants_action_unignore_title) + .setMessage(getString(R.string.settings_unignore_user, command.userId)) + .setPositiveButton(R.string.unignore) { _, _ -> + messageComposerViewModel.handle(MessageComposerAction.SlashCommandConfirmed(command)) + } + .setNegativeButton(R.string.action_cancel, null) + .show() + } + + private fun renderSendMessageResult(sendMessageResult: MessageComposerViewEvents.SendMessageResult) { + when (sendMessageResult) { + is MessageComposerViewEvents.SlashCommandLoading -> { + showLoading(null) + } + is MessageComposerViewEvents.SlashCommandError -> { + displayCommandError(getString(R.string.command_problem_with_parameters, sendMessageResult.command.command)) + } + is MessageComposerViewEvents.SlashCommandUnknown -> { + displayCommandError(getString(R.string.unrecognized_command, sendMessageResult.command)) + } + is MessageComposerViewEvents.SlashCommandResultOk -> { + handleSlashCommandResultOk(sendMessageResult.parsedCommand) + } + is MessageComposerViewEvents.SlashCommandResultError -> { + dismissLoadingDialog() + displayCommandError(errorFormatter.toHumanReadable(sendMessageResult.throwable)) + } + is MessageComposerViewEvents.SlashCommandNotImplemented -> { + displayCommandError(getString(R.string.not_implemented)) + } + is MessageComposerViewEvents.SlashCommandNotSupportedInThreads -> { + displayCommandError(getString(R.string.command_not_supported_in_threads, sendMessageResult.command.command)) + } + } + + lockSendButton = false + } + + private fun handleSlashCommandResultOk(parsedCommand: ParsedCommand) { + dismissLoadingDialog() + views.composerLayout.setTextIfDifferent("") + when (parsedCommand) { + is ParsedCommand.DevTools -> { + navigator.openDevTools(requireContext(), timelineArgs.roomId) + } + is ParsedCommand.SetMarkdown -> { + showSnackWithMessage(getString(if (parsedCommand.enable) R.string.markdown_has_been_enabled else R.string.markdown_has_been_disabled)) + } + else -> Unit + } + } + + private fun displayCommandError(message: String) { + MaterialAlertDialogBuilder(requireActivity()) + .setTitle(R.string.command_error) + .setMessage(message) + .setPositiveButton(R.string.ok, null) + .show() + } + + private fun showSnackWithMessage(message: String) { + view?.showOptimizedSnackbar(message) + } + + private fun handleShowRoomUpgradeDialog(roomDetailViewEvents: MessageComposerViewEvents.ShowRoomUpgradeDialog) { + val tag = MigrateRoomBottomSheet::javaClass.name + MigrateRoomBottomSheet.newInstance(timelineArgs.roomId, roomDetailViewEvents.newVersion) + .show(parentFragmentManager, tag) + } + + private fun openRoomMemberProfile(userId: String) { + navigator.openRoomMemberProfile(userId = userId, roomId = timelineArgs.roomId, context = requireActivity()) + } + + private val contentAttachmentActivityResultLauncher = registerStartForActivityResult { activityResult -> + val data = activityResult.data ?: return@registerStartForActivityResult + if (activityResult.resultCode == Activity.RESULT_OK) { + val sendData = AttachmentsPreviewActivity.getOutput(data) + val keepOriginalSize = AttachmentsPreviewActivity.getKeepOriginalSize(data) + timelineViewModel.handle(RoomDetailAction.SendMedia(sendData, !keepOriginalSize)) + } + } + + /** + * Returns the root thread event if we are in a thread room, otherwise returns null. + */ + fun getRootThreadEventId(): String? = timelineArgs.threadTimelineArgs?.rootThreadEventId + + /** + * Returns true if the current room is a Thread room, false otherwise. + */ + private fun isThreadTimeLine(): Boolean = timelineArgs.threadTimelineArgs?.rootThreadEventId != null + + + // AttachmentsHelper.Callback + override fun onContentAttachmentsReady(attachments: List<ContentAttachmentData>) { + val grouped = attachments.toGroupedContentAttachmentData() + if (grouped.notPreviewables.isNotEmpty()) { + // Send the not previewable attachments right now (?) + timelineViewModel.handle(RoomDetailAction.SendMedia(grouped.notPreviewables, false)) + } + if (grouped.previewables.isNotEmpty()) { + val intent = AttachmentsPreviewActivity.newIntent(requireContext(), AttachmentsPreviewArgs(grouped.previewables)) + contentAttachmentActivityResultLauncher.launch(intent) + } + } + + override fun onContactAttachmentReady(contactAttachment: ContactAttachment) { + val formattedContact = contactAttachment.toHumanReadable() + messageComposerViewModel.handle(MessageComposerAction.SendMessage(formattedContact, false)) + } + + override fun onAttachmentError(throwable: Throwable) { + showFailure(throwable) + } + + // AttachmentTypeSelectorView.Callback + private val typeSelectedActivityResultLauncher = registerForPermissionsResult { allGranted, deniedPermanently -> + if (allGranted) { + val pendingType = attachmentsHelper.pendingType + if (pendingType != null) { + attachmentsHelper.pendingType = null + launchAttachmentProcess(pendingType) + } + } else { + if (deniedPermanently) { + activity?.onPermissionDeniedDialog(R.string.denied_permission_generic) + } + cleanUpAfterPermissionNotGranted() + } + } + + private fun launchAttachmentProcess(type: AttachmentTypeSelectorView.Type) { + when (type) { + AttachmentTypeSelectorView.Type.CAMERA -> attachmentsHelper.openCamera( + activity = requireActivity(), + vectorPreferences = vectorPreferences, + cameraActivityResultLauncher = attachmentCameraActivityResultLauncher, + cameraVideoActivityResultLauncher = attachmentCameraVideoActivityResultLauncher + ) + AttachmentTypeSelectorView.Type.FILE -> attachmentsHelper.selectFile(attachmentFileActivityResultLauncher) + AttachmentTypeSelectorView.Type.GALLERY -> attachmentsHelper.selectGallery(attachmentMediaActivityResultLauncher) + AttachmentTypeSelectorView.Type.CONTACT -> attachmentsHelper.selectContact(attachmentContactActivityResultLauncher) + AttachmentTypeSelectorView.Type.STICKER -> timelineViewModel.handle(RoomDetailAction.SelectStickerAttachment) + AttachmentTypeSelectorView.Type.POLL -> navigator.openCreatePoll(requireContext(), timelineArgs.roomId, null, PollMode.CREATE) + AttachmentTypeSelectorView.Type.LOCATION -> { + navigator + .openLocationSharing( + context = requireContext(), + roomId = timelineArgs.roomId, + mode = LocationSharingMode.STATIC_SHARING, + initialLocationData = null, + locationOwnerId = session.myUserId + ) + } + AttachmentTypeSelectorView.Type.VOICE_BROADCAST -> timelineViewModel.handle(RoomDetailAction.StartVoiceBroadcast) + } + } + + override fun onTypeSelected(type: AttachmentTypeSelectorView.Type) { + if (checkPermissions(type.permissions, requireActivity(), typeSelectedActivityResultLauncher)) { + launchAttachmentProcess(type) + } else { + attachmentsHelper.pendingType = type + } + } + + private val attachmentFileActivityResultLauncher = registerStartForActivityResult { + if (it.resultCode == Activity.RESULT_OK) { + attachmentsHelper.onFileResult(it.data) + } + } + + private val attachmentContactActivityResultLauncher = registerStartForActivityResult { + if (it.resultCode == Activity.RESULT_OK) { + attachmentsHelper.onContactResult(it.data) + } + } + + private val attachmentMediaActivityResultLauncher = registerStartForActivityResult { + if (it.resultCode == Activity.RESULT_OK) { + attachmentsHelper.onMediaResult(it.data) + } + } + + private val attachmentCameraActivityResultLauncher = registerStartForActivityResult { + if (it.resultCode == Activity.RESULT_OK) { + attachmentsHelper.onCameraResult() + } + } + + private val attachmentCameraVideoActivityResultLauncher = registerStartForActivityResult { + if (it.resultCode == Activity.RESULT_OK) { + attachmentsHelper.onCameraVideoResult() + } + } + + private fun cleanUpAfterPermissionNotGranted() { + // Reset all pending data + timelineViewModel.pendingAction = null + attachmentsHelper.pendingType = null + } + + private fun handleShareData() { + when (val sharedData = timelineArgs.sharedData) { + is SharedData.Text -> { + messageComposerViewModel.handle(MessageComposerAction.EnterRegularMode(sharedData.text, fromSharing = true)) + } + is SharedData.Attachments -> { + // open share edition + onContentAttachmentsReady(sharedData.attachmentData) + } + null -> Timber.v("No share data to process") + } + } + + override fun getCurrentText(): Editable? { + return views.composerLayout.text + } + + override fun insertUserDisplayNameInTextEditor(userId: String) { + val startToCompose = views.composerLayout.text.isNullOrBlank() + + if (startToCompose && + userId == session.myUserId) { + // Empty composer, current user: start an emote + views.composerLayout.views.composerEditText.setText(Command.EMOTE.command + " ") + views.composerLayout.views.composerEditText.setSelection(Command.EMOTE.command.length + 1) + } else { + val roomMember = timelineViewModel.getMember(userId) + // TODO move logic outside of fragment + sanitizeDisplayName(roomMember?.displayName ?: userId) + .let { displayName -> + buildSpannedString { + append(displayName) + setSpan( + PillImageSpan( + glideRequests, + avatarRenderer, + requireContext(), + MatrixItem.UserItem(userId, displayName, roomMember?.avatarUrl) + ) + .also { it.bind(views.composerLayout.views.composerEditText) }, + 0, + displayName.length, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ) + append(if (startToCompose) ": " else " ") + }.let { pill -> + if (startToCompose) { + if (displayName.startsWith("/")) { + // Ensure displayName will not be interpreted as a Slash command + views.composerLayout.views.composerEditText.append("\\") + } + views.composerLayout.views.composerEditText.append(pill) + } else { + views.composerLayout.views.composerEditText.text?.insert(views.composerLayout.views.composerEditText.selectionStart, pill) + } + } + } + } + focusComposerAndShowKeyboard() + } + + /** + * Sanitize the display name. + * + * @param displayName the display name to sanitize + * @return the sanitized display name + */ + private fun sanitizeDisplayName(displayName: String): String { + if (displayName.endsWith(ircPattern)) { + return displayName.substring(0, displayName.length - ircPattern.length) + } + + return displayName + } + + /** Set whether the keyboard should disable personalized learning. */ + @RequiresApi(Build.VERSION_CODES.O) + private fun EditText.setUseIncognitoKeyboard(useIncognitoKeyboard: Boolean) { + imeOptions = if (useIncognitoKeyboard) { + imeOptions or EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING + } else { + imeOptions and EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING.inv() + } + } + + /** Set whether enter should send the message or add a new line. */ + private fun EditText.setSendMessageWithEnter(sendMessageWithEnter: Boolean) { + if (sendMessageWithEnter) { + inputType = inputType and EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE.inv() + imeOptions = imeOptions or EditorInfo.IME_ACTION_SEND + } else { + inputType = inputType or EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE + imeOptions = imeOptions and EditorInfo.IME_ACTION_SEND.inv() + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerView.kt index b1b2c87e9c..1935c9460b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerView.kt @@ -149,12 +149,4 @@ class MessageComposerView @JvmOverloads constructor( } TransitionManager.beginDelayedTransition((parent as? ViewGroup ?: this), transition) } - - fun setRoomEncrypted(isEncrypted: Boolean) { - if (isEncrypted) { - views.composerEditText.setHint(R.string.room_message_placeholder) - } else { - views.composerEditText.setHint(R.string.room_message_placeholder) - } - } } 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 new file mode 100644 index 0000000000..964331f924 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceRecorderFragment.kt @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home.room.detail.composer.voice + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.view.isVisible +import com.airbnb.mvrx.args +import com.airbnb.mvrx.existingViewModel +import com.airbnb.mvrx.withState +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 +import im.vector.app.core.utils.registerForPermissionsResult +import im.vector.app.databinding.FragmentVoiceRecorderBinding +import im.vector.app.features.home.room.detail.TimelineViewModel +import im.vector.app.features.home.room.detail.arguments.TimelineArgs +import im.vector.app.features.home.room.detail.composer.MessageComposerAction +import im.vector.app.features.home.room.detail.composer.MessageComposerViewEvents +import im.vector.app.features.home.room.detail.composer.MessageComposerViewModel +import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker +import javax.inject.Inject + +@AndroidEntryPoint +class VoiceRecorderFragment : VectorBaseFragment<FragmentVoiceRecorderBinding>() { + + @Inject lateinit var audioMessagePlaybackTracker: AudioMessagePlaybackTracker + @Inject lateinit var clock: Clock + + private val timelineArgs: TimelineArgs by args() + + private val timelineViewModel: TimelineViewModel by existingViewModel() + private val messageComposerViewModel: MessageComposerViewModel by existingViewModel() + + private val permissionVoiceMessageLauncher = registerForPermissionsResult { allGranted, deniedPermanently -> + if (allGranted) { + // In this case, let the user start again the gesture + } else if (deniedPermanently) { + vectorBaseActivity.onPermissionDeniedSnackbar(R.string.denied_permission_voice_message) + } + } + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentVoiceRecorderBinding { + return FragmentVoiceRecorderBinding.inflate(inflater, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + messageComposerViewModel.observeViewEvents { + when (it) { + is MessageComposerViewEvents.AnimateSendButtonVisibility -> handleSendButtonVisibilityChanged(it.isVisible) + else -> Unit + } + } + } + + override fun onResume() { + super.onResume() + + // Removed listeners should be set again + setupVoiceMessageView() + } + + override fun onPause() { + super.onPause() + + audioMessagePlaybackTracker.pauseAllPlaybacks() + } + + override fun invalidate() = withState(timelineViewModel, messageComposerViewModel) { mainState, messageComposerState -> + if (mainState.tombstoneEvent != null) return@withState + + with(views.root) { + isVisible = messageComposerState.isVoiceMessageRecorderVisible + render(messageComposerState.voiceRecordingUiState) + } + } + + private fun handleSendButtonVisibilityChanged(isSendButtonVisible: Boolean) { + if (isSendButtonVisible) { + views.root.isVisible = false + } else { + views.root.alpha = 0f + views.root.isVisible = true + views.root.animate().alpha(1f).setDuration(150).start() + } + } + + private fun setupVoiceMessageView() { + audioMessagePlaybackTracker.track(AudioMessagePlaybackTracker.RECORDING_ID, views.voiceMessageRecorderView) + views.voiceMessageRecorderView.callback = object : VoiceMessageRecorderView.Callback { + + override fun onVoiceRecordingStarted() { + if (checkPermissions(PERMISSIONS_FOR_VOICE_MESSAGE, requireActivity(), permissionVoiceMessageLauncher)) { + messageComposerViewModel.handle(MessageComposerAction.StartRecordingVoiceMessage) + vibrate(requireContext()) + updateRecordingUiState(VoiceMessageRecorderView.RecordingUiState.Recording(clock.epochMillis())) + } + } + + override fun onVoicePlaybackButtonClicked() { + messageComposerViewModel.handle(MessageComposerAction.PlayOrPauseRecordingPlayback) + } + + override fun onVoiceRecordingCancelled() { + messageComposerViewModel.handle(MessageComposerAction.EndRecordingVoiceMessage(isCancelled = true, rootThreadEventId = getRootThreadEventId())) + vibrate(requireContext()) + updateRecordingUiState(VoiceMessageRecorderView.RecordingUiState.Idle) + } + + override fun onVoiceRecordingLocked() { + val startedState = withState(messageComposerViewModel) { it.voiceRecordingUiState as? VoiceMessageRecorderView.RecordingUiState.Recording } + val startTime = startedState?.recordingStartTimestamp ?: clock.epochMillis() + updateRecordingUiState(VoiceMessageRecorderView.RecordingUiState.Locked(startTime)) + } + + override fun onVoiceRecordingEnded() { + onSendVoiceMessage() + } + + override fun onSendVoiceMessage() { + messageComposerViewModel.handle( + MessageComposerAction.EndRecordingVoiceMessage(isCancelled = false, rootThreadEventId = getRootThreadEventId()) + ) + updateRecordingUiState(VoiceMessageRecorderView.RecordingUiState.Idle) + } + + override fun onDeleteVoiceMessage() { + messageComposerViewModel.handle( + MessageComposerAction.EndRecordingVoiceMessage(isCancelled = true, rootThreadEventId = getRootThreadEventId()) + ) + updateRecordingUiState(VoiceMessageRecorderView.RecordingUiState.Idle) + } + + override fun onRecordingLimitReached() { + messageComposerViewModel.handle( + MessageComposerAction.PauseRecordingVoiceMessage + ) + updateRecordingUiState(VoiceMessageRecorderView.RecordingUiState.Draft) + } + + override fun onRecordingWaveformClicked() { + messageComposerViewModel.handle( + MessageComposerAction.PauseRecordingVoiceMessage + ) + updateRecordingUiState(VoiceMessageRecorderView.RecordingUiState.Draft) + } + + override fun onVoiceWaveformTouchedUp(percentage: Float, duration: Int) { + messageComposerViewModel.handle( + MessageComposerAction.VoiceWaveformTouchedUp(AudioMessagePlaybackTracker.RECORDING_ID, duration, percentage) + ) + } + + override fun onVoiceWaveformMoved(percentage: Float, duration: Int) { + messageComposerViewModel.handle( + MessageComposerAction.VoiceWaveformTouchedUp(AudioMessagePlaybackTracker.RECORDING_ID, duration, percentage) + ) + } + + private fun updateRecordingUiState(state: VoiceMessageRecorderView.RecordingUiState) { + messageComposerViewModel.handle( + MessageComposerAction.OnVoiceRecordingUiStateChanged(state) + ) + } + } + } + + /** + * Returns the root thread event if we are in a thread room, otherwise returns null. + */ + fun getRootThreadEventId(): String? = timelineArgs.threadTimelineArgs?.rootThreadEventId + +} diff --git a/vector/src/main/res/layout/fragment_composer.xml b/vector/src/main/res/layout/fragment_composer.xml new file mode 100644 index 0000000000..0f79500da9 --- /dev/null +++ b/vector/src/main/res/layout/fragment_composer.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<im.vector.app.features.home.room.detail.composer.MessageComposerView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/composerLayout" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="?android:colorBackground" + android:minHeight="56dp" + android:transitionName="composer" + android:visibility="gone" + tools:visibility="visible" /> diff --git a/vector/src/main/res/layout/fragment_timeline.xml b/vector/src/main/res/layout/fragment_timeline.xml index f8a31d3281..2078d729db 100644 --- a/vector/src/main/res/layout/fragment_timeline.xml +++ b/vector/src/main/res/layout/fragment_timeline.xml @@ -119,33 +119,26 @@ android:layout_height="wrap_content" android:inflatedId="@+id/failedMessagesWarningStub" android:layout="@layout/view_stub_failed_message_warning_layout" - app:layout_constraintBottom_toTopOf="@id/composerLayout" + app:layout_constraintBottom_toTopOf="@id/composerContainer" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" tools:layout_height="300dp" /> - <im.vector.app.features.home.room.detail.composer.MessageComposerView - android:id="@+id/composerLayout" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:background="?android:colorBackground" - android:minHeight="56dp" - android:transitionName="composer" - android:visibility="gone" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - tools:visibility="visible" /> - - <im.vector.app.features.home.room.detail.composer.voice.VoiceMessageRecorderView - android:id="@+id/voiceMessageRecorderView" + <FrameLayout + android:id="@+id/composerContainer" android:layout_width="0dp" android:layout_height="wrap_content" - android:visibility="gone" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - tools:visibility="visible" /> + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintBottom_toBottomOf="parent" /> + + <FrameLayout + android:id="@+id/voiceMessageRecorderContainer" + android:layout_width="0dp" + android:layout_height="wrap_content" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintBottom_toBottomOf="parent" /> <ViewStub android:id="@+id/inviteViewStub" @@ -163,7 +156,7 @@ android:layout_width="0dp" android:layout_height="0dp" app:barrierDirection="top" - app:constraint_referenced_ids="composerLayout,notificationAreaView,failedMessagesWarningStub" /> + app:constraint_referenced_ids="composerContainer,notificationAreaView,failedMessagesWarningStub" /> <im.vector.app.core.platform.BadgeFloatingActionButton android:id="@+id/jumpToBottomView" diff --git a/vector/src/main/res/layout/fragment_voice_recorder.xml b/vector/src/main/res/layout/fragment_voice_recorder.xml new file mode 100644 index 0000000000..787bcd25f3 --- /dev/null +++ b/vector/src/main/res/layout/fragment_voice_recorder.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<im.vector.app.features.home.room.detail.composer.voice.VoiceMessageRecorderView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/voiceMessageRecorderView" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:visibility="gone" + tools:visibility="visible" /> From 2c9526543b6b0f139770ca4d76330c5080f4a25d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= <jorgem@element.io> Date: Tue, 4 Oct 2022 12:30:56 +0200 Subject: [PATCH 126/187] Remove coordinator, fix minor issues --- .../home/room/detail/TimelineFragment.kt | 31 +++++-------------- .../detail/composer/MessageComposerAction.kt | 7 +++-- .../composer/MessageComposerFragment.kt | 30 +++++++----------- .../composer/MessageComposerViewEvents.kt | 2 ++ .../composer/MessageComposerViewModel.kt | 13 +++++--- .../composer/MessageComposerViewState.kt | 11 ++++--- .../detail/timeline/item/AbsMessageItem.kt | 8 +---- 7 files changed, 41 insertions(+), 61 deletions(-) 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 ae52d36c7e..fe4f33f37d 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 @@ -129,7 +129,6 @@ import im.vector.app.features.crypto.verification.VerificationBottomSheet import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.arguments.TimelineArgs import im.vector.app.features.home.room.detail.composer.CanSendStatus -import im.vector.app.features.home.room.detail.composer.MessageComposer import im.vector.app.features.home.room.detail.composer.MessageComposerAction import im.vector.app.features.home.room.detail.composer.MessageComposerFragment import im.vector.app.features.home.room.detail.composer.MessageComposerViewModel @@ -289,8 +288,6 @@ class TimelineFragment : private val lazyLoadedViews = RoomDetailLazyLoadedViews() - private lateinit var composer: MessageComposer - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) analyticsScreenName = MobileScreen.ScreenName.Room @@ -301,15 +298,13 @@ class TimelineFragment : } } - val composer = childFragmentManager.findFragmentById(R.id.composerContainer) as? MessageComposerFragment ?: run { - val fragment = MessageComposerFragment() - fragment.arguments = timelineArgs.toMvRxBundle() + childFragmentManager.findFragmentById(R.id.composerContainer) as? MessageComposerFragment ?: run { childFragmentManager.commitTransaction { + val fragment = MessageComposerFragment() + fragment.arguments = timelineArgs.toMvRxBundle() replace(R.id.composerContainer, fragment) } - fragment } - this.composer = composer childFragmentManager.findFragmentById(R.id.voiceMessageRecorderContainer) as? VoiceRecorderFragment ?: run { childFragmentManager.commitTransaction { @@ -970,7 +965,7 @@ class TimelineFragment : is RoomDetailPendingAction.JumpToReadReceipt -> timelineViewModel.handle(RoomDetailAction.JumpToReadReceipt(roomDetailPendingAction.userId)) is RoomDetailPendingAction.MentionUser -> - insertUserDisplayNameInTextEditor(roomDetailPendingAction.userId) + messageComposerViewModel.handle(MessageComposerAction.InsertUserDisplayName(roomDetailPendingAction.userId)) is RoomDetailPendingAction.OpenRoom -> handleOpenRoom(RoomDetailViewEvents.OpenRoom(roomDetailPendingAction.roomId, roomDetailPendingAction.closeCurrentRoom)) } @@ -1051,7 +1046,7 @@ class TimelineFragment : override fun performQuickReplyOnHolder(model: EpoxyModel<*>) { (model as? AbsMessageItem)?.attributes?.informationData?.let { val eventId = it.eventId - messageComposerViewModel.handle(MessageComposerAction.EnterReplyMode(eventId, composer.getCurrentText().toString())) + messageComposerViewModel.handle(MessageComposerAction.EnterReplyMode(eventId)) } } @@ -1573,7 +1568,7 @@ class TimelineFragment : } override fun onMemberNameClicked(informationData: MessageInformationData) { - insertUserDisplayNameInTextEditor(informationData.senderId) + messageComposerViewModel.handle(MessageComposerAction.InsertUserDisplayName(informationData.senderId)) } override fun onClickOnReactionPill(informationData: MessageInformationData, reaction: String, on: Boolean) { @@ -1776,11 +1771,11 @@ class TimelineFragment : } } is EventSharedAction.Quote -> { - messageComposerViewModel.handle(MessageComposerAction.EnterQuoteMode(action.eventId, composer.getCurrentText().toString())) + messageComposerViewModel.handle(MessageComposerAction.EnterQuoteMode(action.eventId)) } is EventSharedAction.Reply -> { if (withState(messageComposerViewModel) { it.isVoiceMessageIdle }) { - messageComposerViewModel.handle(MessageComposerAction.EnterReplyMode(action.eventId, composer.getCurrentText().toString())) + messageComposerViewModel.handle(MessageComposerAction.EnterReplyMode(action.eventId)) } else { requireActivity().toast(R.string.error_voice_message_cannot_reply_or_edit) } @@ -1881,16 +1876,6 @@ class TimelineFragment : .show() } - /** - * Insert a user displayName in the message editor. - * - * @param userId the userId. - */ - @SuppressLint("SetTextI18n") - private fun insertUserDisplayNameInTextEditor(userId: String) { - composer.insertUserDisplayNameInTextEditor(userId) - } - private fun showSnackWithMessage(message: String) { view?.showOptimizedSnackbar(message) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerAction.kt index ac39e0e915..97e6657fc2 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerAction.kt @@ -25,13 +25,14 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent sealed class MessageComposerAction : VectorViewModelAction { data class SendMessage(val text: CharSequence, val autoMarkdown: Boolean) : MessageComposerAction() data class EnterEditMode(val eventId: String) : MessageComposerAction() - data class EnterQuoteMode(val eventId: String, val text: String) : MessageComposerAction() - data class EnterReplyMode(val eventId: String, val text: String) : MessageComposerAction() - data class EnterRegularMode(val text: String, val fromSharing: Boolean) : MessageComposerAction() + data class EnterQuoteMode(val eventId: String) : MessageComposerAction() + data class EnterReplyMode(val eventId: String) : MessageComposerAction() + data class EnterRegularMode(val fromSharing: Boolean) : MessageComposerAction() data class UserIsTyping(val isTyping: Boolean) : MessageComposerAction() data class OnTextChanged(val text: CharSequence) : MessageComposerAction() data class OnEntersBackground(val composerText: String) : MessageComposerAction() data class SlashCommandConfirmed(val parsedCommand: ParsedCommand) : MessageComposerAction() + data class InsertUserDisplayName(val userId: String) : MessageComposerAction() // Voice Message data class InitializeVoiceRecorder(val attachmentData: ContentAttachmentData) : MessageComposerAction() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt index 103950b3b8..d78864d8d6 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt @@ -22,7 +22,6 @@ import android.content.res.Configuration import android.net.Uri import android.os.Build import android.os.Bundle -import android.text.Editable import android.text.Spannable import android.text.format.DateUtils import android.view.KeyEvent @@ -112,13 +111,8 @@ import reactivecircus.flowbinding.android.widget.textChanges import timber.log.Timber import javax.inject.Inject -interface MessageComposer { - fun getCurrentText(): Editable? - fun insertUserDisplayNameInTextEditor(userId: String) -} - @AndroidEntryPoint -class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), MessageComposer, AttachmentsHelper.Callback, AttachmentTypeSelectorView.Callback { +class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), AttachmentsHelper.Callback, AttachmentTypeSelectorView.Callback { companion object { private const val ircPattern = " (IRC)" @@ -196,6 +190,7 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), M } showErrorInSnackbar(it.throwable) } + is MessageComposerViewEvents.InsertUserDisplayName -> insertUserDisplayNameInTextEditor(it.userId) } } @@ -204,10 +199,10 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), M return@onEach } when (mode) { - is SendMode.Regular -> renderRegularMode(mode.text) - is SendMode.Edit -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_edit, R.string.edit, mode.text) - is SendMode.Quote -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_quote, R.string.action_quote, mode.text) - is SendMode.Reply -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_reply, R.string.reply, mode.text) + is SendMode.Regular -> renderRegularMode(mode.text.toString()) + is SendMode.Edit -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_edit, R.string.edit, mode.text.toString()) + is SendMode.Quote -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_quote, R.string.action_quote, mode.text.toString()) + is SendMode.Reply -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_reply, R.string.reply, mode.text.toString()) is SendMode.Voice -> renderVoiceMessageMode(mode.text) } } @@ -304,7 +299,7 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), M } override fun onCloseRelatedMessage() { - messageComposerViewModel.handle(MessageComposerAction.EnterRegularMode(views.composerLayout.text.toString(), false)) + messageComposerViewModel.handle(MessageComposerAction.EnterRegularMode(false)) } override fun onRichContentSelected(contentUri: Uri): Boolean { @@ -723,7 +718,8 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), M private fun handleShareData() { when (val sharedData = timelineArgs.sharedData) { is SharedData.Text -> { - messageComposerViewModel.handle(MessageComposerAction.EnterRegularMode(sharedData.text, fromSharing = true)) + messageComposerViewModel.handle(MessageComposerAction.OnTextChanged(sharedData.text)) + messageComposerViewModel.handle(MessageComposerAction.EnterRegularMode(fromSharing = true)) } is SharedData.Attachments -> { // open share edition @@ -733,17 +729,13 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), M } } - override fun getCurrentText(): Editable? { - return views.composerLayout.text - } - - override fun insertUserDisplayNameInTextEditor(userId: String) { + private fun insertUserDisplayNameInTextEditor(userId: String) { val startToCompose = views.composerLayout.text.isNullOrBlank() if (startToCompose && userId == session.myUserId) { // Empty composer, current user: start an emote - views.composerLayout.views.composerEditText.setText(Command.EMOTE.command + " ") + views.composerLayout.views.composerEditText.setText("${Command.EMOTE.command} ") views.composerLayout.views.composerEditText.setSelection(Command.EMOTE.command.length + 1) } else { val roomMember = timelineViewModel.getMember(userId) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewEvents.kt index e1f6923d21..3a949acb07 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewEvents.kt @@ -47,4 +47,6 @@ sealed class MessageComposerViewEvents : VectorViewEvents { data class ShowRoomUpgradeDialog(val newVersion: String, val isPublic: Boolean) : MessageComposerViewEvents() data class VoicePlaybackOrRecordingFailure(val throwable: Throwable) : MessageComposerViewEvents() + + data class InsertUserDisplayName(val userId: String) : MessageComposerViewEvents() } 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 f9bf244eb1..afdd01ba46 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 @@ -113,6 +113,7 @@ class MessageComposerViewModel @AssistedInject constructor( is MessageComposerAction.VoiceWaveformMovedTo -> handleVoiceWaveformMovedTo(action) is MessageComposerAction.AudioSeekBarMovedTo -> handleAudioSeekBarMovedTo(action) is MessageComposerAction.SlashCommandConfirmed -> handleSlashCommandConfirmed(action) + is MessageComposerAction.InsertUserDisplayName -> handleInsertUserDisplayName(action) } } @@ -144,7 +145,7 @@ class MessageComposerViewModel @AssistedInject constructor( } private fun handleEnterRegularMode(action: MessageComposerAction.EnterRegularMode) = setState { - copy(sendMode = SendMode.Regular(action.text, action.fromSharing)) + copy(sendMode = SendMode.Regular(currentComposerText, action.fromSharing)) } private fun handleEnterEditMode(action: MessageComposerAction.EnterEditMode) { @@ -181,13 +182,13 @@ class MessageComposerViewModel @AssistedInject constructor( private fun handleEnterQuoteMode(action: MessageComposerAction.EnterQuoteMode) { room.getTimelineEvent(action.eventId)?.let { timelineEvent -> - setState { copy(sendMode = SendMode.Quote(timelineEvent, action.text)) } + setState { copy(sendMode = SendMode.Quote(timelineEvent, currentComposerText)) } } } private fun handleEnterReplyMode(action: MessageComposerAction.EnterReplyMode) { room.getTimelineEvent(action.eventId)?.let { timelineEvent -> - setState { copy(sendMode = SendMode.Reply(timelineEvent, action.text)) } + setState { copy(sendMode = SendMode.Reply(timelineEvent, currentComposerText)) } } } @@ -875,7 +876,7 @@ class MessageComposerViewModel @AssistedInject constructor( } } } - handleEnterRegularMode(MessageComposerAction.EnterRegularMode(text = "", fromSharing = false)) + handleEnterRegularMode(MessageComposerAction.EnterRegularMode(fromSharing = false)) } private fun handlePlayOrPauseVoicePlayback(action: MessageComposerAction.PlayOrPauseVoicePlayback) { @@ -943,6 +944,10 @@ class MessageComposerViewModel @AssistedInject constructor( } } + private fun handleInsertUserDisplayName(action: MessageComposerAction.InsertUserDisplayName) { + _viewEvents.post(MessageComposerViewEvents.InsertUserDisplayName(action.userId)) + } + private fun launchSlashCommandFlowSuspendable(parsedCommand: ParsedCommand, block: suspend () -> Unit) { _viewEvents.post(MessageComposerViewEvents.SlashCommandLoading) viewModelScope.launch { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewState.kt index 5698414ab4..47a7122584 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewState.kt @@ -33,15 +33,15 @@ import kotlin.random.Random */ sealed interface SendMode { data class Regular( - val text: String, + val text: CharSequence, val fromSharing: Boolean, // This is necessary for forcing refresh on selectSubscribe private val random: Int = Random.nextInt() ) : SendMode - data class Quote(val timelineEvent: TimelineEvent, val text: String) : SendMode - data class Edit(val timelineEvent: TimelineEvent, val text: String) : SendMode - data class Reply(val timelineEvent: TimelineEvent, val text: String) : SendMode + data class Quote(val timelineEvent: TimelineEvent, val text: CharSequence) : SendMode + data class Edit(val timelineEvent: TimelineEvent, val text: CharSequence) : SendMode + data class Reply(val timelineEvent: TimelineEvent, val text: CharSequence) : SendMode data class Voice(val text: String) : SendMode } @@ -66,7 +66,8 @@ data class MessageComposerViewState( val rootThreadEventId: String? = null, val startsThread: Boolean = false, val sendMode: SendMode = SendMode.Regular("", false), - val voiceRecordingUiState: VoiceMessageRecorderView.RecordingUiState = VoiceMessageRecorderView.RecordingUiState.Idle + val voiceRecordingUiState: VoiceMessageRecorderView.RecordingUiState = VoiceMessageRecorderView.RecordingUiState.Idle, + val text: CharSequence? = null, ) : MavericksState { val isVoiceRecording = when (voiceRecordingUiState) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageItem.kt index 8dba0117b5..869b7d17e2 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageItem.kt @@ -63,12 +63,6 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder>( } } - private val _memberNameClickListener = object : ClickListener { - override fun invoke(p1: View) { - attributes.avatarCallback?.onMemberNameClicked(attributes.informationData) - } - } - private val _threadClickListener = object : ClickListener { override fun invoke(p1: View) { attributes.threadCallback?.onThreadSummaryClicked(attributes.informationData.eventId, attributes.threadDetails?.isRootThread ?: false) @@ -95,7 +89,7 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder>( holder.memberNameView.isVisible = true holder.memberNameView.text = attributes.informationData.memberName holder.memberNameView.setTextColor(attributes.getMemberNameColor()) - holder.memberNameView.onClick(_memberNameClickListener) + holder.memberNameView.onClick(attributes.memberClickListener) holder.memberNameView.setOnLongClickListener(attributes.itemLongClickListener) } else { holder.memberNameView.setOnClickListener(null) From 0d97fa201ed0a0020f86ae69e78a04effbdb9096 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= <jorgem@element.io> Date: Tue, 4 Oct 2022 12:52:00 +0200 Subject: [PATCH 127/187] Try to centralise the usage of fragment args --- .../home/room/detail/RoomDetailViewState.kt | 7 ++++- .../home/room/detail/TimelineFragment.kt | 12 +++----- .../composer/MessageComposerFragment.kt | 28 +++++++++---------- .../composer/voice/VoiceRecorderFragment.kt | 6 +--- 4 files changed, 25 insertions(+), 28 deletions(-) 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 7aa7d5a877..897594ffad 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 @@ -20,6 +20,7 @@ import com.airbnb.mvrx.Async import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized import im.vector.app.features.home.room.detail.arguments.TimelineArgs +import im.vector.app.features.share.SharedData import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState @@ -77,6 +78,8 @@ data class RoomDetailViewState( val threadNotificationBadgeState: ThreadNotificationBadgeState = ThreadNotificationBadgeState(), val typingUsers: List<SenderInfo>? = null, val isSharingLiveLocation: Boolean = false, + val showKeyboardWhenPresented: Boolean = false, + val sharedData: SharedData? = null, ) : MavericksState { constructor(args: TimelineArgs) : this( @@ -86,7 +89,9 @@ data class RoomDetailViewState( // Also highlight the target event, if any highlightedEventId = args.eventId, switchToParentSpace = args.switchToParentSpace, - rootThreadEventId = args.threadTimelineArgs?.rootThreadEventId + rootThreadEventId = args.threadTimelineArgs?.rootThreadEventId, + showKeyboardWhenPresented = args.threadTimelineArgs?.showKeyboard.orFalse(), + sharedData = args.sharedData, ) fun isCallOptionAvailable(): Boolean { 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 fe4f33f37d..4def538c46 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 @@ -300,17 +300,13 @@ class TimelineFragment : childFragmentManager.findFragmentById(R.id.composerContainer) as? MessageComposerFragment ?: run { childFragmentManager.commitTransaction { - val fragment = MessageComposerFragment() - fragment.arguments = timelineArgs.toMvRxBundle() - replace(R.id.composerContainer, fragment) + replace(R.id.composerContainer, MessageComposerFragment()) } } childFragmentManager.findFragmentById(R.id.voiceMessageRecorderContainer) as? VoiceRecorderFragment ?: run { childFragmentManager.commitTransaction { - val fragment = VoiceRecorderFragment() - fragment.arguments = timelineArgs.toMvRxBundle() - replace(R.id.voiceMessageRecorderContainer, fragment) + replace(R.id.voiceMessageRecorderContainer, VoiceRecorderFragment()) } } } @@ -2010,7 +2006,7 @@ class TimelineFragment : /** * Returns true if the current room is a Thread room, false otherwise. */ - private fun isThreadTimeLine(): Boolean = timelineArgs.threadTimelineArgs?.rootThreadEventId != null + private fun isThreadTimeLine(): Boolean = withState(timelineViewModel) { it.isThreadTimeline() } /** * Returns true if the current room is a local room, false otherwise. @@ -2020,5 +2016,5 @@ class TimelineFragment : /** * Returns the root thread event if we are in a thread room, otherwise returns null. */ - fun getRootThreadEventId(): String? = timelineArgs.threadTimelineArgs?.rootThreadEventId + fun getRootThreadEventId(): String? = withState(timelineViewModel) { it.rootThreadEventId } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt index d78864d8d6..8ec5f0d313 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt @@ -40,7 +40,6 @@ import androidx.core.view.isInvisible import androidx.core.view.isVisible import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope -import com.airbnb.mvrx.args import com.airbnb.mvrx.existingViewModel import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder @@ -74,7 +73,6 @@ import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.AutoCompleter import im.vector.app.features.home.room.detail.RoomDetailAction import im.vector.app.features.home.room.detail.TimelineViewModel -import im.vector.app.features.home.room.detail.arguments.TimelineArgs import im.vector.app.features.home.room.detail.composer.voice.VoiceMessageRecorderView import im.vector.app.features.home.room.detail.timeline.action.MessageSharedActionViewModel import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider @@ -131,14 +129,14 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A @Inject lateinit var buildMeta: BuildMeta @Inject lateinit var session: Session - private val timelineArgs: TimelineArgs by args() + private val roomId: String get() = withState(timelineViewModel) { it.roomId } private val autoCompleter: AutoCompleter by lazy { - autoCompleterFactory.create(timelineArgs.roomId, isThreadTimeLine()) + autoCompleterFactory.create(roomId, isThreadTimeLine()) } private val pillsPostProcessor by lazy { - pillsPostProcessorFactory.create(timelineArgs.roomId) + pillsPostProcessorFactory.create(roomId) } private val emojiPopup: EmojiPopup by lifecycleAwareLazy { @@ -267,7 +265,8 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A views.composerLayout.views.composerEmojiButton.isVisible = vectorPreferences.showEmojiKeyboard() - if (isThreadTimeLine() && timelineArgs.threadTimelineArgs?.showKeyboard == true) { + val showKeyboard = withState(timelineViewModel) { it.showKeyboardWhenPresented } + if (isThreadTimeLine() && showKeyboard) { // Show keyboard when the user started a thread views.composerLayout.views.composerEditText.showKeyboard(andRequestFocus = true) } @@ -555,7 +554,7 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A views.composerLayout.setTextIfDifferent("") when (parsedCommand) { is ParsedCommand.DevTools -> { - navigator.openDevTools(requireContext(), timelineArgs.roomId) + navigator.openDevTools(requireContext(), roomId) } is ParsedCommand.SetMarkdown -> { showSnackWithMessage(getString(if (parsedCommand.enable) R.string.markdown_has_been_enabled else R.string.markdown_has_been_disabled)) @@ -578,12 +577,13 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A private fun handleShowRoomUpgradeDialog(roomDetailViewEvents: MessageComposerViewEvents.ShowRoomUpgradeDialog) { val tag = MigrateRoomBottomSheet::javaClass.name - MigrateRoomBottomSheet.newInstance(timelineArgs.roomId, roomDetailViewEvents.newVersion) + val roomId = withState(timelineViewModel) { it.roomId } + MigrateRoomBottomSheet.newInstance(roomId, roomDetailViewEvents.newVersion) .show(parentFragmentManager, tag) } private fun openRoomMemberProfile(userId: String) { - navigator.openRoomMemberProfile(userId = userId, roomId = timelineArgs.roomId, context = requireActivity()) + navigator.openRoomMemberProfile(userId = userId, roomId = roomId, context = requireActivity()) } private val contentAttachmentActivityResultLauncher = registerStartForActivityResult { activityResult -> @@ -598,12 +598,12 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A /** * Returns the root thread event if we are in a thread room, otherwise returns null. */ - fun getRootThreadEventId(): String? = timelineArgs.threadTimelineArgs?.rootThreadEventId + fun getRootThreadEventId(): String? = withState(timelineViewModel) { it.rootThreadEventId } /** * Returns true if the current room is a Thread room, false otherwise. */ - private fun isThreadTimeLine(): Boolean = timelineArgs.threadTimelineArgs?.rootThreadEventId != null + private fun isThreadTimeLine(): Boolean = withState(timelineViewModel) { it.isThreadTimeline() } // AttachmentsHelper.Callback @@ -656,12 +656,12 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A AttachmentTypeSelectorView.Type.GALLERY -> attachmentsHelper.selectGallery(attachmentMediaActivityResultLauncher) AttachmentTypeSelectorView.Type.CONTACT -> attachmentsHelper.selectContact(attachmentContactActivityResultLauncher) AttachmentTypeSelectorView.Type.STICKER -> timelineViewModel.handle(RoomDetailAction.SelectStickerAttachment) - AttachmentTypeSelectorView.Type.POLL -> navigator.openCreatePoll(requireContext(), timelineArgs.roomId, null, PollMode.CREATE) + AttachmentTypeSelectorView.Type.POLL -> navigator.openCreatePoll(requireContext(), roomId, null, PollMode.CREATE) AttachmentTypeSelectorView.Type.LOCATION -> { navigator .openLocationSharing( context = requireContext(), - roomId = timelineArgs.roomId, + roomId = roomId, mode = LocationSharingMode.STATIC_SHARING, initialLocationData = null, locationOwnerId = session.myUserId @@ -716,7 +716,7 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A } private fun handleShareData() { - when (val sharedData = timelineArgs.sharedData) { + when (val sharedData = withState(timelineViewModel) { it.sharedData }) { is SharedData.Text -> { messageComposerViewModel.handle(MessageComposerAction.OnTextChanged(sharedData.text)) messageComposerViewModel.handle(MessageComposerAction.EnterRegularMode(fromSharing = true)) 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 964331f924..c2c2af675c 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 @@ -21,7 +21,6 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.core.view.isVisible -import com.airbnb.mvrx.args import com.airbnb.mvrx.existingViewModel import com.airbnb.mvrx.withState import dagger.hilt.android.AndroidEntryPoint @@ -35,7 +34,6 @@ import im.vector.app.core.utils.onPermissionDeniedSnackbar import im.vector.app.core.utils.registerForPermissionsResult import im.vector.app.databinding.FragmentVoiceRecorderBinding import im.vector.app.features.home.room.detail.TimelineViewModel -import im.vector.app.features.home.room.detail.arguments.TimelineArgs import im.vector.app.features.home.room.detail.composer.MessageComposerAction import im.vector.app.features.home.room.detail.composer.MessageComposerViewEvents import im.vector.app.features.home.room.detail.composer.MessageComposerViewModel @@ -48,8 +46,6 @@ class VoiceRecorderFragment : VectorBaseFragment<FragmentVoiceRecorderBinding>() @Inject lateinit var audioMessagePlaybackTracker: AudioMessagePlaybackTracker @Inject lateinit var clock: Clock - private val timelineArgs: TimelineArgs by args() - private val timelineViewModel: TimelineViewModel by existingViewModel() private val messageComposerViewModel: MessageComposerViewModel by existingViewModel() @@ -191,6 +187,6 @@ class VoiceRecorderFragment : VectorBaseFragment<FragmentVoiceRecorderBinding>() /** * Returns the root thread event if we are in a thread room, otherwise returns null. */ - fun getRootThreadEventId(): String? = timelineArgs.threadTimelineArgs?.rootThreadEventId + fun getRootThreadEventId(): String? = withState(timelineViewModel) { it.rootThreadEventId } } From 3c2e2552ec7d8a74e9e1cc113765983deddd89ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= <jorgem@element.io> Date: Tue, 4 Oct 2022 12:57:18 +0200 Subject: [PATCH 128/187] Simplify child fragment replacement logic --- .../vector/app/features/home/room/detail/TimelineFragment.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 4def538c46..c9e8382a6b 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 @@ -71,6 +71,7 @@ import im.vector.app.core.extensions.ensureEndsLeftToRight import im.vector.app.core.extensions.filterDirectionOverrides import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.registerStartForActivityResult +import im.vector.app.core.extensions.replaceChildFragment import im.vector.app.core.extensions.setTextOrHide import im.vector.app.core.extensions.toMvRxBundle import im.vector.app.core.extensions.trackItemsVisibilityChange @@ -298,13 +299,13 @@ class TimelineFragment : } } - childFragmentManager.findFragmentById(R.id.composerContainer) as? MessageComposerFragment ?: run { + if (childFragmentManager.findFragmentById(R.id.composerContainer) == null) { childFragmentManager.commitTransaction { replace(R.id.composerContainer, MessageComposerFragment()) } } - childFragmentManager.findFragmentById(R.id.voiceMessageRecorderContainer) as? VoiceRecorderFragment ?: run { + if (childFragmentManager.findFragmentById(R.id.voiceMessageRecorderContainer) == null) { childFragmentManager.commitTransaction { replace(R.id.voiceMessageRecorderContainer, VoiceRecorderFragment()) } From 321fddf5f66f55a3fa794fd1811fbaf4898d4233 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= <jorgem@element.io> Date: Tue, 4 Oct 2022 13:24:41 +0200 Subject: [PATCH 129/187] Remove TODO --- .../composer/MessageComposerFragment.kt | 23 ++++++++----------- .../composer/voice/VoiceRecorderFragment.kt | 4 ++-- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt index 8ec5f0d313..369f8c2d74 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt @@ -437,8 +437,6 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A private fun renderVoiceMessageMode(content: String) { ContentAttachmentData.fromJsonString(content)?.let { audioAttachmentData -> - // TODO: review this behaviour -// views.voiceMessageRecorderView.isVisible = true messageComposerViewModel.handle(MessageComposerAction.InitializeVoiceRecorder(audioAttachmentData)) } } @@ -595,17 +593,6 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A } } - /** - * Returns the root thread event if we are in a thread room, otherwise returns null. - */ - fun getRootThreadEventId(): String? = withState(timelineViewModel) { it.rootThreadEventId } - - /** - * Returns true if the current room is a Thread room, false otherwise. - */ - private fun isThreadTimeLine(): Boolean = withState(timelineViewModel) { it.isThreadTimeline() } - - // AttachmentsHelper.Callback override fun onContentAttachmentsReady(attachments: List<ContentAttachmentData>) { val grouped = attachments.toGroupedContentAttachmentData() @@ -787,6 +774,16 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A return displayName } + /** + * Returns the root thread event if we are in a thread room, otherwise returns null. + */ + fun getRootThreadEventId(): String? = withState(timelineViewModel) { it.rootThreadEventId } + + /** + * Returns true if the current room is a Thread room, false otherwise. + */ + private fun isThreadTimeLine(): Boolean = withState(timelineViewModel) { it.isThreadTimeline() } + /** Set whether the keyboard should disable personalized learning. */ @RequiresApi(Build.VERSION_CODES.O) private fun EditText.setUseIncognitoKeyboard(useIncognitoKeyboard: Boolean) { 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 c2c2af675c..c5bb204ddb 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 @@ -88,8 +88,9 @@ class VoiceRecorderFragment : VectorBaseFragment<FragmentVoiceRecorderBinding>() override fun invalidate() = withState(timelineViewModel, messageComposerViewModel) { mainState, messageComposerState -> if (mainState.tombstoneEvent != null) return@withState + val hasVoiceDraft = messageComposerState.voiceRecordingUiState is VoiceMessageRecorderView.RecordingUiState.Draft with(views.root) { - isVisible = messageComposerState.isVoiceMessageRecorderVisible + isVisible = messageComposerState.isVoiceMessageRecorderVisible || hasVoiceDraft render(messageComposerState.voiceRecordingUiState) } } @@ -188,5 +189,4 @@ class VoiceRecorderFragment : VectorBaseFragment<FragmentVoiceRecorderBinding>() * Returns the root thread event if we are in a thread room, otherwise returns null. */ fun getRootThreadEventId(): String? = withState(timelineViewModel) { it.rootThreadEventId } - } From e1cad01d54ef308bff93362870faefaf6d8e9667 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= <jorgem@element.io> Date: Tue, 4 Oct 2022 13:39:14 +0200 Subject: [PATCH 130/187] Fix lint issues --- .../home/room/detail/TimelineFragment.kt | 2 - .../composer/MessageComposerFragment.kt | 68 +++++++++---------- .../composer/voice/VoiceRecorderFragment.kt | 21 +++--- 3 files changed, 41 insertions(+), 50 deletions(-) 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 c9e8382a6b..9f074a45b8 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 @@ -71,9 +71,7 @@ import im.vector.app.core.extensions.ensureEndsLeftToRight import im.vector.app.core.extensions.filterDirectionOverrides import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.registerStartForActivityResult -import im.vector.app.core.extensions.replaceChildFragment import im.vector.app.core.extensions.setTextOrHide -import im.vector.app.core.extensions.toMvRxBundle import im.vector.app.core.extensions.trackItemsVisibilityChange import im.vector.app.core.glide.GlideApp import im.vector.app.core.glide.GlideRequests diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt index 369f8c2d74..00f2c56c37 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt @@ -16,6 +16,7 @@ package im.vector.app.features.home.room.detail.composer +import android.annotation.SuppressLint import android.app.Activity import android.content.Intent import android.content.res.Configuration @@ -249,15 +250,13 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A composerEditText.setOnEditorActionListener { v, actionId, keyEvent -> val imeActionId = actionId and EditorInfo.IME_MASK_ACTION - if (EditorInfo.IME_ACTION_DONE == imeActionId || EditorInfo.IME_ACTION_SEND == imeActionId) { - sendTextMessage(v.text) - true - } + val isSendAction = EditorInfo.IME_ACTION_DONE == imeActionId || EditorInfo.IME_ACTION_SEND == imeActionId // Add external keyboard functionality (to send messages) - else if (null != keyEvent && + val externalKeyboardPressedEnter = null != keyEvent && !keyEvent.isShiftPressed && keyEvent.keyCode == KeyEvent.KEYCODE_ENTER && - resources.configuration.keyboard != Configuration.KEYBOARD_NOKEYS) { + resources.configuration.keyboard != Configuration.KEYBOARD_NOKEYS + if (isSendAction || externalKeyboardPressedEnter) { sendTextMessage(v.text) true } else false @@ -716,6 +715,7 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A } } + @SuppressLint("SetTextI18n") private fun insertUserDisplayNameInTextEditor(userId: String) { val startToCompose = views.composerLayout.text.isNullOrBlank() @@ -726,36 +726,32 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A views.composerLayout.views.composerEditText.setSelection(Command.EMOTE.command.length + 1) } else { val roomMember = timelineViewModel.getMember(userId) - // TODO move logic outside of fragment - sanitizeDisplayName(roomMember?.displayName ?: userId) - .let { displayName -> - buildSpannedString { - append(displayName) - setSpan( - PillImageSpan( - glideRequests, - avatarRenderer, - requireContext(), - MatrixItem.UserItem(userId, displayName, roomMember?.avatarUrl) - ) - .also { it.bind(views.composerLayout.views.composerEditText) }, - 0, - displayName.length, - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE - ) - append(if (startToCompose) ": " else " ") - }.let { pill -> - if (startToCompose) { - if (displayName.startsWith("/")) { - // Ensure displayName will not be interpreted as a Slash command - views.composerLayout.views.composerEditText.append("\\") - } - views.composerLayout.views.composerEditText.append(pill) - } else { - views.composerLayout.views.composerEditText.text?.insert(views.composerLayout.views.composerEditText.selectionStart, pill) - } - } - } + val displayName = sanitizeDisplayName(roomMember?.displayName ?: userId) + val pill = buildSpannedString { + append(displayName) + setSpan( + PillImageSpan( + glideRequests, + avatarRenderer, + requireContext(), + MatrixItem.UserItem(userId, displayName, roomMember?.avatarUrl) + ) + .also { it.bind(views.composerLayout.views.composerEditText) }, + 0, + displayName.length, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ) + append(if (startToCompose) ": " else " ") + } + if (startToCompose) { + if (displayName.startsWith("/")) { + // Ensure displayName will not be interpreted as a Slash command + views.composerLayout.views.composerEditText.append("\\") + } + views.composerLayout.views.composerEditText.append(pill) + } else { + views.composerLayout.views.composerEditText.text?.insert(views.composerLayout.views.composerEditText.selectionStart, pill) + } } focusComposerAndShowKeyboard() } 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 c5bb204ddb..c45410a5cc 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 @@ -151,19 +151,9 @@ class VoiceRecorderFragment : VectorBaseFragment<FragmentVoiceRecorderBinding>() updateRecordingUiState(VoiceMessageRecorderView.RecordingUiState.Idle) } - override fun onRecordingLimitReached() { - messageComposerViewModel.handle( - MessageComposerAction.PauseRecordingVoiceMessage - ) - updateRecordingUiState(VoiceMessageRecorderView.RecordingUiState.Draft) - } + override fun onRecordingLimitReached() = pauseRecording() - override fun onRecordingWaveformClicked() { - messageComposerViewModel.handle( - MessageComposerAction.PauseRecordingVoiceMessage - ) - updateRecordingUiState(VoiceMessageRecorderView.RecordingUiState.Draft) - } + override fun onRecordingWaveformClicked() = pauseRecording() override fun onVoiceWaveformTouchedUp(percentage: Float, duration: Int) { messageComposerViewModel.handle( @@ -182,6 +172,13 @@ class VoiceRecorderFragment : VectorBaseFragment<FragmentVoiceRecorderBinding>() MessageComposerAction.OnVoiceRecordingUiStateChanged(state) ) } + + private fun pauseRecording() { + messageComposerViewModel.handle( + MessageComposerAction.PauseRecordingVoiceMessage + ) + updateRecordingUiState(VoiceMessageRecorderView.RecordingUiState.Draft) + } } } From e6a2d50b92982358e1696dfdddc6a4c1b82fcbe7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= <jorgem@element.io> Date: Wed, 5 Oct 2022 12:55:46 +0200 Subject: [PATCH 131/187] Add changelog, address review comments. --- changelog.d/7285.misc | 1 + .../home/room/detail/composer/MessageComposerFragment.kt | 6 +++--- .../room/detail/composer/voice/VoiceRecorderFragment.kt | 6 +++--- 3 files changed, 7 insertions(+), 6 deletions(-) create mode 100644 changelog.d/7285.misc diff --git a/changelog.d/7285.misc b/changelog.d/7285.misc new file mode 100644 index 0000000000..ce94383146 --- /dev/null +++ b/changelog.d/7285.misc @@ -0,0 +1 @@ +Refactor TimelineFragment, split it into MessageComposerFragment and VoiceRecorderFragment. diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt index 00f2c56c37..21a87f092f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt @@ -41,7 +41,7 @@ import androidx.core.view.isInvisible import androidx.core.view.isVisible import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope -import com.airbnb.mvrx.existingViewModel +import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.vanniktech.emoji.EmojiPopup @@ -156,8 +156,8 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A private lateinit var attachmentsHelper: AttachmentsHelper private lateinit var attachmentTypeSelector: AttachmentTypeSelectorView - private val timelineViewModel: TimelineViewModel by existingViewModel() - private val messageComposerViewModel: MessageComposerViewModel by existingViewModel() + private val timelineViewModel: TimelineViewModel by activityViewModel() + private val messageComposerViewModel: MessageComposerViewModel by activityViewModel() private lateinit var sharedActionViewModel: MessageSharedActionViewModel override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentComposerBinding { 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 c45410a5cc..ef253f87a6 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 @@ -21,7 +21,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.core.view.isVisible -import com.airbnb.mvrx.existingViewModel +import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.withState import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R @@ -46,8 +46,8 @@ class VoiceRecorderFragment : VectorBaseFragment<FragmentVoiceRecorderBinding>() @Inject lateinit var audioMessagePlaybackTracker: AudioMessagePlaybackTracker @Inject lateinit var clock: Clock - private val timelineViewModel: TimelineViewModel by existingViewModel() - private val messageComposerViewModel: MessageComposerViewModel by existingViewModel() + private val timelineViewModel: TimelineViewModel by activityViewModel() + private val messageComposerViewModel: MessageComposerViewModel by activityViewModel() private val permissionVoiceMessageLauncher = registerForPermissionsResult { allGranted, deniedPermanently -> if (allGranted) { From aad2eed39617c39848cca92a0d1398d33513f5d5 Mon Sep 17 00:00:00 2001 From: Amit Kumar <mr.doc10jl96@gmail.com> Date: Wed, 5 Oct 2022 16:49:14 +0530 Subject: [PATCH 132/187] Add initial Sentry setup for crashes and perf tracking (#7141) * Add initial Sentry setup for crashes and perf tracking * Fix failing analytics tests * Reformat code to fix style issue * Close sentry when user signs out * Add initial unit tests for Sentry * Remove unused import * Exclude amitkma from signoff requirements for PRs --- changelog.d/7076.misc | 1 + dependencies.gradle | 5 ++ dependencies_groups.gradle | 1 + tools/danger/dangerfile.js | 1 + .../java/im/vector/app/config/Analytics.kt | 14 +++++- .../main/java/im/vector/app/config/Config.kt | 10 ++-- vector/build.gradle | 1 + vector/src/main/AndroidManifest.xml | 3 ++ .../vector/app/core/di/ConfigurationModule.kt | 8 +-- .../app/features/analytics/AnalyticsConfig.kt | 2 + .../analytics/impl/DefaultVectorAnalytics.kt | 14 ++++++ .../features/analytics/impl/SentryFactory.kt | 50 +++++++++++++++++++ .../impl/DefaultVectorAnalyticsTest.kt | 15 ++++-- .../app/test/fakes/FakeSentryFactory.kt | 44 ++++++++++++++++ .../test/fixtures/AnalyticsConfigFixture.kt | 6 ++- 15 files changed, 162 insertions(+), 13 deletions(-) create mode 100644 changelog.d/7076.misc create mode 100644 vector/src/main/java/im/vector/app/features/analytics/impl/SentryFactory.kt create mode 100644 vector/src/test/java/im/vector/app/test/fakes/FakeSentryFactory.kt diff --git a/changelog.d/7076.misc b/changelog.d/7076.misc new file mode 100644 index 0000000000..009b24b149 --- /dev/null +++ b/changelog.d/7076.misc @@ -0,0 +1 @@ +Add basic integration of Sentry to capture errors and crashes if user has given consent. diff --git a/dependencies.gradle b/dependencies.gradle index 610f8b97aa..3bf3ab746d 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -29,6 +29,8 @@ def jjwt = "0.11.5" // the whole commit which set version 0.16.0-SNAPSHOT def vanniktechEmoji = "0.16.0-SNAPSHOT" +def sentry = "6.4.1" + def fragment = "1.5.3" // Testing @@ -165,6 +167,9 @@ ext.libs = [ apache : [ 'commonsImaging' : "org.apache.sanselan:sanselan:0.97-incubator" ], + sentry: [ + 'sentryAndroid' : "io.sentry:sentry-android:$sentry" + ], tests : [ 'kluent' : "org.amshove.kluent:kluent-android:1.68", 'timberJunitRule' : "net.lachlanmckee:timber-junit-rule:1.0.1", diff --git a/dependencies_groups.gradle b/dependencies_groups.gradle index 149a1fbac5..cdab6172d1 100644 --- a/dependencies_groups.gradle +++ b/dependencies_groups.gradle @@ -148,6 +148,7 @@ ext.groups = [ 'io.opencensus', 'io.reactivex.rxjava2', 'io.realm', + 'io.sentry', 'it.unimi.dsi', 'jakarta.activation', 'jakarta.xml.bind', diff --git a/tools/danger/dangerfile.js b/tools/danger/dangerfile.js index 6314ec8f68..1a36474470 100644 --- a/tools/danger/dangerfile.js +++ b/tools/danger/dangerfile.js @@ -70,6 +70,7 @@ const signOff = "Signed-off-by:" // Please add new names following the alphabetical order. const allowList = [ + "amitkma", "aringenbach", "BillCarsonFr", "bmarty", diff --git a/vector-config/src/main/java/im/vector/app/config/Analytics.kt b/vector-config/src/main/java/im/vector/app/config/Analytics.kt index 7fdc78dc8a..d944a84f94 100644 --- a/vector-config/src/main/java/im/vector/app/config/Analytics.kt +++ b/vector-config/src/main/java/im/vector/app/config/Analytics.kt @@ -27,9 +27,9 @@ sealed interface Analytics { object Disabled : Analytics /** - * Analytics integration via PostHog. + * Analytics integration via PostHog and Sentry. */ - data class PostHog( + data class Enabled( /** * The PostHog instance url. */ @@ -44,5 +44,15 @@ sealed interface Analytics { * A URL to more information about the analytics collection. */ val policyLink: String, + + /** + * The Sentry DSN url. + */ + val sentryDSN: String, + + /** + * Environment for Sentry. + */ + val sentryEnvironment: String ) : Analytics } diff --git a/vector-config/src/main/java/im/vector/app/config/Config.kt b/vector-config/src/main/java/im/vector/app/config/Config.kt index f660799d06..c91987dbfd 100644 --- a/vector-config/src/main/java/im/vector/app/config/Config.kt +++ b/vector-config/src/main/java/im/vector/app/config/Config.kt @@ -68,25 +68,29 @@ object Config { * The analytics configuration to use for the Debug build type. * Can be disabled by providing Analytics.Disabled */ - val DEBUG_ANALYTICS_CONFIG = Analytics.PostHog( + val DEBUG_ANALYTICS_CONFIG = Analytics.Enabled( postHogHost = "https://posthog.element.dev", postHogApiKey = "phc_VtA1L35nw3aeAtHIx1ayrGdzGkss7k1xINeXcoIQzXN", policyLink = "https://element.io/cookie-policy", + sentryDSN = "https://f6acc9cfc2024641b28c87ad95e73e66@sentry.tools.element.io/49", + sentryEnvironment = "DEBUG" ) /** * The analytics configuration to use for the Release build type. * Can be disabled by providing Analytics.Disabled */ - val RELEASE_ANALYTICS_CONFIG = Analytics.PostHog( + val RELEASE_ANALYTICS_CONFIG = Analytics.Enabled( postHogHost = "https://posthog.hss.element.io", postHogApiKey = "phc_Jzsm6DTm6V2705zeU5dcNvQDlonOR68XvX2sh1sEOHO", policyLink = "https://element.io/cookie-policy", + sentryDSN = "https://f6acc9cfc2024641b28c87ad95e73e66@sentry.tools.element.io/49", + sentryEnvironment = "RELEASE" ) /** * The analytics configuration to use for the Nightly build type. * Can be disabled by providing Analytics.Disabled */ - val NIGHTLY_ANALYTICS_CONFIG = RELEASE_ANALYTICS_CONFIG + val NIGHTLY_ANALYTICS_CONFIG = RELEASE_ANALYTICS_CONFIG.copy(sentryEnvironment = "NIGHTLY") } diff --git a/vector/build.gradle b/vector/build.gradle index e10d2a3436..0ddee2428a 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -231,6 +231,7 @@ dependencies { implementation('com.posthog.android:posthog:1.1.2') { exclude group: 'com.android.support', module: 'support-annotations' } + implementation libs.sentry.sentryAndroid // UnifiedPush implementation 'com.github.UnifiedPush:android-connector:2.1.0' diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index dbc6458713..f079d3429e 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -69,6 +69,9 @@ <application android:supportsRtl="true"> + <!-- Sentry auto-initialization disable --> + <meta-data android:name="io.sentry.auto-init" android:value="false" /> + <!-- No limit for screen ratio: avoid black strips --> <meta-data android:name="android.max_aspect" diff --git a/vector/src/main/java/im/vector/app/core/di/ConfigurationModule.kt b/vector/src/main/java/im/vector/app/core/di/ConfigurationModule.kt index a75b3fa46b..caa38d20d9 100644 --- a/vector/src/main/java/im/vector/app/core/di/ConfigurationModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/ConfigurationModule.kt @@ -44,12 +44,14 @@ object ConfigurationModule { else -> throw IllegalStateException("Unhandled build type: ${BuildConfig.BUILD_TYPE}") } return when (config) { - Analytics.Disabled -> AnalyticsConfig(isEnabled = false, "", "", "") - is Analytics.PostHog -> AnalyticsConfig( + Analytics.Disabled -> AnalyticsConfig(isEnabled = false, "", "", "", "", "") + is Analytics.Enabled -> AnalyticsConfig( isEnabled = true, postHogHost = config.postHogHost, postHogApiKey = config.postHogApiKey, - policyLink = config.policyLink + policyLink = config.policyLink, + sentryDSN = config.sentryDSN, + sentryEnvironment = config.sentryEnvironment ) } } diff --git a/vector/src/main/java/im/vector/app/features/analytics/AnalyticsConfig.kt b/vector/src/main/java/im/vector/app/features/analytics/AnalyticsConfig.kt index bffba6fa9c..cc3eed306d 100644 --- a/vector/src/main/java/im/vector/app/features/analytics/AnalyticsConfig.kt +++ b/vector/src/main/java/im/vector/app/features/analytics/AnalyticsConfig.kt @@ -21,4 +21,6 @@ data class AnalyticsConfig( val postHogHost: String, val postHogApiKey: String, val policyLink: String, + val sentryDSN: String, + val sentryEnvironment: String ) 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 be847dcb7f..553d699d86 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 @@ -41,6 +41,7 @@ private val IGNORED_OPTIONS: Options? = null @Singleton class DefaultVectorAnalytics @Inject constructor( postHogFactory: PostHogFactory, + private val sentryFactory: SentryFactory, analyticsConfig: AnalyticsConfig, private val analyticsStore: AnalyticsStore, private val lateInitUserPropertiesFactory: LateInitUserPropertiesFactory, @@ -94,6 +95,9 @@ class DefaultVectorAnalytics @Inject constructor( override suspend fun onSignOut() { // reset the analyticsId setAnalyticsId("") + + // Close Sentry SDK. + sentryFactory.stopSentry() } private fun observeAnalyticsId() { @@ -123,10 +127,20 @@ class DefaultVectorAnalytics @Inject constructor( Timber.tag(analyticsTag.value).d("User consent updated to $consent") userConsent = consent optOutPostHog() + initOrStopSentry() } .launchIn(globalScope) } + private fun initOrStopSentry() { + userConsent?.let { + when (it) { + true -> sentryFactory.initSentry() + false -> sentryFactory.stopSentry() + } + } + } + private fun optOutPostHog() { userConsent?.let { posthog?.optOut(!it) } } diff --git a/vector/src/main/java/im/vector/app/features/analytics/impl/SentryFactory.kt b/vector/src/main/java/im/vector/app/features/analytics/impl/SentryFactory.kt new file mode 100644 index 0000000000..a000f2a77a --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/analytics/impl/SentryFactory.kt @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.analytics.impl + +import android.content.Context +import im.vector.app.features.analytics.AnalyticsConfig +import im.vector.app.features.analytics.log.analyticsTag +import io.sentry.Sentry +import io.sentry.SentryOptions +import io.sentry.android.core.SentryAndroid +import timber.log.Timber +import javax.inject.Inject + +class SentryFactory @Inject constructor( + private val context: Context, + private val analyticsConfig: AnalyticsConfig, +) { + + fun initSentry() { + Timber.tag(analyticsTag.value).d("Initializing Sentry") + if (Sentry.isEnabled()) return + SentryAndroid.init(context) { options -> + options.dsn = analyticsConfig.sentryDSN + options.beforeSend = SentryOptions.BeforeSendCallback { event, _ -> event } + options.tracesSampleRate = 1.0 + options.isEnableUserInteractionTracing = true + options.environment = analyticsConfig.sentryEnvironment + options.diagnosticLevel + } + } + + fun stopSentry() { + Timber.tag(analyticsTag.value).d("Stopping Sentry") + Sentry.close() + } +} 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 543d517db1..be53f1b908 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 @@ -23,6 +23,7 @@ import im.vector.app.test.fakes.FakeAnalyticsStore import im.vector.app.test.fakes.FakeLateInitUserPropertiesFactory import im.vector.app.test.fakes.FakePostHog import im.vector.app.test.fakes.FakePostHogFactory +import im.vector.app.test.fakes.FakeSentryFactory import im.vector.app.test.fixtures.AnalyticsConfigFixture.anAnalyticsConfig import im.vector.app.test.fixtures.aUserProperties import im.vector.app.test.fixtures.aVectorAnalyticsEvent @@ -45,9 +46,11 @@ class DefaultVectorAnalyticsTest { private val fakePostHog = FakePostHog() private val fakeAnalyticsStore = FakeAnalyticsStore() private val fakeLateInitUserPropertiesFactory = FakeLateInitUserPropertiesFactory() + private val fakeSentryFactory = FakeSentryFactory() private val defaultVectorAnalytics = DefaultVectorAnalytics( postHogFactory = FakePostHogFactory(fakePostHog.instance).instance, + sentryFactory = fakeSentryFactory.instance, analyticsStore = fakeAnalyticsStore.instance, globalScope = CoroutineScope(Dispatchers.Unconfined), analyticsConfig = anAnalyticsConfig(isEnabled = true), @@ -67,17 +70,21 @@ class DefaultVectorAnalyticsTest { } @Test - fun `when consenting to analytics then updates posthog opt out to false`() = runTest { + fun `when consenting to analytics then updates posthog opt out to false and initialize Sentry`() = runTest { fakeAnalyticsStore.givenUserContent(consent = true) fakePostHog.verifyOptOutStatus(optedOut = false) + + fakeSentryFactory.verifySentryInit() } @Test - fun `when revoking consent to analytics then updates posthog opt out to true`() = runTest { + fun `when revoking consent to analytics then updates posthog opt out to true and closes Sentry`() = runTest { fakeAnalyticsStore.givenUserContent(consent = false) fakePostHog.verifyOptOutStatus(optedOut = true) + + fakeSentryFactory.verifySentryClose() } @Test @@ -97,12 +104,14 @@ class DefaultVectorAnalyticsTest { } @Test - fun `when signing out then resets posthog`() = runTest { + fun `when signing out then resets posthog and closes Sentry`() = runTest { fakeAnalyticsStore.allowSettingAnalyticsIdToCallBackingFlow() defaultVectorAnalytics.onSignOut() fakePostHog.verifyReset() + + fakeSentryFactory.verifySentryClose() } @Test diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeSentryFactory.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeSentryFactory.kt new file mode 100644 index 0000000000..2628f80435 --- /dev/null +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeSentryFactory.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.test.fakes + +import im.vector.app.features.analytics.impl.SentryFactory +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify + +class FakeSentryFactory { + private var isSentryEnabled = false + + val instance = mockk<SentryFactory>().also { + every { it.initSentry() } answers { + isSentryEnabled = true + } + + every { it.stopSentry() } answers { + isSentryEnabled = false + } + } + + fun verifySentryInit() { + verify { instance.initSentry() } + } + + fun verifySentryClose() { + verify { instance.stopSentry() } + } +} diff --git a/vector/src/test/java/im/vector/app/test/fixtures/AnalyticsConfigFixture.kt b/vector/src/test/java/im/vector/app/test/fixtures/AnalyticsConfigFixture.kt index ea1769ecb2..a53043774d 100644 --- a/vector/src/test/java/im/vector/app/test/fixtures/AnalyticsConfigFixture.kt +++ b/vector/src/test/java/im/vector/app/test/fixtures/AnalyticsConfigFixture.kt @@ -23,6 +23,8 @@ object AnalyticsConfigFixture { isEnabled: Boolean = false, postHogHost: String = "http://posthog.url", postHogApiKey: String = "api-key", - policyLink: String = "http://policy.link" - ) = AnalyticsConfig(isEnabled, postHogHost, postHogApiKey, policyLink) + policyLink: String = "http://policy.link", + sentryDSN: String = "http://sentry.dsn", + sentryEnvironment: String = "sentry-env" + ) = AnalyticsConfig(isEnabled, postHogHost, postHogApiKey, policyLink, sentryDSN, sentryEnvironment) } From 8c35a8cf6b648ba0c9688d1c61ca268868f78fc5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Oct 2022 12:23:36 +0000 Subject: [PATCH 133/187] Bump mavericks from 2.7.0 to 3.0.1 Bumps `mavericks` from 2.7.0 to 3.0.1. Updates `mavericks` from 2.7.0 to 3.0.1 - [Release notes](https://github.com/airbnb/mavericks/releases) - [Changelog](https://github.com/airbnb/mavericks/blob/main/CHANGELOG.md) - [Commits](https://github.com/airbnb/mavericks/compare/2.7.0...v3.0.1) Updates `mavericks-testing` from 2.7.0 to 3.0.1 - [Release notes](https://github.com/airbnb/mavericks/releases) - [Changelog](https://github.com/airbnb/mavericks/blob/main/CHANGELOG.md) - [Commits](https://github.com/airbnb/mavericks/compare/2.7.0...v3.0.1) --- updated-dependencies: - dependency-name: com.airbnb.android:mavericks dependency-type: direct:production update-type: version-update:semver-major - dependency-name: com.airbnb.android:mavericks-testing dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index 3bf3ab746d..81a02d7f9d 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -21,7 +21,7 @@ def lifecycle = "2.5.1" def flowBinding = "1.2.0" def flipper = "0.164.0" def epoxy = "4.6.2" -def mavericks = "2.7.0" +def mavericks = "3.0.1" def glide = "4.14.1" def bigImageViewer = "1.8.1" def jjwt = "0.11.5" From d5f542095caf83c4d3686299d1fd9c17c97249ee Mon Sep 17 00:00:00 2001 From: Vri <element@vrifox.cc> Date: Wed, 5 Oct 2022 11:07:57 +0000 Subject: [PATCH 134/187] Translated using Weblate (German) Currently translated at 100.0% (2419 of 2419 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/de/ --- .../ui-strings/src/main/res/values-de/strings.xml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) 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 a15e9f6c67..27f46160bc 100644 --- a/library/ui-strings/src/main/res/values-de/strings.xml +++ b/library/ui-strings/src/main/res/values-de/strings.xml @@ -1994,8 +1994,8 @@ <string name="your_public_space">Dein öffentlicher Space</string> <string name="command_description_join_space">Betrete einen Space mit der angegebenen ID</string> <string name="create_space_topic_hint">Beschreibung</string> - <string name="create_spaces_loading_message">Erzeuge Space…</string> - <string name="create_spaces_default_public_random_room_name">Irgendetwas</string> + <string name="create_spaces_loading_message">Erzeuge Space …</string> + <string name="create_spaces_default_public_random_room_name">Ohne Thema</string> <string name="create_spaces_default_public_room_name">Allgemein</string> <string name="activity_create_space_title">Einen Space erstellen</string> <string name="create_spaces_just_me">Nur für mich</string> @@ -2621,7 +2621,7 @@ <string name="invites_title">Einladungen</string> <string name="device_manager_other_sessions_description_unverified">Nicht verifiziert · Neueste Aktivität %1$s</string> <string name="device_manager_verification_status_unverified">Nicht verifizierte Sitzung</string> - <string name="device_manager_unverified_sessions_title">Nicht verifizierte Sitzung</string> + <string name="device_manager_unverified_sessions_title">Nicht verifizierte Sitzungen</string> <string name="device_manager_header_section_security_recommendations_description">Verbessere deine Kontosicherheit, indem du diese Empfehlungen beherzigst.</string> <string name="device_manager_header_section_security_recommendations_title">Sicherheitsempfehlungen</string> <plurals name="device_manager_other_sessions_description_inactive"> @@ -2669,8 +2669,8 @@ <string name="device_manager_other_sessions_no_unverified_sessions_found">Keine nicht verifizierten Sitzungen gefunden.</string> <string name="device_manager_other_sessions_no_verified_sessions_found">Keine verifizierten Sitzungen gefunden.</string> <plurals name="device_manager_other_sessions_recommendation_description_inactive"> - <item quantity="one">Erwäge, dich aus alten Sitzungen (%1$d Tag oder mehr) abzumelden, die du nicht mehr benutzt.</item> - <item quantity="other">Erwäge, dich aus alten Sitzungen (%1$d Tage oder mehr) abzumelden, die du nicht mehr benutzt.</item> + <item quantity="one">Erwäge, dich aus alten (ein Tag oder mehr), nicht mehr verwendeten Sitzungen abzumelden.</item> + <item quantity="other">Erwäge, dich aus alten (%1$d Tage oder mehr), nicht mehr verwendeten Sitzungen abzumelden.</item> </plurals> <string name="device_manager_other_sessions_recommendation_title_inactive">Inaktiv</string> <string name="device_manager_other_sessions_recommendation_description_unverified">Für besonders sichere Kommunikation verifiziere deine Sitzungen oder melde dich von ihnen ab, falls du sie nicht mehr identifizieren kannst.</string> @@ -2690,7 +2690,7 @@ <string name="device_manager_session_title">Sitzung</string> <string name="device_manager_current_session_title">Aktuelle Sitzung</string> <plurals name="device_manager_inactive_sessions_description"> - <item quantity="one">Erwäge, dich aus alten (%1$d Tag oder mehr), nicht mehr verwendeten Sitzungen abzumelden.</item> + <item quantity="one">Erwäge, dich aus alten (ein Tag oder mehr), nicht mehr verwendeten Sitzungen abzumelden.</item> <item quantity="other">Erwäge, dich aus alten (%1$d Tage oder mehr), nicht mehr verwendeten Sitzungen abzumelden.</item> </plurals> <string name="device_manager_inactive_sessions_title">Inaktive Sitzungen</string> @@ -2708,5 +2708,5 @@ <string name="device_manager_verification_status_detail_current_session_verified">Deine aktuelle Sitzung ist für sichere Kommunikation bereit.</string> <string name="device_manager_view_details">Details anzeigen</string> <string name="device_manager_verification_status_detail_other_session_unverified">Für bestmögliche Sicherheit und Zuverlässigkeit verifiziere diese Sitzungen oder melde dich von ihr ab.</string> - <string name="device_manager_other_sessions_recommendation_description_verified">Für bestmögliche Sicherheit melde dich von allen Sitzungen ab, die du nicht erkennst oder nutzt.</string> + <string name="device_manager_other_sessions_recommendation_description_verified">Für die bestmögliche Sicherheit, melde dich von allen Sitzungen ab, die du nicht erkennst oder nicht mehr benutzt.</string> </resources> \ No newline at end of file From c39e64dfc76b6b769c7c5500bee967244cb3ddcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sveinn=20=C3=AD=20Felli?= <sv1@fellsnet.is> Date: Wed, 5 Oct 2022 12:23:08 +0000 Subject: [PATCH 135/187] Translated using Weblate (Icelandic) Currently translated at 84.5% (2046 of 2419 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 | 212 +++++++++++++++++- 1 file changed, 207 insertions(+), 5 deletions(-) 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 d25d66bfba..69191e1741 100644 --- a/library/ui-strings/src/main/res/values-is/strings.xml +++ b/library/ui-strings/src/main/res/values-is/strings.xml @@ -1536,7 +1536,7 @@ <string name="external_link_confirmation_title">Yfirfarðu þennan tengil</string> <string name="secure_backup_reset_if_you_reset_all">Ef þú frumstillir allt</string> <string name="qr_code_scanned_verif_waiting_notice">Næstum því búið! Bíð eftir staðfestingu…</string> - <string name="add_a_topic_link_text">Bæta við umræðuefni</string> + <string name="add_a_topic_link_text">Bættu við umræðuefni</string> <string name="crosssigning_verify_this_session">Sannprófa þessa innskráningu</string> <string name="settings_active_sessions_signout_device">Skrá út úr þessari setu</string> <string name="verification_conclusion_ok_notice">Skilaboð við þennan notanda eru enda-í-enda dulrituð þannig að enginn annar getur lesið þau.</string> @@ -1751,7 +1751,7 @@ <string name="settings_developer_mode">Forritarahamur</string> <string name="soft_logout_clear_data_title">Hreinsa persónuleg gögn</string> <string name="login_server_matrix_org_text">Taktu þátt ókeypis ásamt milljónum annarra á stærsta almenningsþjóninum</string> - <string name="ftue_auth_use_case_skip_partial">sleppt þessari spurningu</string> + <string name="ftue_auth_use_case_skip_partial">Sleppa þessari spurningu</string> <string name="ftue_auth_carousel_encrypted_title">Örugg skilaboð.</string> <string name="settings_discovery_bad_identity_server">Gat ekki tengst við auðkennisþjón</string> <string name="keys_backup_settings_status_not_setup">Dulritunarlyklarnir þínir eru ekki öryggisafritaðir úr þessari setu.</string> @@ -1990,10 +1990,10 @@ <string name="seen_by">Séð af</string> <string name="ftue_personalize_skip_this_step">Sleppa þessu skrefi</string> <string name="ftue_personalize_submit">Vista og halda áfram</string> - <string name="ftue_personalize_complete_subtitle">Kjörstillingarnar þínar hafa verið vistaðar.</string> + <string name="ftue_personalize_complete_subtitle">Farðu hvenær sem er í stillingarnar til að breyta notandasniðinu þínu.</string> <string name="ftue_personalize_complete_title">Nú ertu tilbúin(n)!</string> <string name="ftue_personalize_lets_go">Hefjumst handa</string> - <string name="ftue_profile_picture_subtitle">Þú getur breytt þessu hvenær sem er.</string> + <string name="ftue_profile_picture_subtitle">Þú getur breytt þessu hvenær sem er</string> <string name="ftue_profile_picture_title">Bættu við auðkennismynd</string> <string name="ftue_display_name_entry_footer">Þú getur breytt þessu síðar</string> <string name="ftue_display_name_entry_title">Birtingarnafn</string> @@ -2012,4 +2012,206 @@ <string name="action_try_it_out">Prófaðu það</string> <string name="action_disable">Gera óvirkt</string> <string name="initial_sync_request_title">Upphafleg samstillingarbeiðni</string> -</resources> + <string name="onboarding_new_app_layout_welcome_title">Velkomin í nýja sýn!</string> + <string name="location_share_live_view">Skoða staðsetningu í rauntíma</string> + <string name="space_explore_filter_no_result_description">Sumar niðurstöður gætu verið faldar þar sem þær eru einkamál, þá þarftu boð til að geta séð þær.</string> + <string name="space_leave_prompt_msg_as_admin">Þú ert eini stjórnandi þessa svæðis. Ef þú yfirgefur það verður enginn annar sem er með stjórn yfir því.</string> + <string name="space_leave_prompt_msg_private">Þú munt ekki geta tekið þátt aftur nema þér verði boðið aftur.</string> + <string name="space_leave_radio_button_none">Yfirgefa ekkert</string> + <string name="space_leave_radio_button_all">Yfirgefa allt</string> + <string name="space_leave_radio_buttons_title">Efni á þessu svæði</string> + <string name="room_alias_preview_not_found">Þetta samnefni er ekki aðgengilegt í augnablikinu. +\nPrófaðu aftur síðar, eða spurðu einhvern stjórnanda hvort þú hafir aðgang.</string> + <string name="command_description_leave_room">Fara af spjallrás með uppgefið auðkenni (eða fyrirliggjandi spjallrás ef þetta er núll)</string> + <string name="command_description_join_space">Taka þátt í svæði með uppgefið auðkenni</string> + <string name="settings_security_pin_code_use_biometrics_error">Gat ekki virkjað auðkenningu með lífkennum.</string> + <string name="identity_server_set_alternative_notice">Annars geturðu sett inn slóð á hvaða auðkennisþjón sem er</string> + <string name="identity_server_set_default_notice">Heimaþjónninn þinn (%1$s) stingur upp á að nota %2$s sem auðkenningarþjón fyrir þig</string> + <string name="identity_server_user_consent_not_provided">Samþykki notandans hefur ekki verið gefið.</string> + <string name="identity_server_error_no_identity_server_configured">Stilltu fyrst auðkennisþjón.</string> + <string name="identity_server_error_outdated_home_server">Þessi aðgerð er ekki möguleg. Heimaþjónninn er úreltur.</string> + <string name="user_code_info_text">Deildu þessum kóða með fólki svo viðkomandi geti skannað hann, bætt þér við og byrjað að spjalla.</string> + <string name="error_forbidden_digits_only_username">Heimaþjónn notandans samþykkir ekki notendanöfn einungis með tölustöfum.</string> + <string name="settings_security_prevent_screenshots_title">Hindra skjámyndatöku af forritinu</string> + <string name="settings_notification_configuration">Uppsetning tilkynninga</string> + <string name="error_failed_to_import_keys">Mistókst að flytja inn lykla</string> + <string name="qr_code_scanned_self_verif_notice">Næstum því búið! Sýnir hitt tækið gátmerki\?</string> + <string name="room_created_summary_no_topic_creation_text">%s svo fólk viti að um hvað málin snúist.</string> + <string name="send_your_first_msg_to_invite">Sendu fyrstu skilaboðin þín til að bjóða %s að spjalla</string> + <string name="this_is_the_beginning_of_room_no_name">Þetta er upphafið á þessu samtali.</string> + <string name="this_is_the_beginning_of_room">Þetta er upphafið á %s.</string> + <string name="room_created_summary_item">%s bjó til og stillti spjallrásina.</string> + <string name="encryption_unknown_algorithm_tile_description">Dulritunin sem notuð er í þessari spjallrás er ekki studd</string> + <string name="encryption_misconfigured">Dulritun er rangt stillt</string> + <string name="direct_room_encryption_enabled_tile_description_future">Skilaboð í þessu spjalli verða enda-í-enda dulrituð.</string> + <string name="encryption_enabled_tile_description">Skilaboð í þessari spjallrás eru enda-í-enda dulrituð. Lærðu meira um þetta og yfirfarðu notendur í notandasniðum þeirra.</string> + <string name="bootstrap_cancel_text">Ef þú hættir við núna, geturðu tapað dulrituðum skilaboðum og gögnum ef þú missir aðgang að innskráningum þínum. +\n +\nÞú getur víka sett upp örugga afritun og sýslað með dulritunarlyklana þína í stillingunum.</string> + <string name="bootstrap_crosssigning_progress_initializing">Gef út útbúna auðkennislykla</string> + <string name="bootstrap_loading_title">Set upp endurheimtu.</string> + <string name="bootstrap_dont_reuse_pwd">Ekki nota lykilorðið fyrir aðganginn þinn.</string> + <string name="message_key">Lykill skilaboða</string> + <string name="verify_new_session_was_not_me">Þetta var ekki ég</string> + <string name="settings_key_requests">Beiðnir um lykla</string> + <string name="login_default_session_public_name">${app_name} fyrir Android</string> + <string name="qr_code_scanned_by_other_notice">Næstum því búið! Sýnir %s gátmerki\?</string> + <string name="room_member_profile_failed_to_get_devices">Mistókst að ná í setur</string> + <plurals name="settings_active_sessions_count"> + <item quantity="one">%d virk seta</item> + <item quantity="other">%d virkar setur</item> + </plurals> + <string name="settings_failed_to_get_crypto_device_info">Engar dulkóðunarupplýsingar tiltækar</string> + <string name="room_settings_enable_encryption_no_permission">Þú hefur ekki heimild til að virkja dulritun á þessari spjallrás.</string> + <string name="ftue_auth_phone_confirmation_subtitle">Kóði var sendur til: %s</string> + <string name="ftue_auth_phone_confirmation_title">Staðfestu símanúmerið þitt</string> + <string name="ftue_auth_phone_confirmation_entry_title">Staðfestingarkóði</string> + <string name="ftue_auth_choose_server_ems_title">Viltu hýsa þinn eigin netþjón\?</string> + <string name="ftue_auth_choose_server_sign_in_subtitle">Hvert er vistfang netþjónsins þíns\?</string> + <string name="ftue_auth_choose_server_subtitle">Hvert er vistfang netþjónsins þíns\? Þetta er staður sem geymir öll gögnin þín</string> + <string name="ftue_auth_choose_server_title">Veldu netþjón fyrir þig</string> + <string name="ftue_auth_sign_in_choose_server_header">Þar sem samtölin þín eru</string> + <string name="ftue_auth_create_account_choose_server_header">Þar sem samtölin þín verða</string> + <string name="ftue_auth_create_account_password_entry_footer">Verður að vera að minnsta kosti 8 stafir</string> + <string name="ftue_auth_create_account_username_entry_footer">Aðrir geta fundið þig %s</string> + <string name="ftue_account_created_subtitle">%s aðgangur þinn hefur verið útbúinn</string> + <string name="ftue_account_created_take_me_home">Fara á forsíðuna</string> + <string name="ftue_account_created_personalize">Persónugera notandasnið</string> + <string name="ftue_auth_use_case_join_existing_server">Ætlarðu að ganga til liðs við fyrirliggjandi netþjón\?</string> + <string name="ftue_auth_use_case_skip">Ekki ennþá viss\? %s</string> + <string name="ftue_auth_use_case_title">Við hverja muntu helst spjalla\?</string> + <string name="ftue_auth_carousel_workplace_body">${app_name} er líka frábært fyrir vinnustaðinn. Heimsins öruggustu samtök treysta því.</string> + <string name="ftue_auth_carousel_encrypted_body">Enda-í-enda dulritað og ekkert símanúmer nauðsynlegt. Engar auglýsingar eða gagnasöfnun.</string> + <string name="ftue_auth_carousel_control_body">Veldu hvar á að geyma samtölin þín, sem gefur þér stjórnina og algert sjálfstæði. Tengt í gegnum Matrix.</string> + <string name="ftue_auth_carousel_secure_body">Örugg og óháð samskipti sem gefa þér færi á að ræða málin í friði rétt eins og þetta sé maður á mann í heimahúsi.</string> + <string name="ftue_auth_carousel_workplace_title">Skilaboð fyrir teymið þitt.</string> + <string name="reaction_search_type_hint">Skrifaðu stikkorð til að finna viðbrögð.</string> + <string name="a11y_open_spaces">Opna svæðalista</string> + <string name="room_preview_no_preview_join">Ekki er hægt að forskoða þessa spjallrás. Viltu taka þátt í henni\?</string> + <string name="room_preview_not_found">Þessi spjallrás er ekki aðgengileg í augnablikinu. +\nPrófaðu aftur síðar, eða spurðu einhvern stjórnanda hvort þú hafir aðgang.</string> + <string name="malformed_message">Rangt sniðinn atburður, get ekki birt hann</string> + <string name="event_redacted_by_user_reason">Atburði eytt af notanda</string> + <string name="keys_backup_banner_update_line1">Nýjir lyklar fyrir örugg skilaboð</string> + <string name="analytics_opt_in_content">Hjálpaðu okkur við að greina vandamál og bæta ${app_name} með því að deila nafnlausum gögnum varðandi notkun. Til að skilja hvernig fólk notar saman mörg tæki, munum við útbúa tilviljanakennt auðkenni, sem tækin þín deila. +\n +\nÞú getur lesið alla skilmála okkar %s.</string> + <string name="settings_autoplay_animated_images_title">Spila hreyfimyndir sjálfvirkt</string> + <string name="settings_troubleshoot_test_endpoint_registration_failed">Mistókst að skrá endapunkt á heimaþjóninn: +\n%1$s</string> + <string name="settings_troubleshoot_test_endpoint_registration_success">Það tókst að skrá endapunkt á heimaþjóninn.</string> + <string name="settings_troubleshoot_test_endpoint_registration_title">Skráning endapunkts</string> + <plurals name="search_space_multiple_parents"> + <item quantity="one">%1$s og %2$d í viðbót</item> + <item quantity="other">%1$s og %2$d í viðbót</item> + </plurals> + <string name="space_settings_permissions_subtitle">Skoða og uppfæra hlutverk sem krafist er til að breyta ýmsum þáttum svæðisins.</string> + <string name="room_settings_permissions_subtitle">Skoða og uppfæra hlutverk sem krafist er til að breyta ýmsum þáttum spjallrásarinnar.</string> + <string name="auth_reset_password_error_unverified">Tölvupóstfang ekki staðfest, athugaðu pósthólfið þitt</string> + <string name="invites_empty_title">Ekkert nýtt.</string> + <string name="space_list_empty_title">Engin svæði ennþá.</string> + <string name="labs_enable_new_app_layout_summary">Einfaldað Element með valkvæðum flipum</string> + <string name="labs_enable_new_app_layout_title">Virkja nýja framsetningu</string> + <string name="home_layout_preferences">Kjörstillingar framsetningar</string> + <string name="change_space">Skipta um svæði</string> + <string name="all_chats">Allar spjallrásir</string> + <string name="onboarding_new_app_layout_button_try">Prófaðu það</string> + <string name="onboarding_new_app_layout_feedback_title">Gefðu umsögn</string> + <string name="device_manager_session_details_device_ip_address">IP-vistfang</string> + <string name="device_manager_session_details_session_last_activity">Síðasta virkni</string> + <string name="device_manager_session_details_session_name">Nafn á setu</string> + <string name="device_manager_session_details_title">Nánar um setuna</string> + <string name="device_manager_other_sessions_clear_filter">Hreinsa síu</string> + <string name="device_manager_other_sessions_no_inactive_sessions_found">Engar óvirkar setur fundust.</string> + <string name="device_manager_other_sessions_no_unverified_sessions_found">Engar óstaðfestar setur fundust.</string> + <string name="device_manager_other_sessions_no_verified_sessions_found">Engar staðfestar setur fundust.</string> + <string name="device_manager_other_sessions_recommendation_title_inactive">Óvirkt</string> + <string name="device_manager_other_sessions_recommendation_title_unverified">Óstaðfest</string> + <string name="device_manager_other_sessions_recommendation_title_verified">Staðfest</string> + <string name="a11y_device_manager_filter">Sía</string> + <string name="device_manager_filter_option_inactive">Óvirkt</string> + <string name="device_manager_filter_option_unverified">Óstaðfest</string> + <string name="device_manager_filter_option_verified">Staðfest</string> + <string name="device_manager_filter_option_all_sessions">Allar setur</string> + <string name="device_manager_filter_bottom_sheet_title">Sía</string> + <string name="device_manager_session_last_activity">Síðasta virkni %1$s</string> + <string name="device_manager_device_title">Tæki</string> + <string name="device_manager_session_title">Seta</string> + <string name="device_manager_current_session_title">Núverandi seta</string> + <string name="device_manager_unverified_sessions_title">Óstaðfestar setur</string> + <string name="device_manager_other_sessions_view_all">Skoða allt (%1$d)</string> + <string name="device_manager_view_details">Skoða nánar</string> + <string name="device_manager_verify_session">Sannprófa setu</string> + <string name="device_manager_verification_status_unverified">Óstaðfest seta</string> + <string name="device_manager_verification_status_verified">Staðfest seta</string> + <string name="a11y_device_manager_device_type_unknown">Óþekkt tegund tækis</string> + <string name="a11y_device_manager_device_type_desktop">Skjáborð</string> + <string name="a11y_device_manager_device_type_web">Vefur</string> + <string name="a11y_device_manager_device_type_mobile">Farsími</string> + <string name="live_location_labs_promotion_switch_title">Virkja deilingu staðsetninga</string> + <string name="settings_troubleshoot_test_current_gateway_title">Netgátt</string> + <string name="settings_troubleshoot_test_current_distributor_title">Aðferð</string> + <string name="unifiedpush_distributor_background_sync">Samstilling í bakgrunni</string> + <string name="unifiedpush_distributor_fcm_fallback">Google þjónustur</string> + <string name="live_location_share_location_item_share">Deila staðsetningu</string> + <string name="location_share_live_remaining_time">%1$s hætti</string> + <string name="poll_undisclosed_not_ended">Niðurstöður birtast einungis eftir að könnuninni hefur lokið</string> + <string name="space_explore_filter_no_result_title">Engar niðurstöður fundust</string> + <string name="a11y_open_settings">Opna stillingar</string> + <string name="bootstrap_crosssigning_save_cloud">Afritaðu hann á einkageymslu sem þú átt í tölvuskýi</string> + <string name="bootstrap_crosssigning_save_usb">Vistaðu hann á USB-lykil eða öryggisdisk</string> + <string name="bootstrap_crosssigning_print_it">Prentaðu hann og geymdu á öruggum stað</string> + <string name="bootstrap_info_text_2">Settu inn öryggisfrasa sem aðeins þú þekkir, þetta er notað til að verja leyndarmálin sem þú geymir á netþjóninum þínum.</string> + <string name="enter_account_password">Settu inn %s til að halda áfram.</string> + <string name="crosssigning_cannot_verify_this_session">Tókst ekki að sannreyna þetta tæki</string> + <string name="device_manager_sessions_other_title">Aðrar setur</string> + <string name="settings_sessions_list">Setur</string> + <string name="ftue_auth_login_username_entry">Notandanafn / tölvupóstfang / símanúmer</string> + <string name="ftue_auth_captcha_title">Ertu mannvera\?</string> + <string name="ftue_auth_password_reset_confirmation">Endurstilling lykilorðs</string> + <string name="ftue_auth_forgot_password">Gleymt lykilorð</string> + <string name="ftue_auth_email_resend_email">Senda tölvupóst aftur</string> + <string name="ftue_auth_email_verification_title">Skoðaðu tölvupóstinn þinn</string> + <string name="ftue_auth_phone_confirmation_resend_code">Endursenda kóða</string> + <string name="ftue_auth_sign_out_all_devices">Skrá út öll tæki</string> + <string name="ftue_auth_reset_password">Endurstilla lykilorð</string> + <string name="ftue_auth_new_password_title">Veldu nýtt lykilorð</string> + <string name="ftue_auth_new_password_entry_title">Nýtt lykilorð</string> + <string name="ftue_auth_reset_password_breaker_title">Athugaðu tölvupóstinn þinn.</string> + <string name="ftue_auth_phone_entry_title">Símanúmer</string> + <string name="ftue_auth_phone_title">Settu inn símanúmerið þitt</string> + <string name="ftue_auth_email_entry_title">Tölvupóstur</string> + <string name="ftue_auth_email_title">Settu inn tölvupóstfangið þitt</string> + <string name="ftue_auth_choose_server_ems_cta">Hafðu samband</string> + <string name="ftue_auth_choose_server_entry_hint">Slóð netþjóns</string> + <string name="ftue_auth_welcome_back_title">Velkomin(n) aftur!</string> + <string name="ftue_auth_create_account_edit_server_selection">Breyta</string> + <string name="ftue_auth_create_account_sso_section_header">Eða</string> + <string name="ftue_auth_create_account_title">Búa til aðganginn þinn</string> + <string name="ftue_auth_use_case_subtitle">Við munum hjálpa þér að tengjast</string> + <string name="create_room_action_go">Fara</string> + <string name="room_preview_no_preview">Þessa spjallrás er ekki hægt að forskoða</string> + <string name="updating_your_data">Uppfæri gögnin þín…</string> + <string name="room_list_filter_people">Fólk</string> + <string name="room_list_filter_favourites">Eftirlæti</string> + <string name="room_list_filter_unreads">Ólesið</string> + <string name="room_list_filter_all">Allt</string> + <string name="font_size_use_system">Nota sjálfgefnar kerfisstillingar</string> + <string name="font_size_section_manually">Velja handvirkt</string> + <string name="font_size_section_auto">Setja sjálfvirkt</string> + <string name="font_size_title">Veldu leturstærð</string> + <string name="search_space_two_parents">%1$s og %2$s</string> + <string name="invites_title">Boðsgestir</string> + <string name="home_layout_preferences_sort_name">A-Ö</string> + <string name="home_layout_preferences_sort_activity">Virkni</string> + <string name="home_layout_preferences_sort_by">Raða eftir</string> + <string name="home_layout_preferences_recents">Birta nýlegt</string> + <string name="home_layout_preferences_filters">Sýna síur</string> + <string name="action_next">Næsta</string> + <string name="time_unit_second_short">sek</string> + <string name="time_unit_minute_short">mín</string> + <string name="time_unit_hour_short">klst</string> + <string name="explore_rooms">Kanna spjallrásir</string> + <string name="create_room">Búa til spjallrás</string> + <string name="start_chat">Hefja spjall</string> +</resources> \ No newline at end of file From 4de0f0a1e8b2247350e43beda72f1f39e67be6e1 Mon Sep 17 00:00:00 2001 From: lvre <7uu3qrbvm@relay.firefox.com> Date: Mon, 3 Oct 2022 05:00:18 +0000 Subject: [PATCH 136/187] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2419 of 2419 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/pt_BR/ --- .../src/main/res/values-pt-rBR/strings.xml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/library/ui-strings/src/main/res/values-pt-rBR/strings.xml b/library/ui-strings/src/main/res/values-pt-rBR/strings.xml index 23434766b6..c055b6843f 100644 --- a/library/ui-strings/src/main/res/values-pt-rBR/strings.xml +++ b/library/ui-strings/src/main/res/values-pt-rBR/strings.xml @@ -2460,7 +2460,7 @@ <string name="threads_beta_enable_notice_message">Threads ajudam manThreads ajudam manter suas conversas em-tópico e fáceis de rastrear. %sHabilitar threads vai refrescar o app. Isto pode tomar mais tempo para algumas contas.</string> <string name="threads_beta_enable_notice_title">Threads Beta</string> <string name="action_learn_more">Saber mais</string> - <string name="action_try_it_out">Teste aí</string> + <string name="action_try_it_out">Experimentar</string> <string name="screen_sharing_notification_description">Compartilhamento de tela está em progresso</string> <string name="screen_sharing_notification_title">${app_name} Compartilhamento de Tela</string> <string name="call_stop_screen_sharing">Parar compartilhamento de tela</string> @@ -2569,7 +2569,7 @@ <string name="ftue_auth_choose_server_entry_hint">URL de servidor</string> <string name="ftue_auth_choose_server_subtitle">Qual é o endereço de seu servidor\? Isto é como uma casa para todos os seus dados</string> <string name="ftue_auth_choose_server_title">Selecionar seu servidor</string> - <string name="ftue_auth_welcome_back_title">Boas-vindas de volta!</string> + <string name="ftue_auth_welcome_back_title">Boas vindas de volta!</string> <string name="ftue_auth_create_account_edit_server_selection">Editar</string> <string name="ftue_auth_create_account_sso_section_header">Ou</string> <string name="ftue_auth_create_account_choose_server_header">Onde suas conversas vão viver</string> @@ -2633,17 +2633,17 @@ <string name="timeline_error_room_not_found">Desculpe, esta sala não tem sido encontrada. \nPor favor retente mais tarde.%s</string> <string name="invites_title">Convites</string> - <string name="onboarding_new_app_layout_button_try">Teste aí</string> + <string name="onboarding_new_app_layout_button_try">Experimentar</string> <string name="onboarding_new_app_layout_feedback_message">Toque na direita topo para ver a opção para feedback.</string> - <string name="onboarding_new_app_layout_feedback_title">Dar Feedback</string> - <string name="onboarding_new_app_layout_spaces_message">Acessar seus Espaços (direito fundo) mais rápido e fácio que jamais antes.</string> - <string name="onboarding_new_app_layout_spaces_title">Acessar Espaços</string> + <string name="onboarding_new_app_layout_feedback_title">Dê Feedback</string> + <string name="onboarding_new_app_layout_spaces_message">Acesse seus Espaços (direita fundo) mais rápido e fácil que jamais antes.</string> + <string name="onboarding_new_app_layout_spaces_title">Acesse Espaços</string> <string name="onboarding_new_app_layout_welcome_message">Para simplificar seu ${app_name}, abas são agora opcionais. Gerencie-as usando o menu direito topo.</string> - <string name="onboarding_new_app_layout_welcome_title">Boas-vindas a uma nova visão!</string> + <string name="onboarding_new_app_layout_welcome_title">Boas vindas a uma nova visão!</string> <string name="home_empty_no_unreads_message">Isto é onde suas mensagens não-lidas vão aparecer, quando você tiver algumas.</string> - <string name="home_empty_no_unreads_title">Nada a reportar.</string> + <string name="home_empty_no_unreads_title">Nada para reportar.</string> <string name="home_empty_no_rooms_message">O app de chat seguro tudo-em-um para equipes, amigas(os) e organizações. Crie um chat, ou junte-se a uma sala existe, para começar.</string> - <string name="home_empty_no_rooms_title">Boas-vindas a ${app_name}, + <string name="home_empty_no_rooms_title">Boas vindas a ${app_name}, \n%s.</string> <string name="home_empty_space_no_rooms_message">Espaços são uma nova maneira de agrupar salas e pessoas. Adicione uma sala existente, ou crie uma nova, usando o botão direito fundo.</string> <string name="home_empty_space_no_rooms_title">%s From 111f77b017b0a5461b948d18078621e3a3ae1eb1 Mon Sep 17 00:00:00 2001 From: Nui Harime <harime.nui@yandex.ru> Date: Tue, 4 Oct 2022 19:08:55 +0000 Subject: [PATCH 137/187] Translated using Weblate (Russian) Currently translated at 98.3% (2380 of 2419 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 80941dd86a..c8eee49d96 100644 --- a/library/ui-strings/src/main/res/values-ru/strings.xml +++ b/library/ui-strings/src/main/res/values-ru/strings.xml @@ -927,7 +927,7 @@ <string name="navigate_to_room_when_already_in_the_room">Вы уже просмотрели эту комнату!</string> <string name="settings_general_title">Общее</string> <string name="settings_preferences">Предпочтения</string> - <string name="settings_security_and_privacy">Безопасность и конфиденциальность</string> + <string name="settings_security_and_privacy">Безопасность</string> <string name="settings_push_rules">Правила push-уведомлений</string> <string name="push_gateway_item_app_id">app_id:</string> <string name="push_gateway_item_push_key">push_key:</string> From 010f2c458c3afc49800e9c9e8d476e8206af6bc3 Mon Sep 17 00:00:00 2001 From: Florian Renaud <florianr@element.io> Date: Wed, 5 Oct 2022 14:48:59 +0200 Subject: [PATCH 138/187] Replace deprecated MvRxTestRule with MavericksTestRule --- docs/unit_testing.md | 2 +- .../java/im/vector/app/features/MemberListViewModelTest.kt | 4 ++-- .../crypto/quads/SharedSecureStorageViewModelTest.kt | 4 ++-- .../location/live/map/LiveLocationMapViewModelTest.kt | 4 ++-- .../app/features/onboarding/OnboardingViewModelTest.kt | 4 ++-- .../pin/lockscreen/fragment/LockScreenViewModelTests.kt | 6 +++--- .../app/features/poll/create/CreatePollViewModelTest.kt | 4 ++-- .../features/settings/devices/v2/DevicesViewModelTest.kt | 4 ++-- .../devices/v2/details/SessionDetailsViewModelTest.kt | 4 ++-- .../devices/v2/overview/SessionOverviewViewModelTest.kt | 4 ++-- .../devices/v2/rename/RenameSessionViewModelTest.kt | 4 ++-- .../features/settings/font/FontScaleSettingViewModelTest.kt | 4 ++-- 12 files changed, 24 insertions(+), 24 deletions(-) diff --git a/docs/unit_testing.md b/docs/unit_testing.md index f786c9a160..95b78c7f5f 100644 --- a/docs/unit_testing.md +++ b/docs/unit_testing.md @@ -314,7 +314,7 @@ class ViewModelTest { private var initialState = ViewState.Empty @get:Rule - val mvrxTestRule = MvRxTestRule(testDispatcher = UnconfinedTestDispatcher()) + val mavericksTestRule = MavericksTestRule(testDispatcher = UnconfinedTestDispatcher()) @Test fun `when handling MyAction, then emits Loading and Content states`() { diff --git a/vector/src/test/java/im/vector/app/features/MemberListViewModelTest.kt b/vector/src/test/java/im/vector/app/features/MemberListViewModelTest.kt index 5cad1fbc39..f8d4b1e698 100644 --- a/vector/src/test/java/im/vector/app/features/MemberListViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/MemberListViewModelTest.kt @@ -18,7 +18,7 @@ package im.vector.app.features import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.lifecycle.MutableLiveData -import com.airbnb.mvrx.test.MvRxTestRule +import com.airbnb.mvrx.test.MavericksTestRule import im.vector.app.features.roomprofile.RoomProfileArgs import im.vector.app.features.roomprofile.members.RoomMemberListViewModel import im.vector.app.features.roomprofile.members.RoomMemberListViewState @@ -52,7 +52,7 @@ import org.matrix.android.sdk.api.util.Optional class MemberListViewModelTest { @get:Rule - val mvrxTestRule = MvRxTestRule() + val mavericksTestRule = MavericksTestRule() @get:Rule val instantExecutorRule = InstantTaskExecutorRule() diff --git a/vector/src/test/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModelTest.kt b/vector/src/test/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModelTest.kt index e5426c0d9d..b1f893b164 100644 --- a/vector/src/test/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModelTest.kt @@ -17,7 +17,7 @@ package im.vector.app.features.crypto.quads import com.airbnb.mvrx.Uninitialized -import com.airbnb.mvrx.test.MvRxTestRule +import com.airbnb.mvrx.test.MavericksTestRule import im.vector.app.test.fakes.FakeSession import im.vector.app.test.fakes.FakeStringProvider import im.vector.app.test.test @@ -42,7 +42,7 @@ private val KEY_INFO_WITHOUT_PASSPHRASE = KeyInfo(id = "id", content = SecretSto class SharedSecureStorageViewModelTest { @get:Rule - val mvrxTestRule = MvRxTestRule() + val mavericksTestRule = MavericksTestRule() private val stringProvider = FakeStringProvider() private val fakeSession = FakeSession() diff --git a/vector/src/test/java/im/vector/app/features/location/live/map/LiveLocationMapViewModelTest.kt b/vector/src/test/java/im/vector/app/features/location/live/map/LiveLocationMapViewModelTest.kt index fca5db14cc..fef0f55530 100644 --- a/vector/src/test/java/im/vector/app/features/location/live/map/LiveLocationMapViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/location/live/map/LiveLocationMapViewModelTest.kt @@ -16,7 +16,7 @@ package im.vector.app.features.location.live.map -import com.airbnb.mvrx.test.MvRxTestRule +import com.airbnb.mvrx.test.MavericksTestRule import im.vector.app.features.location.LocationData import im.vector.app.features.location.live.StopLiveLocationShareUseCase import im.vector.app.test.fakes.FakeLocationSharingServiceConnection @@ -37,7 +37,7 @@ private const val A_ROOM_ID = "room_id" class LiveLocationMapViewModelTest { @get:Rule - val mvRxTestRule = MvRxTestRule(testDispatcher = UnconfinedTestDispatcher()) + val mavericksTestRule = MavericksTestRule(testDispatcher = UnconfinedTestDispatcher()) private val args = LiveLocationMapViewArgs(roomId = A_ROOM_ID) diff --git a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt index 49c3f3ef23..c3f6b86cb4 100644 --- a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt @@ -18,7 +18,7 @@ package im.vector.app.features.onboarding import android.net.Uri import android.os.Build -import com.airbnb.mvrx.test.MvRxTestRule +import com.airbnb.mvrx.test.MavericksTestRule import im.vector.app.R import im.vector.app.features.login.LoginConfig import im.vector.app.features.login.LoginMode @@ -96,7 +96,7 @@ private val SSO_REGISTRATION_DESCRIPTION = AuthenticationDescription.Register(Au class OnboardingViewModelTest { @get:Rule - val mvrxTestRule = MvRxTestRule() + val mavericksTestRule = MavericksTestRule() private val fakeUri = FakeUri() private val fakeContext = FakeContext() diff --git a/vector/src/test/java/im/vector/app/features/pin/lockscreen/fragment/LockScreenViewModelTests.kt b/vector/src/test/java/im/vector/app/features/pin/lockscreen/fragment/LockScreenViewModelTests.kt index 6037d9933e..8f436444c4 100644 --- a/vector/src/test/java/im/vector/app/features/pin/lockscreen/fragment/LockScreenViewModelTests.kt +++ b/vector/src/test/java/im/vector/app/features/pin/lockscreen/fragment/LockScreenViewModelTests.kt @@ -21,7 +21,7 @@ import android.os.Build import android.security.keystore.KeyPermanentlyInvalidatedException import androidx.biometric.BiometricPrompt import androidx.fragment.app.FragmentActivity -import com.airbnb.mvrx.test.MvRxTestRule +import com.airbnb.mvrx.test.MavericksTestRule import com.airbnb.mvrx.withState import im.vector.app.features.pin.lockscreen.biometrics.BiometricAuthError import im.vector.app.features.pin.lockscreen.biometrics.BiometricHelper @@ -54,7 +54,7 @@ import org.junit.Test class LockScreenViewModelTests { @get:Rule - val mvrxTestRule = MvRxTestRule() + val mavericksTestRule = MavericksTestRule() private val pinCodeHelper = mockk<PinCodeHelper>(relaxed = true) private val biometricHelper = mockk<BiometricHelper>(relaxed = true) @@ -295,7 +295,7 @@ class LockScreenViewModelTests { test.assertEvents(LockScreenViewEvent.ShowBiometricKeyInvalidatedMessage) } - private fun createViewState( + private fun createViewState( lockScreenConfiguration: LockScreenConfiguration = createDefaultConfiguration(), canUseBiometricAuth: Boolean = false, showBiometricPromptAutomatically: Boolean = false, 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 0387fc8986..491834db5b 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 @@ -16,7 +16,7 @@ package im.vector.app.features.poll.create -import com.airbnb.mvrx.test.MvRxTestRule +import com.airbnb.mvrx.test.MavericksTestRule import im.vector.app.features.poll.PollMode import im.vector.app.test.fakes.FakeCreatePollViewStates.A_FAKE_OPTIONS import im.vector.app.test.fakes.FakeCreatePollViewStates.A_FAKE_QUESTION @@ -48,7 +48,7 @@ class CreatePollViewModelTest { private val testDispatcher = UnconfinedTestDispatcher() @get:Rule - val mvRxTestRule = MvRxTestRule( + val mavericksTestRule = MavericksTestRule( testDispatcher = testDispatcher // See https://github.com/airbnb/mavericks/issues/599 ) diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/DevicesViewModelTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/DevicesViewModelTest.kt index abf3c0ade1..c68394e7d7 100644 --- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/DevicesViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/DevicesViewModelTest.kt @@ -18,7 +18,7 @@ package im.vector.app.features.settings.devices.v2 import android.os.SystemClock import com.airbnb.mvrx.Success -import com.airbnb.mvrx.test.MvRxTestRule +import com.airbnb.mvrx.test.MavericksTestRule import im.vector.app.features.settings.devices.v2.list.DeviceType import im.vector.app.features.settings.devices.v2.verification.CheckIfCurrentSessionCanBeVerifiedUseCase import im.vector.app.features.settings.devices.v2.verification.CurrentSessionCrossSigningInfo @@ -48,7 +48,7 @@ import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel class DevicesViewModelTest { @get:Rule - val mvRxTestRule = MvRxTestRule(testDispatcher = testDispatcher) + val mavericksTestRule = MavericksTestRule(testDispatcher = testDispatcher) private val fakeActiveSessionHolder = FakeActiveSessionHolder() private val getCurrentSessionCrossSigningInfoUseCase = mockk<GetCurrentSessionCrossSigningInfoUseCase>() diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsViewModelTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsViewModelTest.kt index df0613e06b..572f39af31 100644 --- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsViewModelTest.kt @@ -17,7 +17,7 @@ package im.vector.app.features.settings.devices.v2.details import com.airbnb.mvrx.Success -import com.airbnb.mvrx.test.MvRxTestRule +import com.airbnb.mvrx.test.MavericksTestRule import im.vector.app.core.utils.CopyToClipboardUseCase import im.vector.app.features.settings.devices.v2.DeviceFullInfo import im.vector.app.features.settings.devices.v2.overview.GetDeviceFullInfoUseCase @@ -39,7 +39,7 @@ private const val A_TEXT = "text" class SessionDetailsViewModelTest { @get:Rule - val mvRxTestRule = MvRxTestRule(testDispatcher = testDispatcher) + val mavericksTestRule = MavericksTestRule(testDispatcher = testDispatcher) private val args = SessionDetailsArgs( deviceId = A_SESSION_ID diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModelTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModelTest.kt index 4f3cc66d63..3454b41ee0 100644 --- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModelTest.kt @@ -18,7 +18,7 @@ package im.vector.app.features.settings.devices.v2.overview import android.os.SystemClock import com.airbnb.mvrx.Success -import com.airbnb.mvrx.test.MvRxTestRule +import com.airbnb.mvrx.test.MavericksTestRule import im.vector.app.R import im.vector.app.features.settings.devices.v2.DeviceFullInfo import im.vector.app.features.settings.devices.v2.RefreshDevicesUseCase @@ -67,7 +67,7 @@ private const val A_PASSWORD = "password" class SessionOverviewViewModelTest { @get:Rule - val mvRxTestRule = MvRxTestRule(testDispatcher = testDispatcher) + val mavericksTestRule = MavericksTestRule(testDispatcher = testDispatcher) private val args = SessionOverviewArgs( deviceId = A_SESSION_ID_1 diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/rename/RenameSessionViewModelTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/rename/RenameSessionViewModelTest.kt index c14f2f3526..8bb6c3119d 100644 --- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/rename/RenameSessionViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/rename/RenameSessionViewModelTest.kt @@ -16,7 +16,7 @@ package im.vector.app.features.settings.devices.v2.rename -import com.airbnb.mvrx.test.MvRxTestRule +import com.airbnb.mvrx.test.MavericksTestRule import im.vector.app.features.settings.devices.v2.DeviceFullInfo import im.vector.app.features.settings.devices.v2.overview.GetDeviceFullInfoUseCase import im.vector.app.test.test @@ -36,7 +36,7 @@ private const val AN_EDITED_SESSION_NAME = "edited-session-name" class RenameSessionViewModelTest { @get:Rule - val mvRxTestRule = MvRxTestRule(testDispatcher = testDispatcher) + val mavericksTestRule = MavericksTestRule(testDispatcher = testDispatcher) private val args = RenameSessionArgs( deviceId = A_SESSION_ID diff --git a/vector/src/test/java/im/vector/app/features/settings/font/FontScaleSettingViewModelTest.kt b/vector/src/test/java/im/vector/app/features/settings/font/FontScaleSettingViewModelTest.kt index f21cc86572..96cf6a3b52 100644 --- a/vector/src/test/java/im/vector/app/features/settings/font/FontScaleSettingViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/settings/font/FontScaleSettingViewModelTest.kt @@ -16,7 +16,7 @@ package im.vector.app.features.settings.font -import com.airbnb.mvrx.test.MvRxTestRule +import com.airbnb.mvrx.test.MavericksTestRule import im.vector.app.features.settings.FontScaleValue import im.vector.app.test.fakes.FakeConfiguration import im.vector.app.test.fakes.FakeFontScalePreferences @@ -38,7 +38,7 @@ private fun aFontScaleValue(index: Int) = FontScaleValue(index, "foo", -1f, 0) class FontScaleSettingViewModelTest { @get:Rule - val mvrxTestRule = MvRxTestRule() + val mavericksTestRule = MavericksTestRule() private val fakeConfiguration = FakeConfiguration() private val fakeFontScalePreferences = FakeFontScalePreferences() From 47457b39d0ad6062a0fc8dbd5aebb184fec1ac2a Mon Sep 17 00:00:00 2001 From: Benoit Marty <benoit@matrix.org> Date: Wed, 5 Oct 2022 16:03:26 +0200 Subject: [PATCH 139/187] Fix typo --- library/ui-strings/src/main/res/values-pt-rBR/strings.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/library/ui-strings/src/main/res/values-pt-rBR/strings.xml b/library/ui-strings/src/main/res/values-pt-rBR/strings.xml index c055b6843f..108ecc7e38 100644 --- a/library/ui-strings/src/main/res/values-pt-rBR/strings.xml +++ b/library/ui-strings/src/main/res/values-pt-rBR/strings.xml @@ -2569,7 +2569,7 @@ <string name="ftue_auth_choose_server_entry_hint">URL de servidor</string> <string name="ftue_auth_choose_server_subtitle">Qual é o endereço de seu servidor\? Isto é como uma casa para todos os seus dados</string> <string name="ftue_auth_choose_server_title">Selecionar seu servidor</string> - <string name="ftue_auth_welcome_back_title">Boas vindas de volta!</string> + <string name="ftue_auth_welcome_back_title">Boas-vindas de volta!</string> <string name="ftue_auth_create_account_edit_server_selection">Editar</string> <string name="ftue_auth_create_account_sso_section_header">Ou</string> <string name="ftue_auth_create_account_choose_server_header">Onde suas conversas vão viver</string> @@ -2639,11 +2639,11 @@ <string name="onboarding_new_app_layout_spaces_message">Acesse seus Espaços (direita fundo) mais rápido e fácil que jamais antes.</string> <string name="onboarding_new_app_layout_spaces_title">Acesse Espaços</string> <string name="onboarding_new_app_layout_welcome_message">Para simplificar seu ${app_name}, abas são agora opcionais. Gerencie-as usando o menu direito topo.</string> - <string name="onboarding_new_app_layout_welcome_title">Boas vindas a uma nova visão!</string> + <string name="onboarding_new_app_layout_welcome_title">Boas-vindas a uma nova visão!</string> <string name="home_empty_no_unreads_message">Isto é onde suas mensagens não-lidas vão aparecer, quando você tiver algumas.</string> <string name="home_empty_no_unreads_title">Nada para reportar.</string> <string name="home_empty_no_rooms_message">O app de chat seguro tudo-em-um para equipes, amigas(os) e organizações. Crie um chat, ou junte-se a uma sala existe, para começar.</string> - <string name="home_empty_no_rooms_title">Boas vindas a ${app_name}, + <string name="home_empty_no_rooms_title">Boas-vindas a ${app_name}, \n%s.</string> <string name="home_empty_space_no_rooms_message">Espaços são uma nova maneira de agrupar salas e pessoas. Adicione uma sala existente, ou crie uma nova, usando o botão direito fundo.</string> <string name="home_empty_space_no_rooms_title">%s @@ -2710,4 +2710,4 @@ <string name="labs_enable_deferred_dm_title">Habilitar DMs diferidas</string> <string name="labs_enable_new_app_layout_summary">Um Element simplificado com abas opcionais</string> <string name="labs_enable_new_app_layout_title">Habilitar novo layout</string> -</resources> \ No newline at end of file +</resources> From 8934eabc999077630b0c5fea4832214af1d05aa4 Mon Sep 17 00:00:00 2001 From: Benoit Marty <benoit@matrix.org> Date: Wed, 5 Oct 2022 16:33:05 +0200 Subject: [PATCH 140/187] Remove TODO, the string `a11y_create_room` is actually used. --- library/ui-strings/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index bb28fe1151..71ccf5b234 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -1847,7 +1847,7 @@ <string name="a11y_create_menu_close">Close the create room menu…</string> <string name="a11y_create_direct_message">Create a new direct conversation</string> <string name="a11y_create_message">Create a new conversation or room</string> - <string name="a11y_create_room">Create a new room</string> <!-- TODO TO BE REMOVED --> + <string name="a11y_create_room">Create a new room</string> <string name="a11y_open_spaces">Open spaces list</string> <string name="a11y_close_keys_backup_banner">Close keys backup banner</string> <string name="a11y_jump_to_bottom">Jump to bottom</string> From c76945f9c649a4b1f2526bd1b58d05feed5f1b8f Mon Sep 17 00:00:00 2001 From: Benoit Marty <benoit@matrix.org> Date: Wed, 5 Oct 2022 16:46:15 +0200 Subject: [PATCH 141/187] Fix tests, there is a new item in the list --- .../im/vector/app/ui/robot/RoomSettingsRobot.kt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/vector-app/src/androidTest/java/im/vector/app/ui/robot/RoomSettingsRobot.kt b/vector-app/src/androidTest/java/im/vector/app/ui/robot/RoomSettingsRobot.kt index 2c57dd058d..62c34e1b66 100644 --- a/vector-app/src/androidTest/java/im/vector/app/ui/robot/RoomSettingsRobot.kt +++ b/vector-app/src/androidTest/java/im/vector/app/ui/robot/RoomSettingsRobot.kt @@ -34,18 +34,18 @@ class RoomSettingsRobot { fun crawl() { // Room settings - clickListItem(R.id.matrixProfileRecyclerView, 3) + clickListItem(R.id.matrixProfileRecyclerView, 4) navigateToRoomParameters() pressBack() // Notifications - clickListItem(R.id.matrixProfileRecyclerView, 5) + clickListItem(R.id.matrixProfileRecyclerView, 6) pressBack() assertDisplayed(R.id.roomProfileAvatarView) // People - clickListItem(R.id.matrixProfileRecyclerView, 7) + clickListItem(R.id.matrixProfileRecyclerView, 8) assertDisplayed(R.id.inviteUsersButton) navigateToRoomPeople() // Fab @@ -56,7 +56,7 @@ class RoomSettingsRobot { assertDisplayed(R.id.roomProfileAvatarView) // Uploads - clickListItem(R.id.matrixProfileRecyclerView, 9) + clickListItem(R.id.matrixProfileRecyclerView, 10) // File tab clickOn(R.string.uploads_files_title) waitUntilViewVisible(withText(R.string.uploads_media_title)) @@ -73,12 +73,12 @@ class RoomSettingsRobot { // Advanced // Room addresses - clickListItem(R.id.matrixProfileRecyclerView, 15) + clickListItem(R.id.matrixProfileRecyclerView, 16) waitUntilViewVisible(withText(R.string.room_alias_published_alias_title)) pressBack() // Room permissions - clickListItem(R.id.matrixProfileRecyclerView, 17) + clickListItem(R.id.matrixProfileRecyclerView, 18) waitUntilViewVisible(withText(R.string.room_permissions_change_room_avatar)) clickOn(R.string.room_permissions_change_room_avatar) waitUntilDialogVisible(withId(android.R.id.button2)) @@ -95,7 +95,7 @@ class RoomSettingsRobot { } private fun leaveRoom(block: DialogRobot.() -> Unit) { - clickListItem(R.id.matrixProfileRecyclerView, 13) + clickListItem(R.id.matrixProfileRecyclerView, 14) waitUntilDialogVisible(withId(android.R.id.button2)) val dialogRobot = DialogRobot() block(dialogRobot) From 94fa3343b411b6e7047a69d0bfa841948df7545f Mon Sep 17 00:00:00 2001 From: Benoit Marty <benoit@matrix.org> Date: Wed, 5 Oct 2022 17:06:58 +0200 Subject: [PATCH 142/187] Run towncrier --- CHANGES.md | 56 ++++++++++++++++++++++++++++++++++++++++ changelog.d/12.misc | 1 - changelog.d/351.feature | 1 - changelog.d/5029.bugfix | 1 - changelog.d/5712.misc | 1 - changelog.d/5798.misc | 1 - changelog.d/6215.bugfix | 1 - changelog.d/6508.misc | 1 - changelog.d/6633.feature | 1 - changelog.d/6702.bugfix | 1 - changelog.d/6723.bugfix | 1 - changelog.d/6725.bugfix | 1 - changelog.d/6906.bugfix | 1 - changelog.d/6929.misc | 1 - changelog.d/7076.misc | 1 - changelog.d/7100.wip | 1 - changelog.d/7114.wip | 1 - changelog.d/7126.doc | 1 - changelog.d/7126.misc | 1 - changelog.d/7143.wip | 1 - changelog.d/7158.wip | 1 - changelog.d/7159.misc | 1 - changelog.d/7166.misc | 1 - changelog.d/7170.wip | 1 - changelog.d/7184.bugfix | 1 - changelog.d/7190.wip | 1 - changelog.d/7193.misc | 1 - changelog.d/7198.sdk | 1 - changelog.d/7207.sdk | 1 - changelog.d/7209.sdk | 1 - changelog.d/7211.misc | 1 - changelog.d/7224.bugfix | 1 - changelog.d/7247.wip | 1 - changelog.d/7258.wip | 1 - changelog.d/7287.misc | 1 - 35 files changed, 56 insertions(+), 34 deletions(-) delete mode 100644 changelog.d/12.misc delete mode 100644 changelog.d/351.feature delete mode 100644 changelog.d/5029.bugfix delete mode 100644 changelog.d/5712.misc delete mode 100644 changelog.d/5798.misc delete mode 100644 changelog.d/6215.bugfix delete mode 100644 changelog.d/6508.misc delete mode 100644 changelog.d/6633.feature delete mode 100644 changelog.d/6702.bugfix delete mode 100644 changelog.d/6723.bugfix delete mode 100644 changelog.d/6725.bugfix delete mode 100644 changelog.d/6906.bugfix delete mode 100644 changelog.d/6929.misc delete mode 100644 changelog.d/7076.misc delete mode 100644 changelog.d/7100.wip delete mode 100644 changelog.d/7114.wip delete mode 100644 changelog.d/7126.doc delete mode 100644 changelog.d/7126.misc delete mode 100644 changelog.d/7143.wip delete mode 100644 changelog.d/7158.wip delete mode 100644 changelog.d/7159.misc delete mode 100644 changelog.d/7166.misc delete mode 100644 changelog.d/7170.wip delete mode 100644 changelog.d/7184.bugfix delete mode 100644 changelog.d/7190.wip delete mode 100644 changelog.d/7193.misc delete mode 100644 changelog.d/7198.sdk delete mode 100644 changelog.d/7207.sdk delete mode 100644 changelog.d/7209.sdk delete mode 100644 changelog.d/7211.misc delete mode 100644 changelog.d/7224.bugfix delete mode 100644 changelog.d/7247.wip delete mode 100644 changelog.d/7258.wip delete mode 100644 changelog.d/7287.misc diff --git a/CHANGES.md b/CHANGES.md index 009c2b2af5..03072a3108 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,59 @@ +Changes in Element v1.5.2 (2022-10-05) +====================================== + +Features ✨ +---------- + - Render inline images in the timeline ([#351](https://github.com/vector-im/element-android/issues/351)) + - Add privacy setting to disable personalized learning by the keyboard ([#6633](https://github.com/vector-im/element-android/issues/6633)) + +Bugfixes 🐛 +---------- + - Disable emoji keyboard not applies in reply ([#5029](https://github.com/vector-im/element-android/issues/5029)) + - Fix animated images not autoplaying sometimes if only a thumbnail was fetched from the server ([#6215](https://github.com/vector-im/element-android/issues/6215)) + - Add Warning shield when a user previously verified rotated their cross signing keys ([#6702](https://github.com/vector-im/element-android/issues/6702)) + - Can't verify user when option to send keys to verified devices only is selected ([#6723](https://github.com/vector-im/element-android/issues/6723)) + - Add option to only send to verified devices per room (web parity) ([#6725](https://github.com/vector-im/element-android/issues/6725)) + - Delete pin code key and the key used for biometrics authentication on logout ([#6906](https://github.com/vector-im/element-android/issues/6906)) + - Fix crash on previewing images to upload on Android Pie. ([#7184](https://github.com/vector-im/element-android/issues/7184)) + - Fix app restarts in loop on Android 13 on the first run of the app. ([#7224](https://github.com/vector-im/element-android/issues/7224)) + +In development 🚧 +---------------- + - [Device Management] Learn more bottom sheets ([#7100](https://github.com/vector-im/element-android/issues/7100)) + - [Device management] Verify current session ([#7114](https://github.com/vector-im/element-android/issues/7114)) + - [Device management] Verify another session ([#7143](https://github.com/vector-im/element-android/issues/7143)) + - [Device management] Rename a session ([#7158](https://github.com/vector-im/element-android/issues/7158)) + - [Device Manager] Unverified and inactive sessions list ([#7170](https://github.com/vector-im/element-android/issues/7170)) + - [Device management] Sign out a session ([#7190](https://github.com/vector-im/element-android/issues/7190)) + - [Device Manager] Parse user agents ([#7247](https://github.com/vector-im/element-android/issues/7247)) + - [Voice Broadcast] Add a feature flag with the composer action ([#7258](https://github.com/vector-im/element-android/issues/7258)) + +Improved Documentation 📚 +------------------------ + - Draft onboarding documentation of the project at `./docs/_developer_onboarding.md` ([#7126](https://github.com/vector-im/element-android/issues/7126)) + +SDK API changes ⚠️ +------------------ + - Allow the sync timeout to be configured (mainly useful for testing) ([#7198](https://github.com/vector-im/element-android/issues/7198)) + - Ports SDK instrumentation tests to use suspending functions instead of countdown latches ([#7207](https://github.com/vector-im/element-android/issues/7207)) + - [Device Manager] Extend user agent to include device information ([#7209](https://github.com/vector-im/element-android/issues/7209)) + +Other changes +------------- + - Add support for `/tableflip` command ([#12](https://github.com/vector-im/element-android/issues/12)) + - Decreases the size of rounded corners and increases the maximum width of message bubbles to help avoid unnecessary unused space on screen ([#5712](https://github.com/vector-im/element-android/issues/5712)) + - Adds screenshot testing tooling ([#5798](https://github.com/vector-im/element-android/issues/5798)) + - [AppLayout]: added tracking of new analytics events ([#6508](https://github.com/vector-im/element-android/issues/6508)) + - Target API 12 and compile with Android SDK 32. ([#6929](https://github.com/vector-im/element-android/issues/6929)) + - Add basic integration of Sentry to capture errors and crashes if user has given consent. ([#7076](https://github.com/vector-im/element-android/issues/7076)) + - Add support to `/devtools` command. ([#7126](https://github.com/vector-im/element-android/issues/7126)) + - Fix lint warning, and cleanup the code ([#7159](https://github.com/vector-im/element-android/issues/7159)) + - New App Layout is now enabled by default! Go to the Settings > Labs to toggle this ([#7166](https://github.com/vector-im/element-android/issues/7166)) + - Mutualize the pending auth handling ([#7193](https://github.com/vector-im/element-android/issues/7193)) + - CI: Prevent modification of translations by developer. ([#7211](https://github.com/vector-im/element-android/issues/7211)) + - Fix typo in strings.xml and make sure this is American English. ([#7287](https://github.com/vector-im/element-android/issues/7287)) + + Changes in Element v1.5.1 (2022-09-28) ====================================== diff --git a/changelog.d/12.misc b/changelog.d/12.misc deleted file mode 100644 index 392d7b1122..0000000000 --- a/changelog.d/12.misc +++ /dev/null @@ -1 +0,0 @@ -Add support for `/tableflip` command \ No newline at end of file diff --git a/changelog.d/351.feature b/changelog.d/351.feature deleted file mode 100644 index af86d2fb39..0000000000 --- a/changelog.d/351.feature +++ /dev/null @@ -1 +0,0 @@ -Render inline images in the timeline diff --git a/changelog.d/5029.bugfix b/changelog.d/5029.bugfix deleted file mode 100644 index 9e8bbd7b7b..0000000000 --- a/changelog.d/5029.bugfix +++ /dev/null @@ -1 +0,0 @@ -Disable emoji keyboard not applies in reply diff --git a/changelog.d/5712.misc b/changelog.d/5712.misc deleted file mode 100644 index 549306c63f..0000000000 --- a/changelog.d/5712.misc +++ /dev/null @@ -1 +0,0 @@ -Decreases the size of rounded corners and increases the maximum width of message bubbles to help avoid unnecessary unused space on screen diff --git a/changelog.d/5798.misc b/changelog.d/5798.misc deleted file mode 100644 index 40185eac0d..0000000000 --- a/changelog.d/5798.misc +++ /dev/null @@ -1 +0,0 @@ -Adds screenshot testing tooling diff --git a/changelog.d/6215.bugfix b/changelog.d/6215.bugfix deleted file mode 100644 index 5291d7d604..0000000000 --- a/changelog.d/6215.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix animated images not autoplaying sometimes if only a thumbnail was fetched from the server diff --git a/changelog.d/6508.misc b/changelog.d/6508.misc deleted file mode 100644 index 096fb750c2..0000000000 --- a/changelog.d/6508.misc +++ /dev/null @@ -1 +0,0 @@ -[AppLayout]: added tracking of new analytics events diff --git a/changelog.d/6633.feature b/changelog.d/6633.feature deleted file mode 100644 index b52e9d95bc..0000000000 --- a/changelog.d/6633.feature +++ /dev/null @@ -1 +0,0 @@ -Add privacy setting to disable personalized learning by the keyboard diff --git a/changelog.d/6702.bugfix b/changelog.d/6702.bugfix deleted file mode 100644 index a1d646cf71..0000000000 --- a/changelog.d/6702.bugfix +++ /dev/null @@ -1 +0,0 @@ -Add Warning shield when a user previously verified rotated their cross signing keys diff --git a/changelog.d/6723.bugfix b/changelog.d/6723.bugfix deleted file mode 100644 index 08b1d1fe2e..0000000000 --- a/changelog.d/6723.bugfix +++ /dev/null @@ -1 +0,0 @@ -Can't verify user when option to send keys to verified devices only is selected diff --git a/changelog.d/6725.bugfix b/changelog.d/6725.bugfix deleted file mode 100644 index f05ddbc69d..0000000000 --- a/changelog.d/6725.bugfix +++ /dev/null @@ -1 +0,0 @@ -Add option to only send to verified devices per room (web parity) diff --git a/changelog.d/6906.bugfix b/changelog.d/6906.bugfix deleted file mode 100644 index 9b6a76f5cb..0000000000 --- a/changelog.d/6906.bugfix +++ /dev/null @@ -1 +0,0 @@ -Delete pin code key and the key used for biometrics authentication on logout diff --git a/changelog.d/6929.misc b/changelog.d/6929.misc deleted file mode 100644 index d12167cfea..0000000000 --- a/changelog.d/6929.misc +++ /dev/null @@ -1 +0,0 @@ -Target API 12 and compile with Android SDK 32. diff --git a/changelog.d/7076.misc b/changelog.d/7076.misc deleted file mode 100644 index 009b24b149..0000000000 --- a/changelog.d/7076.misc +++ /dev/null @@ -1 +0,0 @@ -Add basic integration of Sentry to capture errors and crashes if user has given consent. diff --git a/changelog.d/7100.wip b/changelog.d/7100.wip deleted file mode 100644 index 47e7a6f810..0000000000 --- a/changelog.d/7100.wip +++ /dev/null @@ -1 +0,0 @@ -[Device Management] Learn more bottom sheets diff --git a/changelog.d/7114.wip b/changelog.d/7114.wip deleted file mode 100644 index 79ad705132..0000000000 --- a/changelog.d/7114.wip +++ /dev/null @@ -1 +0,0 @@ -[Device management] Verify current session diff --git a/changelog.d/7126.doc b/changelog.d/7126.doc deleted file mode 100644 index 9c69350a11..0000000000 --- a/changelog.d/7126.doc +++ /dev/null @@ -1 +0,0 @@ -Draft onboarding documentation of the project at `./docs/_developer_onboarding.md` diff --git a/changelog.d/7126.misc b/changelog.d/7126.misc deleted file mode 100644 index a79d61f819..0000000000 --- a/changelog.d/7126.misc +++ /dev/null @@ -1 +0,0 @@ -Add support to `/devtools` command. diff --git a/changelog.d/7143.wip b/changelog.d/7143.wip deleted file mode 100644 index 588f7fb255..0000000000 --- a/changelog.d/7143.wip +++ /dev/null @@ -1 +0,0 @@ -[Device management] Verify another session diff --git a/changelog.d/7158.wip b/changelog.d/7158.wip deleted file mode 100644 index 6c303281d8..0000000000 --- a/changelog.d/7158.wip +++ /dev/null @@ -1 +0,0 @@ -[Device management] Rename a session diff --git a/changelog.d/7159.misc b/changelog.d/7159.misc deleted file mode 100644 index 76f5f45c40..0000000000 --- a/changelog.d/7159.misc +++ /dev/null @@ -1 +0,0 @@ -Fix lint warning, and cleanup the code diff --git a/changelog.d/7166.misc b/changelog.d/7166.misc deleted file mode 100644 index d223208853..0000000000 --- a/changelog.d/7166.misc +++ /dev/null @@ -1 +0,0 @@ -New App Layout is now enabled by default! Go to the Settings > Labs to toggle this diff --git a/changelog.d/7170.wip b/changelog.d/7170.wip deleted file mode 100644 index f5b71a14f8..0000000000 --- a/changelog.d/7170.wip +++ /dev/null @@ -1 +0,0 @@ -[Device Manager] Unverified and inactive sessions list diff --git a/changelog.d/7184.bugfix b/changelog.d/7184.bugfix deleted file mode 100644 index 50d7beedd6..0000000000 --- a/changelog.d/7184.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix crash on previewing images to upload on Android Pie. diff --git a/changelog.d/7190.wip b/changelog.d/7190.wip deleted file mode 100644 index 3c70666d91..0000000000 --- a/changelog.d/7190.wip +++ /dev/null @@ -1 +0,0 @@ -[Device management] Sign out a session diff --git a/changelog.d/7193.misc b/changelog.d/7193.misc deleted file mode 100644 index efa0f594ae..0000000000 --- a/changelog.d/7193.misc +++ /dev/null @@ -1 +0,0 @@ -Mutualize the pending auth handling diff --git a/changelog.d/7198.sdk b/changelog.d/7198.sdk deleted file mode 100644 index 115b8d6113..0000000000 --- a/changelog.d/7198.sdk +++ /dev/null @@ -1 +0,0 @@ -Allow the sync timeout to be configured (mainly useful for testing) diff --git a/changelog.d/7207.sdk b/changelog.d/7207.sdk deleted file mode 100644 index 0bc221e9f7..0000000000 --- a/changelog.d/7207.sdk +++ /dev/null @@ -1 +0,0 @@ -Ports SDK instrumentation tests to use suspending functions instead of countdown latches diff --git a/changelog.d/7209.sdk b/changelog.d/7209.sdk deleted file mode 100644 index 6375f5e495..0000000000 --- a/changelog.d/7209.sdk +++ /dev/null @@ -1 +0,0 @@ -[Device Manager] Extend user agent to include device information diff --git a/changelog.d/7211.misc b/changelog.d/7211.misc deleted file mode 100644 index 44abd3d59d..0000000000 --- a/changelog.d/7211.misc +++ /dev/null @@ -1 +0,0 @@ - CI: Prevent modification of translations by developer. diff --git a/changelog.d/7224.bugfix b/changelog.d/7224.bugfix deleted file mode 100644 index e48925e9e6..0000000000 --- a/changelog.d/7224.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix app restarts in loop on Android 13 on the first run of the app. diff --git a/changelog.d/7247.wip b/changelog.d/7247.wip deleted file mode 100644 index 8f2a447742..0000000000 --- a/changelog.d/7247.wip +++ /dev/null @@ -1 +0,0 @@ -[Device Manager] Parse user agents diff --git a/changelog.d/7258.wip b/changelog.d/7258.wip deleted file mode 100644 index ee4c2b85f3..0000000000 --- a/changelog.d/7258.wip +++ /dev/null @@ -1 +0,0 @@ -[Voice Broadcast] Add a feature flag with the composer action diff --git a/changelog.d/7287.misc b/changelog.d/7287.misc deleted file mode 100644 index 4826bf7e9c..0000000000 --- a/changelog.d/7287.misc +++ /dev/null @@ -1 +0,0 @@ -Fix typo in strings.xml and make sure this is American English. From a6cb936ae6067e9a80d6a647c7a35980ab8fe0f5 Mon Sep 17 00:00:00 2001 From: Benoit Marty <benoit@matrix.org> Date: Wed, 5 Oct 2022 17:08:19 +0200 Subject: [PATCH 143/187] Reorder changes --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 03072a3108..d1e4834988 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,7 @@ Changes in Element v1.5.2 (2022-10-05) Features ✨ ---------- + - New App Layout is now enabled by default! Go to the Settings > Labs to toggle this ([#7166](https://github.com/vector-im/element-android/issues/7166)) - Render inline images in the timeline ([#351](https://github.com/vector-im/element-android/issues/351)) - Add privacy setting to disable personalized learning by the keyboard ([#6633](https://github.com/vector-im/element-android/issues/6633)) @@ -48,7 +49,6 @@ Other changes - Add basic integration of Sentry to capture errors and crashes if user has given consent. ([#7076](https://github.com/vector-im/element-android/issues/7076)) - Add support to `/devtools` command. ([#7126](https://github.com/vector-im/element-android/issues/7126)) - Fix lint warning, and cleanup the code ([#7159](https://github.com/vector-im/element-android/issues/7159)) - - New App Layout is now enabled by default! Go to the Settings > Labs to toggle this ([#7166](https://github.com/vector-im/element-android/issues/7166)) - Mutualize the pending auth handling ([#7193](https://github.com/vector-im/element-android/issues/7193)) - CI: Prevent modification of translations by developer. ([#7211](https://github.com/vector-im/element-android/issues/7211)) - Fix typo in strings.xml and make sure this is American English. ([#7287](https://github.com/vector-im/element-android/issues/7287)) From b49585bde3734ef05b847e93543193d074756c6a Mon Sep 17 00:00:00 2001 From: Benoit Marty <benoit@matrix.org> Date: Wed, 5 Oct 2022 17:17:08 +0200 Subject: [PATCH 144/187] Fastlane --- fastlane/metadata/android/en-US/changelogs/40105020.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/en-US/changelogs/40105020.txt diff --git a/fastlane/metadata/android/en-US/changelogs/40105020.txt b/fastlane/metadata/android/en-US/changelogs/40105020.txt new file mode 100644 index 0000000000..41795c468c --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40105020.txt @@ -0,0 +1,2 @@ +Main changes in this version: New app layout enabled by default! +Full changelog: https://github.com/vector-im/element-android/releases From 38ca3b6cdb83e8d9a3b408d3ee36b8fd7a7cbac6 Mon Sep 17 00:00:00 2001 From: Benoit Marty <benoit@matrix.org> Date: Wed, 5 Oct 2022 17:20:05 +0200 Subject: [PATCH 145/187] 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 1ed3aff057..ea2b5d6c47 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -60,7 +60,7 @@ android { // that the app's state is completely cleared between tests. testInstrumentationRunnerArguments clearPackageData: 'true' - buildConfigField "String", "SDK_VERSION", "\"1.5.2\"" + buildConfigField "String", "SDK_VERSION", "\"1.5.4\"" 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 7dcd6a648e..ad72e44e13 100644 --- a/vector-app/build.gradle +++ b/vector-app/build.gradle @@ -36,7 +36,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 = 2 +ext.versionPatch = 4 static def getGitTimestamp() { def cmd = 'git show -s --format=%ct' From 27e408fb0cafdfe18bf0d8a4cdf36bf3aae229a4 Mon Sep 17 00:00:00 2001 From: Benoit Marty <benoitm@matrix.org> Date: Wed, 5 Oct 2022 17:30:06 +0200 Subject: [PATCH 146/187] Fix bad merge conflict fix --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index 394fe92a69..ab2a5b4a6f 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -21,7 +21,7 @@ def lifecycle = "2.5.1" def flowBinding = "1.2.0" def flipper = "0.164.0" def epoxy = "5.0.0" -def mavericks = "2.7.0" +def mavericks = "3.0.1" def glide = "4.14.1" def bigImageViewer = "1.8.1" def jjwt = "0.11.5" From ef88386fc154cb85315c5095130a1e7b0e16dec5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Oct 2022 23:14:09 +0000 Subject: [PATCH 147/187] Bump sharetarget from 1.1.0 to 1.2.0 Bumps sharetarget from 1.1.0 to 1.2.0. --- updated-dependencies: - dependency-name: androidx.sharetarget:sharetarget dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> --- vector-app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector-app/build.gradle b/vector-app/build.gradle index 0e9d30b165..eb19027880 100644 --- a/vector-app/build.gradle +++ b/vector-app/build.gradle @@ -359,7 +359,7 @@ dependencies { debugImplementation project(':library:ui-styles') implementation libs.dagger.hilt implementation 'androidx.multidex:multidex:2.0.1' - implementation "androidx.sharetarget:sharetarget:1.1.0" + implementation "androidx.sharetarget:sharetarget:1.2.0" // Flipper, debug builds only debugImplementation(libs.flipper.flipper) { From 3e447dff2997c0a44f238bd3806214eb09281202 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Oct 2022 23:14:38 +0000 Subject: [PATCH 148/187] Bump sentry-android from 6.4.1 to 6.4.3 Bumps [sentry-android](https://github.com/getsentry/sentry-java) from 6.4.1 to 6.4.3. - [Release notes](https://github.com/getsentry/sentry-java/releases) - [Changelog](https://github.com/getsentry/sentry-java/blob/6.4.3/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-java/compare/6.4.1...6.4.3) --- updated-dependencies: - dependency-name: io.sentry:sentry-android dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index ab2a5b4a6f..afaa6f8c2c 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -29,7 +29,7 @@ def jjwt = "0.11.5" // the whole commit which set version 0.16.0-SNAPSHOT def vanniktechEmoji = "0.16.0-SNAPSHOT" -def sentry = "6.4.1" +def sentry = "6.4.3" def fragment = "1.5.3" From 667bbceebbe29135a12bd6796bea8ee78eceb3d0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Oct 2022 23:15:21 +0000 Subject: [PATCH 149/187] Bump exifinterface from 1.3.3 to 1.3.4 Bumps exifinterface from 1.3.3 to 1.3.4. --- updated-dependencies: - dependency-name: androidx.exifinterface:exifinterface dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index ab2a5b4a6f..80e768a5d0 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -56,7 +56,7 @@ ext.libs = [ 'biometric' : "androidx.biometric:biometric:1.1.0", 'core' : "androidx.core:core-ktx:1.8.0", 'recyclerview' : "androidx.recyclerview:recyclerview:1.2.1", - 'exifinterface' : "androidx.exifinterface:exifinterface:1.3.3", + 'exifinterface' : "androidx.exifinterface:exifinterface:1.3.4", 'fragmentKtx' : "androidx.fragment:fragment-ktx:$fragment", 'fragmentTesting' : "androidx.fragment:fragment-testing:$fragment", 'constraintLayout' : "androidx.constraintlayout:constraintlayout:2.1.4", From 4091d2731158cd0d18d101a8b23f5136f8d87ac7 Mon Sep 17 00:00:00 2001 From: Florian Renaud <florianr@element.io> Date: Thu, 15 Sep 2022 18:37:28 +0200 Subject: [PATCH 150/187] Add "io.element.voice_broadcast_info" state event --- .../session/room/model/message/MessageType.kt | 3 ++ .../voicebroadcast/VoiceBroadcastConstants.kt | 20 +++++++ .../model/MessageVoiceBroadcastInfoContent.kt | 53 +++++++++++++++++++ .../model/VoiceBroadcastState.kt | 46 ++++++++++++++++ 4 files changed, 122 insertions(+) create mode 100644 vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt create mode 100644 vector/src/main/java/im/vector/app/features/voicebroadcast/model/MessageVoiceBroadcastInfoContent.kt create mode 100644 vector/src/main/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastState.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt index b12d9ed6c8..e97a5be303 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt @@ -43,4 +43,7 @@ object MessageType { // Fake message types for live location events to be able to inherit them from MessageContent const val MSGTYPE_BEACON_INFO = "org.matrix.android.sdk.beacon.info" const val MSGTYPE_BEACON_LOCATION_DATA = "org.matrix.android.sdk.beacon.location.data" + + // Fake message types for voice broadcast events to be able to inherit them from MessageContent + const val MSGTYPE_VOICE_BROADCAST_INFO = "io.element.voicebroadcast.info" } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt new file mode 100644 index 0000000000..d7d74b08e9 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastConstants.kt @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.voicebroadcast + +/** Voice Broadcast State Event. */ +const val STATE_ROOM_VOICE_BROADCAST_INFO = "io.element.voice_broadcast_info" 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 new file mode 100644 index 0000000000..b33d6cc4da --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/model/MessageVoiceBroadcastInfoContent.kt @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.voicebroadcast.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +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 [STATE_ROOM_VOICE_BROADCAST_INFO]. + * + * It contains general info related to a voice broadcast. + */ +@JsonClass(generateAdapter = true) +data class MessageVoiceBroadcastInfoContent( + /** Local message type, not from server. */ + @Transient override val msgType: String = MSGTYPE_VOICE_BROADCAST_INFO, + @Json(name = "body") override val body: String = "", + @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null, + @Json(name = "m.new_content") override val newContent: Content? = null, + + /** The [VoiceBroadcastState] value. **/ + @Json(name = "state") val voiceBroadcastStateStr: String = "", + /** The length of the voice chunks in seconds. **/ + @Json(name = "chunk_length") val chunkLength: Long? = null, +) : MessageContent { + + val voiceBroadcastState: VoiceBroadcastState? = VoiceBroadcastState.values() + .find { it.value == voiceBroadcastStateStr } + ?: run { + Timber.w("Invalid value for state: `$voiceBroadcastStateStr`") + null + } +} diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastState.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastState.kt new file mode 100644 index 0000000000..02e1b2decc --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastState.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2020 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 im.vector.app.features.voicebroadcast.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * Ref: https://github.com/vector-im/element-meta/discussions/632 + */ +@JsonClass(generateAdapter = false) +enum class VoiceBroadcastState(val value: String) { + /** + * The voice broadcast had been started and is currently being live. + */ + @Json(name = "started") STARTED("started"), + + /** + * The voice broadcast has been paused and may be resumed at any time by the recorder. + */ + @Json(name = "paused") PAUSED("paused"), + + /** + * The voice broadcast is currently being live again. + */ + @Json(name = "resumed") RESUMED("resumed"), + + /** + * The voice broadcast has ended. + */ + @Json(name = "stopped") STOPPED("stopped"), +} From d08cfe1147be078a93832221b3f746b7c3be2dae Mon Sep 17 00:00:00 2001 From: Florian Renaud <florianr@element.io> Date: Fri, 23 Sep 2022 08:51:01 +0200 Subject: [PATCH 151/187] Add voice broadcast use cases --- .../voicebroadcast/VoiceBroadcastHelper.kt | 41 +++++++++++ .../usecase/PauseVoiceBroadcastUseCase.kt | 70 +++++++++++++++++++ .../usecase/ResumeVoiceBroadcastUseCase.kt | 68 ++++++++++++++++++ .../usecase/StartVoiceBroadcastUseCase.kt | 60 ++++++++++++++++ .../usecase/StopVoiceBroadcastUseCase.kt | 70 +++++++++++++++++++ 5 files changed, 309 insertions(+) create mode 100644 vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastHelper.kt create mode 100644 vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt create mode 100644 vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCase.kt create mode 100644 vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt create mode 100644 vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastHelper.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastHelper.kt new file mode 100644 index 0000000000..f682cd2f5e --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastHelper.kt @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.voicebroadcast + +import im.vector.app.features.voicebroadcast.usecase.PauseVoiceBroadcastUseCase +import im.vector.app.features.voicebroadcast.usecase.ResumeVoiceBroadcastUseCase +import im.vector.app.features.voicebroadcast.usecase.StartVoiceBroadcastUseCase +import im.vector.app.features.voicebroadcast.usecase.StopVoiceBroadcastUseCase +import javax.inject.Inject + +/** + * Helper class to record voice broadcast. + */ +class VoiceBroadcastHelper @Inject constructor( + private val startVoiceBroadcastUseCase: StartVoiceBroadcastUseCase, + private val pauseVoiceBroadcastUseCase: PauseVoiceBroadcastUseCase, + private val resumeVoiceBroadcastUseCase: ResumeVoiceBroadcastUseCase, + private val stopVoiceBroadcastUseCase: StopVoiceBroadcastUseCase, +) { + suspend fun startVoiceBroadcast(roomId: String) = startVoiceBroadcastUseCase.execute(roomId) + + suspend fun pauseVoiceBroadcast(roomId: String) = pauseVoiceBroadcastUseCase.execute(roomId) + + suspend fun resumeVoiceBroadcast(roomId: String) = resumeVoiceBroadcastUseCase.execute(roomId) + + suspend fun stopVoiceBroadcast(roomId: String) = stopVoiceBroadcastUseCase.execute(roomId) +} diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt new file mode 100644 index 0000000000..a7b544f69e --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.voicebroadcast.usecase + +import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent +import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState +import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.RelationType +import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.Room +import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent +import timber.log.Timber +import javax.inject.Inject + +class PauseVoiceBroadcastUseCase @Inject constructor( + private val session: Session, +) { + + suspend fun execute(roomId: String) { + val room = session.roomService().getRoom(roomId) ?: return + + Timber.d("## PauseVoiceBroadcastUseCase: Pause voice broadcast requested") + + val lastVoiceBroadcastEvent = room.stateService().getStateEvent(STATE_ROOM_VOICE_BROADCAST_INFO, QueryStringValue.Equals(session.myUserId)) + val lastVoiceBroadcastInfoContent = lastVoiceBroadcastEvent?.content.toModel<MessageVoiceBroadcastInfoContent>() + when (val voiceBroadcastState = lastVoiceBroadcastInfoContent?.voiceBroadcastState) { + VoiceBroadcastState.STARTED, + VoiceBroadcastState.RESUMED -> pauseVoiceBroadcast(room, lastVoiceBroadcastEvent) + else -> Timber.d("## PauseVoiceBroadcastUseCase: Cannot pause voice broadcast: currentState=$voiceBroadcastState") + } + } + + private suspend fun pauseVoiceBroadcast(room: Room, event: Event?) { + Timber.d("## PauseVoiceBroadcastUseCase: Send new voice broadcast info state event") + val lastVoiceBroadcastContent = event?.content.toModel<MessageVoiceBroadcastInfoContent>() + val relatesTo = if (lastVoiceBroadcastContent?.voiceBroadcastState == VoiceBroadcastState.STARTED) { + RelationDefaultContent(RelationType.REFERENCE, event?.eventId) + } else { + lastVoiceBroadcastContent?.relatesTo + } + room.stateService().sendStateEvent( + eventType = STATE_ROOM_VOICE_BROADCAST_INFO, + stateKey = session.myUserId, + body = MessageVoiceBroadcastInfoContent( + relatesTo = relatesTo, + voiceBroadcastStateStr = VoiceBroadcastState.PAUSED.value, + ).toContent(), + ) + + // TODO pause recording audio files + } +} diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCase.kt new file mode 100644 index 0000000000..294d91da0f --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCase.kt @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.voicebroadcast.usecase + +import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent +import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState +import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.RelationType +import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.Room +import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent +import timber.log.Timber +import javax.inject.Inject + +class ResumeVoiceBroadcastUseCase @Inject constructor( + private val session: Session, +) { + + suspend fun execute(roomId: String) { + val room = session.roomService().getRoom(roomId) ?: return + + Timber.d("## ResumeVoiceBroadcastUseCase: Resume voice broadcast requested") + + val lastVoiceBroadcastEvent = room.stateService().getStateEvent(STATE_ROOM_VOICE_BROADCAST_INFO, QueryStringValue.Equals(session.myUserId)) + when (val voiceBroadcastState = lastVoiceBroadcastEvent?.content.toModel<MessageVoiceBroadcastInfoContent>()?.voiceBroadcastState) { + VoiceBroadcastState.PAUSED -> resumeVoiceBroadcast(room, lastVoiceBroadcastEvent) + else -> Timber.d("## ResumeVoiceBroadcastUseCase: Cannot resume voice broadcast: currentState=$voiceBroadcastState") + } + } + + private suspend fun resumeVoiceBroadcast(room: Room, event: Event?) { + Timber.d("## ResumeVoiceBroadcastUseCase: Send new voice broadcast info state event") + val lastVoiceBroadcastContent = event?.content.toModel<MessageVoiceBroadcastInfoContent>() + val relatesTo = if (lastVoiceBroadcastContent?.voiceBroadcastState == VoiceBroadcastState.STARTED) { + RelationDefaultContent(RelationType.REFERENCE, event?.eventId) + } else { + lastVoiceBroadcastContent?.relatesTo + } + room.stateService().sendStateEvent( + eventType = STATE_ROOM_VOICE_BROADCAST_INFO, + stateKey = session.myUserId, + body = MessageVoiceBroadcastInfoContent( + relatesTo = relatesTo, + voiceBroadcastStateStr = VoiceBroadcastState.RESUMED.value, + ).toContent(), + ) + + // TODO resume recording audio files + } +} diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt new file mode 100644 index 0000000000..3a9a8e1437 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.voicebroadcast.usecase + +import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent +import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState +import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.Room +import timber.log.Timber +import javax.inject.Inject + +class StartVoiceBroadcastUseCase @Inject constructor( + private val session: Session, +) { + + suspend fun execute(roomId: String) { + val room = session.roomService().getRoom(roomId) ?: return + + Timber.d("## StartVoiceBroadcastUseCase: Start voice broadcast requested") + + val lastVoiceBroadcastEvent = room.stateService().getStateEvent(STATE_ROOM_VOICE_BROADCAST_INFO, QueryStringValue.Equals(session.myUserId)) + when (val voiceBroadcastState = lastVoiceBroadcastEvent?.content.toModel<MessageVoiceBroadcastInfoContent>()?.voiceBroadcastState) { + VoiceBroadcastState.STOPPED, + null -> startVoiceBroadcast(room) + else -> Timber.d("## StartVoiceBroadcastUseCase: Cannot start voice broadcast: currentState=$voiceBroadcastState") + } + } + + private suspend fun startVoiceBroadcast(room: Room) { + Timber.d("## StartVoiceBroadcastUseCase: Send new voice broadcast info state event") + room.stateService().sendStateEvent( + eventType = STATE_ROOM_VOICE_BROADCAST_INFO, + stateKey = session.myUserId, + body = MessageVoiceBroadcastInfoContent( + voiceBroadcastStateStr = VoiceBroadcastState.STARTED.value, + chunkLength = 5L, // TODO Get length from voice broadcast settings + ).toContent() + ) + + // TODO start recording audio files + } +} diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt new file mode 100644 index 0000000000..5a72f379a3 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.voicebroadcast.usecase + +import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent +import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState +import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.RelationType +import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.Room +import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent +import timber.log.Timber +import javax.inject.Inject + +class StopVoiceBroadcastUseCase @Inject constructor( + private val session: Session, +) { + + suspend fun execute(roomId: String) { + val room = session.roomService().getRoom(roomId) ?: return + + Timber.d("## StopVoiceBroadcastUseCase: Stop voice broadcast requested") + + val lastVoiceBroadcastEvent = room.stateService().getStateEvent(STATE_ROOM_VOICE_BROADCAST_INFO, QueryStringValue.Equals(session.myUserId)) + when (val voiceBroadcastState = lastVoiceBroadcastEvent?.content.toModel<MessageVoiceBroadcastInfoContent>()?.voiceBroadcastState) { + VoiceBroadcastState.STARTED, + VoiceBroadcastState.PAUSED, + VoiceBroadcastState.RESUMED -> stopVoiceBroadcast(room, lastVoiceBroadcastEvent) + else -> Timber.d("## StopVoiceBroadcastUseCase: Cannot stop voice broadcast: currentState=$voiceBroadcastState") + } + } + + private suspend fun stopVoiceBroadcast(room: Room, event: Event?) { + Timber.d("## StopVoiceBroadcastUseCase: Send new voice broadcast info state event") + val lastVoiceBroadcastContent = event?.content.toModel<MessageVoiceBroadcastInfoContent>() + val relatesTo = if (lastVoiceBroadcastContent?.voiceBroadcastState == VoiceBroadcastState.STARTED) { + RelationDefaultContent(RelationType.REFERENCE, event?.eventId) + } else { + lastVoiceBroadcastContent?.relatesTo + } + room.stateService().sendStateEvent( + eventType = STATE_ROOM_VOICE_BROADCAST_INFO, + stateKey = session.myUserId, + body = MessageVoiceBroadcastInfoContent( + relatesTo = relatesTo, + voiceBroadcastStateStr = VoiceBroadcastState.STOPPED.value, + ).toContent(), + ) + + // TODO stop recording audio files + } +} From bcc84c8025d38ea0663eaa4cfe9e4ed0c7bc3a2a Mon Sep 17 00:00:00 2001 From: Florian Renaud <florianr@element.io> Date: Fri, 23 Sep 2022 08:53:21 +0200 Subject: [PATCH 152/187] Add several voice broadcast actions --- .../home/room/detail/RoomDetailAction.kt | 8 +++- .../home/room/detail/TimelineViewModel.kt | 17 +++++-- .../composer/MessageComposerFragment.kt | 3 +- .../timeline/factory/MessageItemFactory.kt | 47 ++++++++++++++++++- .../timeline/factory/TimelineItemFactory.kt | 2 + 5 files changed, 70 insertions(+), 7 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt index 10708d2290..3e828f62b7 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt @@ -79,7 +79,6 @@ sealed class RoomDetailAction : VectorViewModelAction { data class ReRequestKeys(val eventId: String) : RoomDetailAction() object SelectStickerAttachment : RoomDetailAction() - object StartVoiceBroadcast : RoomDetailAction() object OpenIntegrationManager : RoomDetailAction() object ManageIntegrations : RoomDetailAction() data class AddJitsiWidget(val withVideo: Boolean) : RoomDetailAction() @@ -120,4 +119,11 @@ sealed class RoomDetailAction : VectorViewModelAction { object StopLiveLocationSharing : RoomDetailAction() object OpenElementCallWidget : RoomDetailAction() + + sealed class VoiceBroadcastAction : RoomDetailAction() { + object Start : VoiceBroadcastAction() + object Pause : VoiceBroadcastAction() + object Resume : VoiceBroadcastAction() + object Stop : VoiceBroadcastAction() + } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt index 4bed477711..b251facb90 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt @@ -65,6 +65,7 @@ import im.vector.app.features.raw.wellknown.withElementWellKnown import im.vector.app.features.session.coroutineScope import im.vector.app.features.settings.VectorDataStore import im.vector.app.features.settings.VectorPreferences +import im.vector.app.features.voicebroadcast.VoiceBroadcastHelper import im.vector.lib.core.utils.flow.chunk import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableSharedFlow @@ -149,6 +150,7 @@ class TimelineViewModel @AssistedInject constructor( buildMeta: BuildMeta, timelineFactory: TimelineFactory, private val spaceStateHandler: SpaceStateHandler, + private val voiceBroadcastHelper: VoiceBroadcastHelper, ) : VectorViewModel<RoomDetailViewState, RoomDetailAction, RoomDetailViewEvents>(initialState), Timeline.Listener, ChatEffectManager.Delegate, CallProtocolsChecker.Listener, LocationSharingServiceConnection.Callback { @@ -456,7 +458,7 @@ class TimelineViewModel @AssistedInject constructor( is RoomDetailAction.ReRequestKeys -> handleReRequestKeys(action) is RoomDetailAction.TapOnFailedToDecrypt -> handleTapOnFailedToDecrypt(action) is RoomDetailAction.SelectStickerAttachment -> handleSelectStickerAttachment() - is RoomDetailAction.StartVoiceBroadcast -> handleStartVoiceBroadcast() + is RoomDetailAction.VoiceBroadcastAction -> handleVoiceBroadcastAction(action) is RoomDetailAction.OpenIntegrationManager -> handleOpenIntegrationManager() is RoomDetailAction.StartCall -> handleStartCall(action) is RoomDetailAction.AcceptCall -> handleAcceptCall(action) @@ -598,9 +600,16 @@ class TimelineViewModel @AssistedInject constructor( } } - private fun handleStartVoiceBroadcast() { - // Todo implement start voice broadcast action - Timber.d("Start voice broadcast clicked") + private fun handleVoiceBroadcastAction(action: RoomDetailAction.VoiceBroadcastAction) { + if (room == null) return + viewModelScope.launch { + when (action) { + RoomDetailAction.VoiceBroadcastAction.Pause -> voiceBroadcastHelper.pauseVoiceBroadcast(room.roomId) + RoomDetailAction.VoiceBroadcastAction.Resume -> voiceBroadcastHelper.resumeVoiceBroadcast(room.roomId) + RoomDetailAction.VoiceBroadcastAction.Start -> voiceBroadcastHelper.startVoiceBroadcast(room.roomId) + RoomDetailAction.VoiceBroadcastAction.Stop -> voiceBroadcastHelper.stopVoiceBroadcast(room.roomId) + } + } } private fun handleOpenIntegrationManager() { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt index 21a87f092f..509e8cc4af 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt @@ -73,6 +73,7 @@ import im.vector.app.features.command.ParsedCommand import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.AutoCompleter import im.vector.app.features.home.room.detail.RoomDetailAction +import im.vector.app.features.home.room.detail.RoomDetailAction.VoiceBroadcastAction import im.vector.app.features.home.room.detail.TimelineViewModel import im.vector.app.features.home.room.detail.composer.voice.VoiceMessageRecorderView import im.vector.app.features.home.room.detail.timeline.action.MessageSharedActionViewModel @@ -653,7 +654,7 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A locationOwnerId = session.myUserId ) } - AttachmentTypeSelectorView.Type.VOICE_BROADCAST -> timelineViewModel.handle(RoomDetailAction.StartVoiceBroadcast) + AttachmentTypeSelectorView.Type.VOICE_BROADCAST -> timelineViewModel.handle(VoiceBroadcastAction.Start) } } 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 eb7b0d4ed8..ecd34208f6 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 @@ -77,6 +77,8 @@ import im.vector.app.features.media.ImageContentRenderer import im.vector.app.features.media.VideoContentRenderer import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.voice.AudioWaveformView +import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence import me.gujun.android.span.span import org.matrix.android.sdk.api.MatrixUrls.isMxcUrl @@ -102,6 +104,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageVerification import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent import org.matrix.android.sdk.api.session.room.model.message.getFileUrl import org.matrix.android.sdk.api.session.room.model.message.getThumbnailUrl +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent import org.matrix.android.sdk.api.settings.LightweightSettingsStorage import org.matrix.android.sdk.api.util.MimeTypes @@ -163,7 +166,7 @@ class MessageItemFactory @Inject constructor( return buildRedactedItem(attributes, highlight) } - val messageContent = event.getLastMessageContent() + val messageContent = getLastMessageContent(event) if (messageContent == null) { val malformedText = stringProvider.getString(R.string.malformed_message) return defaultItemFactory.create(malformedText, informationData, highlight, callback) @@ -197,6 +200,7 @@ class MessageItemFactory @Inject constructor( is MessagePollContent -> buildPollItem(messageContent, informationData, highlight, callback, attributes) is MessageLocationContent -> buildLocationItem(messageContent, informationData, highlight, attributes) is MessageBeaconInfoContent -> liveLocationShareMessageItemFactory.create(params.event, highlight, attributes) + is MessageVoiceBroadcastInfoContent -> buildVoiceBroadcastItem(messageContent, informationData, highlight, callback, attributes) else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes) } return messageItem?.apply { @@ -204,6 +208,17 @@ class MessageItemFactory @Inject constructor( } } + private fun getLastMessageContent(event: TimelineEvent): MessageContent? { + return with(event) { + // Iterate on event types which are not part of the matrix sdk, otherwise fallback to the sdk method + when (root.getClearType()) { + STATE_ROOM_VOICE_BROADCAST_INFO -> + (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel<MessageVoiceBroadcastInfoContent>() + else -> event.getLastMessageContent() + } + } + } + private fun buildLocationItem( locationContent: MessageLocationContent, informationData: MessageInformationData, @@ -706,6 +721,36 @@ class MessageItemFactory @Inject constructor( .highlighted(highlight) } + private fun buildVoiceBroadcastItem( + messageContent: MessageVoiceBroadcastInfoContent, + @Suppress("UNUSED_PARAMETER") + informationData: MessageInformationData, + highlight: Boolean, + callback: TimelineEventController.Callback?, + attributes: AbsMessageItem.Attributes, + ): MessageTextItem? { + val htmlBody = "voice broadcast state: ${messageContent.voiceBroadcastState}" + val formattedBody = span { + text = htmlBody + textColor = colorProvider.getColorFromAttribute(R.attr.vctr_content_secondary) + textStyle = "italic" + } + + val bindingOptions = spanUtils.getBindingOptions(htmlBody) + val message = formattedBody.linkify(callback) + + return MessageTextItem_() + .leftGuideline(avatarSizeProvider.leftGuideline) + .previewUrlRetriever(callback?.getPreviewUrlRetriever()) + .imageContentRenderer(imageContentRenderer) + .previewUrlCallback(callback) + .attributes(attributes) + .message(message.toEpoxyCharSequence()) + .bindingOptions(bindingOptions) + .highlighted(highlight) + .movementMethod(createLinkMovementMethod(callback)) + } + private fun List<Int?>?.toFft(): List<Int>? { return this ?.filterNotNull() 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 6c5a66d39d..0b8f95b4a1 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 @@ -21,6 +21,7 @@ import im.vector.app.core.epoxy.TimelineEmptyItem_ import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.features.analytics.DecryptionFailureTracker import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventVisibilityHelper +import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import timber.log.Timber @@ -88,6 +89,7 @@ class TimelineItemFactory @Inject constructor( // State room create EventType.STATE_ROOM_CREATE -> roomCreateItemFactory.create(params) in EventType.STATE_ROOM_BEACON_INFO -> messageItemFactory.create(params) + STATE_ROOM_VOICE_BROADCAST_INFO -> messageItemFactory.create(params) // Unhandled state event types else -> { // Should only happen when shouldShowHiddenEvents() settings is ON From daf4fc0f6db5b13f4711549a4cc7cf1dea09546f Mon Sep 17 00:00:00 2001 From: Florian Renaud <florianr@element.io> Date: Thu, 29 Sep 2022 10:29:08 +0200 Subject: [PATCH 153/187] Add basic timeline voice broadcast item --- .../timeline/factory/MessageItemFactory.kt | 30 +++----- .../item/MessageVoiceBroadcastItem.kt | 72 +++++++++++++++++++ ...em_timeline_event_view_stubs_container.xml | 7 ++ ...em_timeline_event_voice_broadcast_stub.xml | 62 ++++++++++++++++ 4 files changed, 149 insertions(+), 22 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastItem.kt create mode 100644 vector/src/main/res/layout/item_timeline_event_voice_broadcast_stub.xml 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 ecd34208f6..0fbd1033c7 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 @@ -55,6 +55,8 @@ import im.vector.app.features.home.room.detail.timeline.item.MessageLocationItem import im.vector.app.features.home.room.detail.timeline.item.MessageLocationItem_ import im.vector.app.features.home.room.detail.timeline.item.MessageTextItem import im.vector.app.features.home.room.detail.timeline.item.MessageTextItem_ +import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastItem +import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastItem_ import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceItem import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceItem_ import im.vector.app.features.home.room.detail.timeline.item.PollItem @@ -200,7 +202,7 @@ class MessageItemFactory @Inject constructor( is MessagePollContent -> buildPollItem(messageContent, informationData, highlight, callback, attributes) is MessageLocationContent -> buildLocationItem(messageContent, informationData, highlight, attributes) is MessageBeaconInfoContent -> liveLocationShareMessageItemFactory.create(params.event, highlight, attributes) - is MessageVoiceBroadcastInfoContent -> buildVoiceBroadcastItem(messageContent, informationData, highlight, callback, attributes) + is MessageVoiceBroadcastInfoContent -> buildVoiceBroadcastItem(messageContent, highlight, callback, attributes) else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes) } return messageItem?.apply { @@ -723,32 +725,16 @@ class MessageItemFactory @Inject constructor( private fun buildVoiceBroadcastItem( messageContent: MessageVoiceBroadcastInfoContent, - @Suppress("UNUSED_PARAMETER") - informationData: MessageInformationData, highlight: Boolean, callback: TimelineEventController.Callback?, attributes: AbsMessageItem.Attributes, - ): MessageTextItem? { - val htmlBody = "voice broadcast state: ${messageContent.voiceBroadcastState}" - val formattedBody = span { - text = htmlBody - textColor = colorProvider.getColorFromAttribute(R.attr.vctr_content_secondary) - textStyle = "italic" - } - - val bindingOptions = spanUtils.getBindingOptions(htmlBody) - val message = formattedBody.linkify(callback) - - return MessageTextItem_() - .leftGuideline(avatarSizeProvider.leftGuideline) - .previewUrlRetriever(callback?.getPreviewUrlRetriever()) - .imageContentRenderer(imageContentRenderer) - .previewUrlCallback(callback) + ): MessageVoiceBroadcastItem? { + return MessageVoiceBroadcastItem_() .attributes(attributes) - .message(message.toEpoxyCharSequence()) - .bindingOptions(bindingOptions) .highlighted(highlight) - .movementMethod(createLinkMovementMethod(callback)) + .playingState(messageContent.voiceBroadcastStateStr.toEpoxyCharSequence()) + .leftGuideline(avatarSizeProvider.leftGuideline) + .callback(callback) } private fun List<Int?>?.toFft(): List<Int>? { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastItem.kt new file mode 100644 index 0000000000..0476ea2a93 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastItem.kt @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home.room.detail.timeline.item + +import android.annotation.SuppressLint +import android.widget.ImageButton +import android.widget.TextView +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.app.R +import im.vector.app.features.home.room.detail.RoomDetailAction +import im.vector.app.features.home.room.detail.timeline.TimelineEventController +import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState +import im.vector.lib.core.utils.epoxy.charsequence.EpoxyCharSequence + +@EpoxyModelClass +abstract class MessageVoiceBroadcastItem : AbsMessageItem<MessageVoiceBroadcastItem.Holder>() { + + @EpoxyAttribute + var callback: TimelineEventController.Callback? = null + + @EpoxyAttribute + var playingState: EpoxyCharSequence? = null + + override fun bind(holder: Holder) { + super.bind(holder) + bindVoiceBroadcastItem(holder) + } + + @SuppressLint("SetTextI18n") // Temporary text + private fun bindVoiceBroadcastItem(holder: Holder) { + with(holder) { + currentStateText.text = "Voice Broadcast state: ${playingState?.charSequence ?: "None"}" + val voiceBroadcastState = VoiceBroadcastState.values().find { it.value == playingState?.charSequence } + playButton.isEnabled = voiceBroadcastState == VoiceBroadcastState.PAUSED + pauseButton.isEnabled = voiceBroadcastState == VoiceBroadcastState.STARTED || voiceBroadcastState == VoiceBroadcastState.RESUMED + stopButton.isEnabled = voiceBroadcastState == VoiceBroadcastState.STARTED || + voiceBroadcastState == VoiceBroadcastState.RESUMED || + voiceBroadcastState == VoiceBroadcastState.PAUSED + playButton.setOnClickListener { attributes.callback?.onTimelineItemAction(RoomDetailAction.VoiceBroadcastAction.Resume) } + pauseButton.setOnClickListener { attributes.callback?.onTimelineItemAction(RoomDetailAction.VoiceBroadcastAction.Pause) } + stopButton.setOnClickListener { attributes.callback?.onTimelineItemAction(RoomDetailAction.VoiceBroadcastAction.Stop) } + } + } + + override fun getViewStubId() = STUB_ID + + class Holder : AbsMessageLocationItem.Holder(STUB_ID) { + val currentStateText by bind<TextView>(R.id.currentStateText) + val playButton by bind<ImageButton>(R.id.playButton) + val pauseButton by bind<ImageButton>(R.id.pauseButton) + val stopButton by bind<ImageButton>(R.id.stopButton) + } + + companion object { + private val STUB_ID = R.id.messageVoiceBroadcastStub + } +} diff --git a/vector/src/main/res/layout/item_timeline_event_view_stubs_container.xml b/vector/src/main/res/layout/item_timeline_event_view_stubs_container.xml index 0d45a48b9b..6fcf5711f7 100644 --- a/vector/src/main/res/layout/item_timeline_event_view_stubs_container.xml +++ b/vector/src/main/res/layout/item_timeline_event_view_stubs_container.xml @@ -47,6 +47,13 @@ android:layout="@layout/item_timeline_event_audio_stub" tools:visibility="gone" /> + <ViewStub + android:id="@+id/messageVoiceBroadcastStub" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout="@layout/item_timeline_event_voice_broadcast_stub" + tools:visibility="gone" /> + <ViewStub android:id="@+id/messageContentPollStub" android:layout_width="match_parent" diff --git a/vector/src/main/res/layout/item_timeline_event_voice_broadcast_stub.xml b/vector/src/main/res/layout/item_timeline_event_voice_broadcast_stub.xml new file mode 100644 index 0000000000..e35060f72a --- /dev/null +++ b/vector/src/main/res/layout/item_timeline_event_voice_broadcast_stub.xml @@ -0,0 +1,62 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/messageRootLayout" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="@dimen/layout_vertical_margin" + tools:viewBindingIgnore="true"> + + <TextView + android:id="@+id/currentStateText" + style="@style/Widget.Vector.TextView.HeadlineMedium" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:layout_constraintBottom_toTopOf="@id/playButton" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + tools:text="Voice Broadcast state: STARTED" /> + + <ImageButton + android:id="@+id/playButton" + android:layout_width="@dimen/item_event_message_media_button_size" + android:layout_height="@dimen/item_event_message_media_button_size" + android:layout_marginTop="@dimen/layout_vertical_margin" + android:background="?android:attr/selectableItemBackground" + android:contentDescription="@string/a11y_play_voice_message" + android:src="@drawable/ic_play_pause_play" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toStartOf="@id/pauseButton" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/currentStateText" + app:tint="@color/vector_content_primary_tint_selector" /> + + <ImageButton + android:id="@+id/pauseButton" + android:layout_width="@dimen/item_event_message_media_button_size" + android:layout_height="@dimen/item_event_message_media_button_size" + android:background="?android:attr/selectableItemBackground" + android:contentDescription="@string/a11y_pause_voice_message" + android:src="@drawable/ic_play_pause_pause" + app:layout_constraintBottom_toBottomOf="@id/playButton" + app:layout_constraintEnd_toStartOf="@id/stopButton" + app:layout_constraintStart_toEndOf="@id/playButton" + app:layout_constraintTop_toTopOf="@id/playButton" + app:tint="@color/vector_content_primary_tint_selector" /> + + <ImageButton + android:id="@+id/stopButton" + android:layout_width="@dimen/item_event_message_media_button_size" + android:layout_height="@dimen/item_event_message_media_button_size" + android:background="?android:attr/selectableItemBackground" + android:contentDescription="@string/a11y_stop_voice_message" + android:src="@drawable/ic_close_24dp" + app:layout_constraintBottom_toBottomOf="@id/pauseButton" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@id/pauseButton" + app:layout_constraintTop_toTopOf="@id/playButton" + app:tint="@color/vector_content_primary_tint_selector" /> + +</androidx.constraintlayout.widget.ConstraintLayout> From e4a52e1d5e0848c0597b0ef5cade46596184953c Mon Sep 17 00:00:00 2001 From: Florian Renaud <florianr@element.io> Date: Thu, 29 Sep 2022 13:41:43 +0200 Subject: [PATCH 154/187] Add deletion action on voice broadcast event --- .../timeline/action/CheckIfCanRedactEventUseCase.kt | 10 ++++++++-- .../action/CheckIfCanRedactEventUseCaseTest.kt | 3 ++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanRedactEventUseCase.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanRedactEventUseCase.kt index 3bc3a5e351..eda1929133 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanRedactEventUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanRedactEventUseCase.kt @@ -17,6 +17,7 @@ package im.vector.app.features.home.room.detail.timeline.action import im.vector.app.core.di.ActiveSessionHolder +import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import javax.inject.Inject @@ -27,8 +28,13 @@ class CheckIfCanRedactEventUseCase @Inject constructor( fun execute(event: TimelineEvent, actionPermissions: ActionPermissions): Boolean { // Only some event types are supported for the moment - val canRedactEventTypes = listOf(EventType.MESSAGE, EventType.STICKER) + - EventType.POLL_START + EventType.STATE_ROOM_BEACON_INFO + val canRedactEventTypes: List<String> = listOf( + EventType.MESSAGE, + EventType.STICKER, + STATE_ROOM_VOICE_BROADCAST_INFO, + ) + + EventType.POLL_START + + EventType.STATE_ROOM_BEACON_INFO return event.root.getClearType() in canRedactEventTypes && // Message sent by the current user can always be redacted, else check permission for messages sent by other users diff --git a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanRedactEventUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanRedactEventUseCaseTest.kt index 08dd5dac5b..fcb052cb2b 100644 --- a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanRedactEventUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanRedactEventUseCaseTest.kt @@ -16,6 +16,7 @@ package im.vector.app.features.home.room.detail.timeline.action +import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO import im.vector.app.test.fakes.FakeActiveSessionHolder import io.mockk.mockk import org.amshove.kluent.shouldBe @@ -34,7 +35,7 @@ class CheckIfCanRedactEventUseCaseTest { @Test fun `given an event which can be redacted and owned by user when use case executes then the result is true`() { - val canRedactEventTypes = listOf(EventType.MESSAGE, EventType.STICKER) + + val canRedactEventTypes = listOf(EventType.MESSAGE, EventType.STICKER, STATE_ROOM_VOICE_BROADCAST_INFO) + EventType.POLL_START + EventType.STATE_ROOM_BEACON_INFO canRedactEventTypes.forEach { eventType -> From 1bc894712efaef4933d3df8ba8ad15fc91217ac0 Mon Sep 17 00:00:00 2001 From: Florian Renaud <florianr@element.io> Date: Fri, 30 Sep 2022 15:49:04 +0200 Subject: [PATCH 155/187] Add unit tests for use cases --- .../usecase/PauseVoiceBroadcastUseCaseTest.kt | 129 ++++++++++++++++++ .../ResumeVoiceBroadcastUseCaseTest.kt | 129 ++++++++++++++++++ .../usecase/StartVoiceBroadcastUseCaseTest.kt | 117 ++++++++++++++++ .../usecase/StopVoiceBroadcastUseCaseTest.kt | 129 ++++++++++++++++++ .../java/im/vector/app/test/fakes/FakeRoom.kt | 3 + .../vector/app/test/fakes/FakeStateService.kt | 29 ++++ 6 files changed, 536 insertions(+) create mode 100644 vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCaseTest.kt create mode 100644 vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCaseTest.kt create mode 100644 vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCaseTest.kt create mode 100644 vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCaseTest.kt create mode 100644 vector/src/test/java/im/vector/app/test/fakes/FakeStateService.kt diff --git a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCaseTest.kt new file mode 100644 index 0000000000..3139f20cd4 --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCaseTest.kt @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.voicebroadcast.usecase + +import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent +import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState +import im.vector.app.test.fakes.FakeRoom +import im.vector.app.test.fakes.FakeRoomService +import im.vector.app.test.fakes.FakeSession +import io.mockk.clearAllMocks +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.slot +import kotlinx.coroutines.test.runTest +import org.amshove.kluent.shouldBe +import org.junit.Test +import org.matrix.android.sdk.api.session.events.model.Content +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.toContent +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent + +private const val A_ROOM_ID = "room_id" +private const val AN_EVENT_ID = "event_id" +private const val A_STARTED_VOICE_BROADCAST_EVENT_ID = "a_started_voice_broadcast_event_id" + +class PauseVoiceBroadcastUseCaseTest { + + private val fakeRoom = FakeRoom() + private val fakeSession = FakeSession(fakeRoomService = FakeRoomService(fakeRoom)) + private val pauseVoiceBroadcastUseCase = PauseVoiceBroadcastUseCase(fakeSession) + + @Test + fun `given a room id with a potential existing voice broadcast state when calling execute then the voice broadcast is paused or not`() = runTest { + val cases = listOf<VoiceBroadcastState?>(null).plus(VoiceBroadcastState.values()).map { + when (it) { + VoiceBroadcastState.STARTED, + VoiceBroadcastState.RESUMED -> Case(it, true) + VoiceBroadcastState.STOPPED, + VoiceBroadcastState.PAUSED, + null -> Case(it, false) + } + } + + cases.forEach { case -> + if (case.canPauseVoiceBroadcast) { + testVoiceBroadcastPaused(case.previousState) + } else { + testVoiceBroadcastNotPaused(case.previousState) + } + } + } + + private suspend fun testVoiceBroadcastPaused(previousState: VoiceBroadcastState?) { + // Given + clearAllMocks() + givenAVoiceBroadcastState(previousState) + val voiceBroadcastInfoContentInterceptor = slot<Content>() + coEvery { fakeRoom.stateService().sendStateEvent(any(), any(), capture(voiceBroadcastInfoContentInterceptor)) } coAnswers { AN_EVENT_ID } + + // When + pauseVoiceBroadcastUseCase.execute(A_ROOM_ID) + + // Then + coVerify { + fakeRoom.stateService().sendStateEvent( + eventType = STATE_ROOM_VOICE_BROADCAST_INFO, + stateKey = fakeSession.myUserId, + body = any(), + ) + } + val voiceBroadcastInfoContent = voiceBroadcastInfoContentInterceptor.captured.toModel<MessageVoiceBroadcastInfoContent>() + voiceBroadcastInfoContent?.voiceBroadcastState shouldBe VoiceBroadcastState.PAUSED + voiceBroadcastInfoContent?.relatesTo?.type shouldBe RelationType.REFERENCE + voiceBroadcastInfoContent?.relatesTo?.eventId shouldBe A_STARTED_VOICE_BROADCAST_EVENT_ID + } + + private suspend fun testVoiceBroadcastNotPaused(previousState: VoiceBroadcastState?) { + // Given + clearAllMocks() + givenAVoiceBroadcastState(previousState) + + // When + pauseVoiceBroadcastUseCase.execute(A_ROOM_ID) + + // Then + coVerify(exactly = 0) { fakeRoom.stateService().sendStateEvent(any(), any(), any()) } + } + + private fun givenAVoiceBroadcastState(state: VoiceBroadcastState?) { + val relatesTo = when (state) { + VoiceBroadcastState.STARTED, + null -> null + VoiceBroadcastState.PAUSED, + VoiceBroadcastState.RESUMED, + VoiceBroadcastState.STOPPED -> RelationDefaultContent(RelationType.REFERENCE, A_STARTED_VOICE_BROADCAST_EVENT_ID) + } + val event = state?.let { + Event( + eventId = if (state == VoiceBroadcastState.STARTED) A_STARTED_VOICE_BROADCAST_EVENT_ID else AN_EVENT_ID, + type = STATE_ROOM_VOICE_BROADCAST_INFO, + stateKey = fakeSession.myUserId, + content = MessageVoiceBroadcastInfoContent( + voiceBroadcastStateStr = state.value, + relatesTo = relatesTo + ).toContent() + ) + } + fakeRoom.stateService().givenGetStateEvent(event) + } + + private data class Case(val previousState: VoiceBroadcastState?, val canPauseVoiceBroadcast: Boolean) +} diff --git a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCaseTest.kt new file mode 100644 index 0000000000..23d506482b --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCaseTest.kt @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.voicebroadcast.usecase + +import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent +import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState +import im.vector.app.test.fakes.FakeRoom +import im.vector.app.test.fakes.FakeRoomService +import im.vector.app.test.fakes.FakeSession +import io.mockk.clearAllMocks +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.slot +import kotlinx.coroutines.test.runTest +import org.amshove.kluent.shouldBe +import org.junit.Test +import org.matrix.android.sdk.api.session.events.model.Content +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.toContent +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent + +private const val A_ROOM_ID = "room_id" +private const val AN_EVENT_ID = "event_id" +private const val A_STARTED_VOICE_BROADCAST_EVENT_ID = "a_started_voice_broadcast_event_id" + +class ResumeVoiceBroadcastUseCaseTest { + + private val fakeRoom = FakeRoom() + private val fakeSession = FakeSession(fakeRoomService = FakeRoomService(fakeRoom)) + private val resumeVoiceBroadcastUseCase = ResumeVoiceBroadcastUseCase(fakeSession) + + @Test + fun `given a room id with a potential existing voice broadcast state when calling execute then the voice broadcast is resumed or not`() = runTest { + val cases = listOf<VoiceBroadcastState?>(null).plus(VoiceBroadcastState.values()).map { + when (it) { + VoiceBroadcastState.PAUSED -> Case(it, true) + VoiceBroadcastState.STARTED, + VoiceBroadcastState.RESUMED, + VoiceBroadcastState.STOPPED, + null -> Case(it, false) + } + } + + cases.forEach { case -> + if (case.canResumeVoiceBroadcast) { + testVoiceBroadcastResumed(case.previousState) + } else { + testVoiceBroadcastNotResumed(case.previousState) + } + } + } + + private suspend fun testVoiceBroadcastResumed(previousState: VoiceBroadcastState?) { + // Given + clearAllMocks() + givenAVoiceBroadcastState(previousState) + val voiceBroadcastInfoContentInterceptor = slot<Content>() + coEvery { fakeRoom.stateService().sendStateEvent(any(), any(), capture(voiceBroadcastInfoContentInterceptor)) } coAnswers { AN_EVENT_ID } + + // When + resumeVoiceBroadcastUseCase.execute(A_ROOM_ID) + + // Then + coVerify { + fakeRoom.stateService().sendStateEvent( + eventType = STATE_ROOM_VOICE_BROADCAST_INFO, + stateKey = fakeSession.myUserId, + body = any(), + ) + } + val voiceBroadcastInfoContent = voiceBroadcastInfoContentInterceptor.captured.toModel<MessageVoiceBroadcastInfoContent>() + voiceBroadcastInfoContent?.voiceBroadcastState shouldBe VoiceBroadcastState.RESUMED + voiceBroadcastInfoContent?.relatesTo?.type shouldBe RelationType.REFERENCE + voiceBroadcastInfoContent?.relatesTo?.eventId shouldBe A_STARTED_VOICE_BROADCAST_EVENT_ID + } + + private suspend fun testVoiceBroadcastNotResumed(previousState: VoiceBroadcastState?) { + // Given + clearAllMocks() + givenAVoiceBroadcastState(previousState) + + // When + resumeVoiceBroadcastUseCase.execute(A_ROOM_ID) + + // Then + coVerify(exactly = 0) { fakeRoom.stateService().sendStateEvent(any(), any(), any()) } + } + + private fun givenAVoiceBroadcastState(state: VoiceBroadcastState?) { + val relatesTo = when (state) { + VoiceBroadcastState.STARTED, + null -> null + VoiceBroadcastState.PAUSED, + VoiceBroadcastState.RESUMED, + VoiceBroadcastState.STOPPED -> RelationDefaultContent(RelationType.REFERENCE, A_STARTED_VOICE_BROADCAST_EVENT_ID) + } + val event = state?.let { + Event( + eventId = if (state == VoiceBroadcastState.STARTED) A_STARTED_VOICE_BROADCAST_EVENT_ID else AN_EVENT_ID, + type = STATE_ROOM_VOICE_BROADCAST_INFO, + stateKey = fakeSession.myUserId, + content = MessageVoiceBroadcastInfoContent( + voiceBroadcastStateStr = state.value, + relatesTo = relatesTo + ).toContent() + ) + } + fakeRoom.stateService().givenGetStateEvent(event) + } + + private data class Case(val previousState: VoiceBroadcastState?, val canResumeVoiceBroadcast: Boolean) +} diff --git a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCaseTest.kt new file mode 100644 index 0000000000..3bb04f3a7a --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCaseTest.kt @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.voicebroadcast.usecase + +import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent +import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState +import im.vector.app.test.fakes.FakeRoom +import im.vector.app.test.fakes.FakeRoomService +import im.vector.app.test.fakes.FakeSession +import io.mockk.clearAllMocks +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.slot +import kotlinx.coroutines.test.runTest +import org.amshove.kluent.shouldBe +import org.amshove.kluent.shouldBeNull +import org.junit.Test +import org.matrix.android.sdk.api.session.events.model.Content +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.events.model.toModel + +private const val A_ROOM_ID = "room_id" +private const val AN_EVENT_ID = "event_id" + +class StartVoiceBroadcastUseCaseTest { + + private val fakeRoom = FakeRoom() + private val fakeSession = FakeSession(fakeRoomService = FakeRoomService(fakeRoom)) + private val startVoiceBroadcastUseCase = StartVoiceBroadcastUseCase(fakeSession) + + @Test + fun `given a room id with a potential existing voice broadcast state when calling execute then the voice broadcast is started or not`() = runTest { + val cases = listOf<VoiceBroadcastState?>(null).plus(VoiceBroadcastState.values()).map { + when (it) { + VoiceBroadcastState.STARTED, + VoiceBroadcastState.PAUSED, + VoiceBroadcastState.RESUMED -> Case(it, false) + VoiceBroadcastState.STOPPED, + null -> Case(it, true) + } + } + + cases.forEach { case -> + if (case.canStartVoiceBroadcast) { + testVoiceBroadcastStarted(case.previousState) + } else { + testVoiceBroadcastNotStarted(case.previousState) + } + } + } + + private suspend fun testVoiceBroadcastStarted(previousState: VoiceBroadcastState?) { + // Given + clearAllMocks() + givenAVoiceBroadcastState(previousState) + val voiceBroadcastInfoContentInterceptor = slot<Content>() + coEvery { fakeRoom.stateService().sendStateEvent(any(), any(), capture(voiceBroadcastInfoContentInterceptor)) } coAnswers { AN_EVENT_ID } + + // When + startVoiceBroadcastUseCase.execute(A_ROOM_ID) + + // Then + coVerify { + fakeRoom.stateService().sendStateEvent( + eventType = STATE_ROOM_VOICE_BROADCAST_INFO, + stateKey = fakeSession.myUserId, + body = any(), + ) + } + val voiceBroadcastInfoContent = voiceBroadcastInfoContentInterceptor.captured.toModel<MessageVoiceBroadcastInfoContent>() + voiceBroadcastInfoContent?.voiceBroadcastState shouldBe VoiceBroadcastState.STARTED + voiceBroadcastInfoContent?.relatesTo.shouldBeNull() + } + + private suspend fun testVoiceBroadcastNotStarted(previousState: VoiceBroadcastState?) { + // Given + clearAllMocks() + givenAVoiceBroadcastState(previousState) + + // When + startVoiceBroadcastUseCase.execute(A_ROOM_ID) + + // Then + coVerify(exactly = 0) { fakeRoom.stateService().sendStateEvent(any(), any(), any()) } + } + + private fun givenAVoiceBroadcastState(state: VoiceBroadcastState?) { + val event = state?.let { + Event( + type = STATE_ROOM_VOICE_BROADCAST_INFO, + stateKey = fakeSession.myUserId, + content = MessageVoiceBroadcastInfoContent( + voiceBroadcastStateStr = state.value + ).toContent() + ) + } + fakeRoom.stateService().givenGetStateEvent(event) + } + + private data class Case(val previousState: VoiceBroadcastState?, val canStartVoiceBroadcast: Boolean) +} diff --git a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCaseTest.kt new file mode 100644 index 0000000000..aa8dcddf30 --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCaseTest.kt @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.voicebroadcast.usecase + +import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent +import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState +import im.vector.app.test.fakes.FakeRoom +import im.vector.app.test.fakes.FakeRoomService +import im.vector.app.test.fakes.FakeSession +import io.mockk.clearAllMocks +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.slot +import kotlinx.coroutines.test.runTest +import org.amshove.kluent.shouldBe +import org.junit.Test +import org.matrix.android.sdk.api.session.events.model.Content +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.toContent +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent + +private const val A_ROOM_ID = "room_id" +private const val AN_EVENT_ID = "event_id" +private const val A_STARTED_VOICE_BROADCAST_EVENT_ID = "a_started_voice_broadcast_event_id" + +class StopVoiceBroadcastUseCaseTest { + + private val fakeRoom = FakeRoom() + private val fakeSession = FakeSession(fakeRoomService = FakeRoomService(fakeRoom)) + private val stopVoiceBroadcastUseCase = StopVoiceBroadcastUseCase(fakeSession) + + @Test + fun `given a room id with a potential existing voice broadcast state when calling execute then the voice broadcast is stopped or not`() = runTest { + val cases = listOf<VoiceBroadcastState?>(null).plus(VoiceBroadcastState.values()).map { + when (it) { + VoiceBroadcastState.STARTED, + VoiceBroadcastState.RESUMED, + VoiceBroadcastState.PAUSED -> Case(it, true) + VoiceBroadcastState.STOPPED, + null -> Case(it, false) + } + } + + cases.forEach { case -> + if (case.canStopVoiceBroadcast) { + testVoiceBroadcastStopped(case.previousState) + } else { + testVoiceBroadcastNotStopped(case.previousState) + } + } + } + + private suspend fun testVoiceBroadcastStopped(previousState: VoiceBroadcastState?) { + // Given + clearAllMocks() + givenAVoiceBroadcastState(previousState) + val voiceBroadcastInfoContentInterceptor = slot<Content>() + coEvery { fakeRoom.stateService().sendStateEvent(any(), any(), capture(voiceBroadcastInfoContentInterceptor)) } coAnswers { AN_EVENT_ID } + + // When + stopVoiceBroadcastUseCase.execute(A_ROOM_ID) + + // Then + coVerify { + fakeRoom.stateService().sendStateEvent( + eventType = STATE_ROOM_VOICE_BROADCAST_INFO, + stateKey = fakeSession.myUserId, + body = any(), + ) + } + val voiceBroadcastInfoContent = voiceBroadcastInfoContentInterceptor.captured.toModel<MessageVoiceBroadcastInfoContent>() + voiceBroadcastInfoContent?.voiceBroadcastState shouldBe VoiceBroadcastState.STOPPED + voiceBroadcastInfoContent?.relatesTo?.type shouldBe RelationType.REFERENCE + voiceBroadcastInfoContent?.relatesTo?.eventId shouldBe A_STARTED_VOICE_BROADCAST_EVENT_ID + } + + private suspend fun testVoiceBroadcastNotStopped(previousState: VoiceBroadcastState?) { + // Given + clearAllMocks() + givenAVoiceBroadcastState(previousState) + + // When + stopVoiceBroadcastUseCase.execute(A_ROOM_ID) + + // Then + coVerify(exactly = 0) { fakeRoom.stateService().sendStateEvent(any(), any(), any()) } + } + + private fun givenAVoiceBroadcastState(state: VoiceBroadcastState?) { + val relatesTo = when (state) { + VoiceBroadcastState.STARTED, + null -> null + VoiceBroadcastState.PAUSED, + VoiceBroadcastState.RESUMED, + VoiceBroadcastState.STOPPED -> RelationDefaultContent(RelationType.REFERENCE, A_STARTED_VOICE_BROADCAST_EVENT_ID) + } + val event = state?.let { + Event( + eventId = if (state == VoiceBroadcastState.STARTED) A_STARTED_VOICE_BROADCAST_EVENT_ID else AN_EVENT_ID, + type = STATE_ROOM_VOICE_BROADCAST_INFO, + stateKey = fakeSession.myUserId, + content = MessageVoiceBroadcastInfoContent( + voiceBroadcastStateStr = state.value, + relatesTo = relatesTo + ).toContent() + ) + } + fakeRoom.stateService().givenGetStateEvent(event) + } + + private data class Case(val previousState: VoiceBroadcastState?, val canStopVoiceBroadcast: Boolean) +} 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 865b01551a..7835c314ef 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 @@ -24,6 +24,7 @@ class FakeRoom( private val fakeSendService: FakeSendService = FakeSendService(), private val fakeTimelineService: FakeTimelineService = FakeTimelineService(), private val fakeRelationService: FakeRelationService = FakeRelationService(), + private val fakeStateService: FakeStateService = FakeStateService(), ) : Room by mockk() { override fun locationSharingService() = fakeLocationSharingService @@ -33,4 +34,6 @@ class FakeRoom( override fun timelineService() = fakeTimelineService override fun relationService() = fakeRelationService + + override fun stateService() = fakeStateService } diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeStateService.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeStateService.kt new file mode 100644 index 0000000000..03a0cf07d8 --- /dev/null +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeStateService.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.test.fakes + +import io.mockk.every +import io.mockk.mockk +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.room.state.StateService + +class FakeStateService : StateService by mockk(relaxed = true) { + + fun givenGetStateEvent(event: Event?) { + every { getStateEvent(any(), any()) } returns event + } +} From faeb078c8bbe61457263198e45b1171113fe31df Mon Sep 17 00:00:00 2001 From: Florian Renaud <florianr@element.io> Date: Mon, 3 Oct 2022 10:10:33 +0200 Subject: [PATCH 156/187] Add voice broadcast state event in timeline displayable events --- .../detail/timeline/helper/TimelineDisplayableEvents.kt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt index 23db2a721c..87844aba8e 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt @@ -16,6 +16,7 @@ package im.vector.app.features.home.room.detail.timeline.helper +import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent @@ -24,7 +25,7 @@ object TimelineDisplayableEvents { /** * All types we have an item to build with. Every type not defined here will be shown as DefaultItem if forced to be shown, otherwise will be hidden. */ - val DISPLAYABLE_TYPES = listOf( + val DISPLAYABLE_TYPES: List<String> = listOf( EventType.MESSAGE, EventType.STATE_ROOM_WIDGET_LEGACY, EventType.STATE_ROOM_WIDGET, @@ -51,7 +52,11 @@ object TimelineDisplayableEvents { EventType.STATE_ROOM_JOIN_RULES, EventType.KEY_VERIFICATION_DONE, EventType.KEY_VERIFICATION_CANCEL, - ) + EventType.POLL_START + EventType.STATE_ROOM_BEACON_INFO + EventType.BEACON_LOCATION_DATA + STATE_ROOM_VOICE_BROADCAST_INFO, + ) + + EventType.POLL_START + + EventType.STATE_ROOM_BEACON_INFO + + EventType.BEACON_LOCATION_DATA } fun TimelineEvent.isRoomConfiguration(roomCreatorUserId: String?): Boolean { From 0c5d4c5f128bb5197777ae26e8132156dfe9b1e4 Mon Sep 17 00:00:00 2001 From: Florian Renaud <florianr@element.io> Date: Mon, 3 Oct 2022 09:55:32 +0200 Subject: [PATCH 157/187] Add changelog file --- changelog.d/7273.wip | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7273.wip diff --git a/changelog.d/7273.wip b/changelog.d/7273.wip new file mode 100644 index 0000000000..c480a79a43 --- /dev/null +++ b/changelog.d/7273.wip @@ -0,0 +1 @@ +[Voice Broadcast] Add the "io.element.voice_broadcast_info" state event with a minimalist timeline widget From b9bb7d789249daa644b6c12169d5a852bec4f510 Mon Sep 17 00:00:00 2001 From: Florian Renaud <florianr@element.io> Date: Tue, 4 Oct 2022 15:13:01 +0200 Subject: [PATCH 158/187] Add VoiceBroadcastEvent wrapper --- .../model/VoiceBroadcastEvent.kt | 55 ++++++++ .../usecase/PauseVoiceBroadcastUseCase.kt | 24 ++-- .../usecase/ResumeVoiceBroadcastUseCase.kt | 29 +++-- .../usecase/StartVoiceBroadcastUseCase.kt | 9 +- .../usecase/StopVoiceBroadcastUseCase.kt | 23 ++-- .../model/VoiceBroadcastEventTest.kt | 123 ++++++++++++++++++ 6 files changed, 217 insertions(+), 46 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastEvent.kt create mode 100644 vector/src/test/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastEventTest.kt diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastEvent.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastEvent.kt new file mode 100644 index 0000000000..c09a5712a8 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastEvent.kt @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.voicebroadcast.model + +import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +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.toModel +import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent + +/** + * [Event] wrapper for [STATE_ROOM_VOICE_BROADCAST_INFO] event type. + * Provides additional fields and functions related to voice broadcast. + */ +@JvmInline +value class VoiceBroadcastEvent(val root: Event) { + + /** + * Reference on the initial voice broadcast state event (ie. with [MessageVoiceBroadcastInfoContent.voiceBroadcastState]=[VoiceBroadcastState.STARTED]). + */ + val reference: RelationDefaultContent? + get() { + val voiceBroadcastInfoContent = root.content.toModel<MessageVoiceBroadcastInfoContent>() + return if (voiceBroadcastInfoContent?.voiceBroadcastState == VoiceBroadcastState.STARTED) { + RelationDefaultContent(RelationType.REFERENCE, root.eventId) + } else { + voiceBroadcastInfoContent?.relatesTo + } + } + + /** + * The mapped [MessageVoiceBroadcastInfoContent] model of the event content. + */ + val content: MessageVoiceBroadcastInfoContent? + get() = root.content.toModel() +} + +/** + * Map a [STATE_ROOM_VOICE_BROADCAST_INFO] state event to a [VoiceBroadcastEvent]. + */ +fun Event.asVoiceBroadcastEvent() = if (type == STATE_ROOM_VOICE_BROADCAST_INFO) VoiceBroadcastEvent(this) else null diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt index a7b544f69e..9c3fb3a3c3 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt @@ -19,12 +19,10 @@ package im.vector.app.features.voicebroadcast.usecase import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState +import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.api.session.events.model.RelationType import org.matrix.android.sdk.api.session.events.model.toContent -import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent import timber.log.Timber @@ -39,28 +37,24 @@ class PauseVoiceBroadcastUseCase @Inject constructor( Timber.d("## PauseVoiceBroadcastUseCase: Pause voice broadcast requested") - val lastVoiceBroadcastEvent = room.stateService().getStateEvent(STATE_ROOM_VOICE_BROADCAST_INFO, QueryStringValue.Equals(session.myUserId)) - val lastVoiceBroadcastInfoContent = lastVoiceBroadcastEvent?.content.toModel<MessageVoiceBroadcastInfoContent>() - when (val voiceBroadcastState = lastVoiceBroadcastInfoContent?.voiceBroadcastState) { + val lastVoiceBroadcastEvent = room.stateService().getStateEvent( + STATE_ROOM_VOICE_BROADCAST_INFO, + QueryStringValue.Equals(session.myUserId) + )?.asVoiceBroadcastEvent() + when (val voiceBroadcastState = lastVoiceBroadcastEvent?.content?.voiceBroadcastState) { VoiceBroadcastState.STARTED, - VoiceBroadcastState.RESUMED -> pauseVoiceBroadcast(room, lastVoiceBroadcastEvent) + VoiceBroadcastState.RESUMED -> pauseVoiceBroadcast(room, lastVoiceBroadcastEvent.reference) else -> Timber.d("## PauseVoiceBroadcastUseCase: Cannot pause voice broadcast: currentState=$voiceBroadcastState") } } - private suspend fun pauseVoiceBroadcast(room: Room, event: Event?) { + private suspend fun pauseVoiceBroadcast(room: Room, reference: RelationDefaultContent?) { Timber.d("## PauseVoiceBroadcastUseCase: Send new voice broadcast info state event") - val lastVoiceBroadcastContent = event?.content.toModel<MessageVoiceBroadcastInfoContent>() - val relatesTo = if (lastVoiceBroadcastContent?.voiceBroadcastState == VoiceBroadcastState.STARTED) { - RelationDefaultContent(RelationType.REFERENCE, event?.eventId) - } else { - lastVoiceBroadcastContent?.relatesTo - } room.stateService().sendStateEvent( eventType = STATE_ROOM_VOICE_BROADCAST_INFO, stateKey = session.myUserId, body = MessageVoiceBroadcastInfoContent( - relatesTo = relatesTo, + relatesTo = reference, voiceBroadcastStateStr = VoiceBroadcastState.PAUSED.value, ).toContent(), ) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCase.kt index 294d91da0f..67839ccc5f 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCase.kt @@ -19,12 +19,10 @@ package im.vector.app.features.voicebroadcast.usecase import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState +import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.api.session.events.model.RelationType import org.matrix.android.sdk.api.session.events.model.toContent -import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent import timber.log.Timber @@ -39,26 +37,29 @@ class ResumeVoiceBroadcastUseCase @Inject constructor( Timber.d("## ResumeVoiceBroadcastUseCase: Resume voice broadcast requested") - val lastVoiceBroadcastEvent = room.stateService().getStateEvent(STATE_ROOM_VOICE_BROADCAST_INFO, QueryStringValue.Equals(session.myUserId)) - when (val voiceBroadcastState = lastVoiceBroadcastEvent?.content.toModel<MessageVoiceBroadcastInfoContent>()?.voiceBroadcastState) { - VoiceBroadcastState.PAUSED -> resumeVoiceBroadcast(room, lastVoiceBroadcastEvent) + val lastVoiceBroadcastEvent = room.stateService().getStateEvent( + STATE_ROOM_VOICE_BROADCAST_INFO, + QueryStringValue.Equals(session.myUserId) + )?.asVoiceBroadcastEvent() + when (val voiceBroadcastState = lastVoiceBroadcastEvent?.content?.voiceBroadcastState) { + VoiceBroadcastState.PAUSED -> resumeVoiceBroadcast(room, lastVoiceBroadcastEvent.reference) else -> Timber.d("## ResumeVoiceBroadcastUseCase: Cannot resume voice broadcast: currentState=$voiceBroadcastState") } } - private suspend fun resumeVoiceBroadcast(room: Room, event: Event?) { + /** + * Resume a paused voice broadcast in the given room. + * + * @param room the room related to the voice broadcast + * @param reference reference on the initial voice broadcast state event (ie. state=STARTED) + */ + private suspend fun resumeVoiceBroadcast(room: Room, reference: RelationDefaultContent?) { Timber.d("## ResumeVoiceBroadcastUseCase: Send new voice broadcast info state event") - val lastVoiceBroadcastContent = event?.content.toModel<MessageVoiceBroadcastInfoContent>() - val relatesTo = if (lastVoiceBroadcastContent?.voiceBroadcastState == VoiceBroadcastState.STARTED) { - RelationDefaultContent(RelationType.REFERENCE, event?.eventId) - } else { - lastVoiceBroadcastContent?.relatesTo - } room.stateService().sendStateEvent( eventType = STATE_ROOM_VOICE_BROADCAST_INFO, stateKey = session.myUserId, body = MessageVoiceBroadcastInfoContent( - relatesTo = relatesTo, + relatesTo = reference, voiceBroadcastStateStr = VoiceBroadcastState.RESUMED.value, ).toContent(), ) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt index 3a9a8e1437..e423a35f26 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt @@ -19,10 +19,10 @@ package im.vector.app.features.voicebroadcast.usecase import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState +import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.toContent -import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.Room import timber.log.Timber import javax.inject.Inject @@ -36,8 +36,11 @@ class StartVoiceBroadcastUseCase @Inject constructor( Timber.d("## StartVoiceBroadcastUseCase: Start voice broadcast requested") - val lastVoiceBroadcastEvent = room.stateService().getStateEvent(STATE_ROOM_VOICE_BROADCAST_INFO, QueryStringValue.Equals(session.myUserId)) - when (val voiceBroadcastState = lastVoiceBroadcastEvent?.content.toModel<MessageVoiceBroadcastInfoContent>()?.voiceBroadcastState) { + val lastVoiceBroadcastEvent = room.stateService().getStateEvent( + STATE_ROOM_VOICE_BROADCAST_INFO, + QueryStringValue.Equals(session.myUserId) + )?.asVoiceBroadcastEvent() + when (val voiceBroadcastState = lastVoiceBroadcastEvent?.content?.voiceBroadcastState) { VoiceBroadcastState.STOPPED, null -> startVoiceBroadcast(room) else -> Timber.d("## StartVoiceBroadcastUseCase: Cannot start voice broadcast: currentState=$voiceBroadcastState") diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt index 5a72f379a3..88a484c5a9 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt @@ -19,12 +19,10 @@ package im.vector.app.features.voicebroadcast.usecase import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState +import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.api.session.events.model.RelationType import org.matrix.android.sdk.api.session.events.model.toContent -import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent import timber.log.Timber @@ -39,28 +37,25 @@ class StopVoiceBroadcastUseCase @Inject constructor( Timber.d("## StopVoiceBroadcastUseCase: Stop voice broadcast requested") - val lastVoiceBroadcastEvent = room.stateService().getStateEvent(STATE_ROOM_VOICE_BROADCAST_INFO, QueryStringValue.Equals(session.myUserId)) - when (val voiceBroadcastState = lastVoiceBroadcastEvent?.content.toModel<MessageVoiceBroadcastInfoContent>()?.voiceBroadcastState) { + val lastVoiceBroadcastEvent = room.stateService().getStateEvent( + STATE_ROOM_VOICE_BROADCAST_INFO, + QueryStringValue.Equals(session.myUserId) + )?.asVoiceBroadcastEvent() + when (val voiceBroadcastState = lastVoiceBroadcastEvent?.content?.voiceBroadcastState) { VoiceBroadcastState.STARTED, VoiceBroadcastState.PAUSED, - VoiceBroadcastState.RESUMED -> stopVoiceBroadcast(room, lastVoiceBroadcastEvent) + VoiceBroadcastState.RESUMED -> stopVoiceBroadcast(room, lastVoiceBroadcastEvent.reference) else -> Timber.d("## StopVoiceBroadcastUseCase: Cannot stop voice broadcast: currentState=$voiceBroadcastState") } } - private suspend fun stopVoiceBroadcast(room: Room, event: Event?) { + private suspend fun stopVoiceBroadcast(room: Room, reference: RelationDefaultContent?) { Timber.d("## StopVoiceBroadcastUseCase: Send new voice broadcast info state event") - val lastVoiceBroadcastContent = event?.content.toModel<MessageVoiceBroadcastInfoContent>() - val relatesTo = if (lastVoiceBroadcastContent?.voiceBroadcastState == VoiceBroadcastState.STARTED) { - RelationDefaultContent(RelationType.REFERENCE, event?.eventId) - } else { - lastVoiceBroadcastContent?.relatesTo - } room.stateService().sendStateEvent( eventType = STATE_ROOM_VOICE_BROADCAST_INFO, stateKey = session.myUserId, body = MessageVoiceBroadcastInfoContent( - relatesTo = relatesTo, + relatesTo = reference, voiceBroadcastStateStr = VoiceBroadcastState.STOPPED.value, ).toContent(), ) diff --git a/vector/src/test/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastEventTest.kt b/vector/src/test/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastEventTest.kt new file mode 100644 index 0000000000..8865e870f0 --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastEventTest.kt @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.voicebroadcast.model + +import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import org.amshove.kluent.shouldBeEqualTo +import org.amshove.kluent.shouldBeNull +import org.amshove.kluent.shouldNotBeNull +import org.junit.Test +import org.matrix.android.sdk.api.session.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.toContent +import org.matrix.android.sdk.api.session.room.model.message.AudioInfo +import org.matrix.android.sdk.api.session.room.model.message.AudioWaveformInfo +import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent +import org.matrix.android.sdk.api.session.room.model.message.MessageType +import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent +import org.matrix.android.sdk.api.session.room.model.relation.ReplyToContent + +private const val AN_EVENT_ID = "event_id" +private const val A_REFERENCED_EVENT_ID = "event_id_ref" +private const val A_CHUNK_LENGTH = 3_600L + +class VoiceBroadcastEventTest { + + @Test + fun `given a started Voice Broadcast Event, when mapping to VoiceBroadcastEvent, then return expected object`() { + // Given + val content = MessageVoiceBroadcastInfoContent( + voiceBroadcastStateStr = VoiceBroadcastState.STARTED.value, + chunkLength = A_CHUNK_LENGTH, + relatesTo = RelationDefaultContent(RelationType.REFERENCE, A_REFERENCED_EVENT_ID), + ) + val event = Event( + eventId = AN_EVENT_ID, + type = STATE_ROOM_VOICE_BROADCAST_INFO, + content = content.toContent(), + ) + val expectedReference = RelationDefaultContent(RelationType.REFERENCE, event.eventId) + + // When + val result = event.asVoiceBroadcastEvent() + + // Then + result.shouldNotBeNull() + result.content shouldBeEqualTo content + result.reference shouldBeEqualTo expectedReference + } + + @Test + fun `given a not started Voice Broadcast Event, when mapping to VoiceBroadcastEvent, then return expected object`() { + // Given + val content = MessageVoiceBroadcastInfoContent( + voiceBroadcastStateStr = VoiceBroadcastState.PAUSED.value, + chunkLength = A_CHUNK_LENGTH, + relatesTo = RelationDefaultContent(RelationType.REFERENCE, A_REFERENCED_EVENT_ID), + ) + val event = Event( + type = STATE_ROOM_VOICE_BROADCAST_INFO, + content = content.toContent(), + ) + val expectedReference = content.relatesTo + + // When + val result = event.asVoiceBroadcastEvent() + + // Then + result.shouldNotBeNull() + result.content shouldBeEqualTo content + result.reference shouldBeEqualTo expectedReference + } + + @Test + fun `given a non Voice Broadcast Event, when mapping to VoiceBroadcastEvent, then return null`() { + // Given + val content = MessageAudioContent( + msgType = MessageType.MSGTYPE_AUDIO, + body = "audio", + audioInfo = AudioInfo( + duration = 300, + mimeType = "", + size = 500L + ), + url = "a_url", + audioWaveformInfo = AudioWaveformInfo( + duration = 300, + waveform = null + ), + voiceMessageIndicator = emptyMap(), + relatesTo = RelationDefaultContent( + type = RelationType.THREAD, + eventId = AN_EVENT_ID, + isFallingBack = true, + inReplyTo = ReplyToContent(eventId = A_REFERENCED_EVENT_ID) + ) + ) + val event = Event( + type = EventType.MESSAGE, + content = content.toContent(), + ) + + // When + val result = event.asVoiceBroadcastEvent() + + // Then + result.shouldBeNull() + } +} From e4e953acf96f4b98bf10d2d15c8381caa1d5a1c7 Mon Sep 17 00:00:00 2001 From: Florian Renaud <florianr@element.io> Date: Tue, 4 Oct 2022 17:50:35 +0200 Subject: [PATCH 159/187] Verify that there is no ongoing voice broadcast before starting a new one --- .../usecase/StartVoiceBroadcastUseCase.kt | 19 ++++---- .../usecase/StartVoiceBroadcastUseCaseTest.kt | 48 ++++++++++--------- .../vector/app/test/fakes/FakeStateService.kt | 5 ++ 3 files changed, 42 insertions(+), 30 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt index e423a35f26..2daf1a412d 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt @@ -36,14 +36,17 @@ class StartVoiceBroadcastUseCase @Inject constructor( Timber.d("## StartVoiceBroadcastUseCase: Start voice broadcast requested") - val lastVoiceBroadcastEvent = room.stateService().getStateEvent( - STATE_ROOM_VOICE_BROADCAST_INFO, - QueryStringValue.Equals(session.myUserId) - )?.asVoiceBroadcastEvent() - when (val voiceBroadcastState = lastVoiceBroadcastEvent?.content?.voiceBroadcastState) { - VoiceBroadcastState.STOPPED, - null -> startVoiceBroadcast(room) - else -> Timber.d("## StartVoiceBroadcastUseCase: Cannot start voice broadcast: currentState=$voiceBroadcastState") + val onGoingVoiceBroadcastEvents = room.stateService().getStateEvents( + setOf(STATE_ROOM_VOICE_BROADCAST_INFO), + QueryStringValue.IsNotEmpty + ) + .mapNotNull { it.asVoiceBroadcastEvent() } + .filter { it.content?.voiceBroadcastState != VoiceBroadcastState.STOPPED } + + if (onGoingVoiceBroadcastEvents.isEmpty()) { + startVoiceBroadcast(room) + } else { + Timber.d("## StartVoiceBroadcastUseCase: Cannot start voice broadcast: currentVoiceBroadcastEvents=$onGoingVoiceBroadcastEvents") } } diff --git a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCaseTest.kt index 3bb04f3a7a..398d6fedf0 100644 --- a/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCaseTest.kt @@ -30,6 +30,7 @@ import kotlinx.coroutines.test.runTest import org.amshove.kluent.shouldBe import org.amshove.kluent.shouldBeNull import org.junit.Test +import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.toContent @@ -37,6 +38,7 @@ import org.matrix.android.sdk.api.session.events.model.toModel private const val A_ROOM_ID = "room_id" private const val AN_EVENT_ID = "event_id" +private const val A_USER_ID = "user_id" class StartVoiceBroadcastUseCaseTest { @@ -45,30 +47,31 @@ class StartVoiceBroadcastUseCaseTest { private val startVoiceBroadcastUseCase = StartVoiceBroadcastUseCase(fakeSession) @Test - fun `given a room id with a potential existing voice broadcast state when calling execute then the voice broadcast is started or not`() = runTest { - val cases = listOf<VoiceBroadcastState?>(null).plus(VoiceBroadcastState.values()).map { - when (it) { - VoiceBroadcastState.STARTED, - VoiceBroadcastState.PAUSED, - VoiceBroadcastState.RESUMED -> Case(it, false) - VoiceBroadcastState.STOPPED, - null -> Case(it, true) - } - } + fun `given a room id with potential several existing voice broadcast states when calling execute then the voice broadcast is started or not`() = runTest { + val cases = VoiceBroadcastState.values() + .flatMap { first -> + VoiceBroadcastState.values().map { second -> + Case( + voiceBroadcasts = listOf(VoiceBroadcast(fakeSession.myUserId, first), VoiceBroadcast(A_USER_ID, second)), + canStartVoiceBroadcast = first == VoiceBroadcastState.STOPPED && second == VoiceBroadcastState.STOPPED + ) + } + } + .plus(Case(emptyList(), true)) cases.forEach { case -> if (case.canStartVoiceBroadcast) { - testVoiceBroadcastStarted(case.previousState) + testVoiceBroadcastStarted(case.voiceBroadcasts) } else { - testVoiceBroadcastNotStarted(case.previousState) + testVoiceBroadcastNotStarted(case.voiceBroadcasts) } } } - private suspend fun testVoiceBroadcastStarted(previousState: VoiceBroadcastState?) { + private suspend fun testVoiceBroadcastStarted(voiceBroadcasts: List<VoiceBroadcast>) { // Given clearAllMocks() - givenAVoiceBroadcastState(previousState) + givenAVoiceBroadcasts(voiceBroadcasts) val voiceBroadcastInfoContentInterceptor = slot<Content>() coEvery { fakeRoom.stateService().sendStateEvent(any(), any(), capture(voiceBroadcastInfoContentInterceptor)) } coAnswers { AN_EVENT_ID } @@ -88,10 +91,10 @@ class StartVoiceBroadcastUseCaseTest { voiceBroadcastInfoContent?.relatesTo.shouldBeNull() } - private suspend fun testVoiceBroadcastNotStarted(previousState: VoiceBroadcastState?) { + private suspend fun testVoiceBroadcastNotStarted(voiceBroadcasts: List<VoiceBroadcast>) { // Given clearAllMocks() - givenAVoiceBroadcastState(previousState) + givenAVoiceBroadcasts(voiceBroadcasts) // When startVoiceBroadcastUseCase.execute(A_ROOM_ID) @@ -100,18 +103,19 @@ class StartVoiceBroadcastUseCaseTest { coVerify(exactly = 0) { fakeRoom.stateService().sendStateEvent(any(), any(), any()) } } - private fun givenAVoiceBroadcastState(state: VoiceBroadcastState?) { - val event = state?.let { + private fun givenAVoiceBroadcasts(voiceBroadcasts: List<VoiceBroadcast>) { + val events = voiceBroadcasts.map { Event( type = STATE_ROOM_VOICE_BROADCAST_INFO, - stateKey = fakeSession.myUserId, + stateKey = it.userId, content = MessageVoiceBroadcastInfoContent( - voiceBroadcastStateStr = state.value + voiceBroadcastStateStr = it.state.value ).toContent() ) } - fakeRoom.stateService().givenGetStateEvent(event) + fakeRoom.stateService().givenGetStateEvents(QueryStringValue.IsNotEmpty, events) } - private data class Case(val previousState: VoiceBroadcastState?, val canStartVoiceBroadcast: Boolean) + private data class VoiceBroadcast(val userId: String, val state: VoiceBroadcastState) + private data class Case(val voiceBroadcasts: List<VoiceBroadcast>, val canStartVoiceBroadcast: Boolean) } diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeStateService.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeStateService.kt index 03a0cf07d8..7c393c7a57 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeStateService.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeStateService.kt @@ -18,11 +18,16 @@ package im.vector.app.test.fakes import io.mockk.every import io.mockk.mockk +import org.matrix.android.sdk.api.query.QueryStateEventValue import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.state.StateService class FakeStateService : StateService by mockk(relaxed = true) { + fun givenGetStateEvents(stateKey: QueryStateEventValue, result: List<Event>) { + every { getStateEvents(any(), stateKey) } returns result + } + fun givenGetStateEvent(event: Event?) { every { getStateEvent(any(), any()) } returns event } From 01e0383dd50924a04f4134366fe725353d71d8d1 Mon Sep 17 00:00:00 2001 From: Florian Renaud <florianr@element.io> Date: Tue, 4 Oct 2022 17:57:48 +0200 Subject: [PATCH 160/187] Reorder VoiceBroadcastAction switch cases --- .../vector/app/features/home/room/detail/TimelineViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt index b251facb90..511fd597fe 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt @@ -604,9 +604,9 @@ class TimelineViewModel @AssistedInject constructor( if (room == null) return viewModelScope.launch { when (action) { + RoomDetailAction.VoiceBroadcastAction.Start -> voiceBroadcastHelper.startVoiceBroadcast(room.roomId) RoomDetailAction.VoiceBroadcastAction.Pause -> voiceBroadcastHelper.pauseVoiceBroadcast(room.roomId) RoomDetailAction.VoiceBroadcastAction.Resume -> voiceBroadcastHelper.resumeVoiceBroadcast(room.roomId) - RoomDetailAction.VoiceBroadcastAction.Start -> voiceBroadcastHelper.startVoiceBroadcast(room.roomId) RoomDetailAction.VoiceBroadcastAction.Stop -> voiceBroadcastHelper.stopVoiceBroadcast(room.roomId) } } From 05b8f7d3750540ddd2978be675852fbacf16e71b Mon Sep 17 00:00:00 2001 From: Florian Renaud <florianr@element.io> Date: Tue, 4 Oct 2022 23:54:05 +0200 Subject: [PATCH 161/187] Create TimelineEvent.getVectorLastMessageContent() in vector module --- .../vector/app/core/extensions/TimelineEvent.kt | 17 +++++++++++++++++ .../detail/composer/MessageComposerFragment.kt | 4 ++-- .../detail/composer/MessageComposerViewModel.kt | 4 ++-- .../timeline/action/MessageActionsViewModel.kt | 6 +++--- .../timeline/factory/MessageItemFactory.kt | 17 ++--------------- .../format/DisplayableEventFormatter.kt | 4 ++-- .../helper/MessageInformationDataFactory.kt | 10 +++++++--- .../style/TimelineMessageLayoutFactory.kt | 8 ++++---- .../notifications/NotifiableEventResolver.kt | 4 ++-- .../features/poll/create/CreatePollViewModel.kt | 4 ++-- 10 files changed, 43 insertions(+), 35 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/extensions/TimelineEvent.kt b/vector/src/main/java/im/vector/app/core/extensions/TimelineEvent.kt index 28c1587b1a..cdb84387ce 100644 --- a/vector/src/main/java/im/vector/app/core/extensions/TimelineEvent.kt +++ b/vector/src/main/java/im/vector/app/core/extensions/TimelineEvent.kt @@ -16,9 +16,14 @@ package im.vector.app.core.extensions +import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import org.matrix.android.sdk.api.session.events.model.EventType +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.send.SendState import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent fun TimelineEvent.canReact(): Boolean { // Only event of type EventType.MESSAGE, EventType.STICKER and EventType.POLL_START are supported for the moment @@ -26,3 +31,15 @@ fun TimelineEvent.canReact(): Boolean { root.sendState == SendState.SYNCED && !root.isRedacted() } + +/** + * Get last MessageContent, after a possible edition. + * This method iterate on the vector event types and fallback to [getLastMessageContent] from the matrix sdk for the other types. + */ +fun TimelineEvent.getVectorLastMessageContent(): MessageContent? { + // Iterate on event types which are not part of the matrix sdk, otherwise fallback to the sdk method + return when (root.getClearType()) { + STATE_ROOM_VOICE_BROADCAST_INFO -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel<MessageVoiceBroadcastInfoContent>() + else -> getLastMessageContent() + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt index 509e8cc4af..3f58a4a184 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt @@ -48,6 +48,7 @@ import com.vanniktech.emoji.EmojiPopup import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.error.fatalError +import im.vector.app.core.extensions.getVectorLastMessageContent import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.extensions.showKeyboard import im.vector.app.core.glide.GlideApp @@ -103,7 +104,6 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageFormat import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent -import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.toMatrixItem import reactivecircus.flowbinding.android.view.focusChanges @@ -356,7 +356,7 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A setTextColor(matrixItemColorProvider.getColor(MatrixItem.UserItem(event.root.senderId ?: "@"))) } - val messageContent: MessageContent? = event.getLastMessageContent() + val messageContent: MessageContent? = event.getVectorLastMessageContent() val nonFormattedBody = when (messageContent) { is MessageAudioContent -> getAudioContentBodyText(messageContent) is MessagePollContent -> messageContent.getBestPollCreationInfo()?.question?.getBestQuestion() 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 afdd01ba46..c83f818ac8 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 @@ -23,6 +23,7 @@ import dagger.assisted.AssistedInject import im.vector.app.R import im.vector.app.core.di.MavericksAssistedViewModelFactory 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.features.analytics.AnalyticsTracker @@ -62,7 +63,6 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageType import org.matrix.android.sdk.api.session.room.model.relation.shouldRenderInThread import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.room.send.UserDraft -import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent import org.matrix.android.sdk.api.session.room.timeline.getRelationContent import org.matrix.android.sdk.api.session.room.timeline.getTextEditableContent import org.matrix.android.sdk.api.session.space.CreateSpaceParams @@ -513,7 +513,7 @@ class MessageComposerViewModel @AssistedInject constructor( room.relationService().editReply(state.sendMode.timelineEvent, it, action.text.toString()) } } else { - val messageContent = state.sendMode.timelineEvent.getLastMessageContent() + val messageContent = state.sendMode.timelineEvent.getVectorLastMessageContent() val existingBody = messageContent?.body ?: "" if (existingBody != action.text) { room.relationService().editTextMessage( 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 3dfb6744e0..0c44ee386d 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 @@ -25,6 +25,7 @@ import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.extensions.canReact +import im.vector.app.core.extensions.getVectorLastMessageContent import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider @@ -60,7 +61,6 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachme import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper 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.getLastMessageContent import org.matrix.android.sdk.api.session.room.timeline.hasBeenEdited import org.matrix.android.sdk.api.session.room.timeline.isPoll import org.matrix.android.sdk.api.session.room.timeline.isRootThread @@ -187,7 +187,7 @@ class MessageActionsViewModel @AssistedInject constructor( when (timelineEvent.root.getClearType()) { EventType.MESSAGE, EventType.STICKER -> { - val messageContent: MessageContent? = timelineEvent.getLastMessageContent() + val messageContent: MessageContent? = timelineEvent.getVectorLastMessageContent() if (messageContent is MessageTextContent && messageContent.format == MessageFormat.FORMAT_MATRIX_HTML) { val html = messageContent.formattedBody ?.takeIf { it.isNotBlank() } @@ -253,7 +253,7 @@ class MessageActionsViewModel @AssistedInject constructor( } private fun actionsForEvent(timelineEvent: TimelineEvent, actionPermissions: ActionPermissions): List<EventSharedAction> { - val messageContent = timelineEvent.getLastMessageContent() + val messageContent = timelineEvent.getVectorLastMessageContent() val msgType = messageContent?.msgType return arrayListOf<EventSharedAction>().apply { 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 0fbd1033c7..12fd286054 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 @@ -28,6 +28,7 @@ import dagger.Lazy import im.vector.app.R import im.vector.app.core.epoxy.ClickListener import im.vector.app.core.epoxy.VectorEpoxyModel +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 @@ -79,7 +80,6 @@ import im.vector.app.features.media.ImageContentRenderer import im.vector.app.features.media.VideoContentRenderer import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.voice.AudioWaveformView -import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence import me.gujun.android.span.span @@ -106,8 +106,6 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageVerification import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent import org.matrix.android.sdk.api.session.room.model.message.getFileUrl import org.matrix.android.sdk.api.session.room.model.message.getThumbnailUrl -import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent -import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent import org.matrix.android.sdk.api.settings.LightweightSettingsStorage import org.matrix.android.sdk.api.util.MimeTypes import javax.inject.Inject @@ -168,7 +166,7 @@ class MessageItemFactory @Inject constructor( return buildRedactedItem(attributes, highlight) } - val messageContent = getLastMessageContent(event) + val messageContent = event.getVectorLastMessageContent() if (messageContent == null) { val malformedText = stringProvider.getString(R.string.malformed_message) return defaultItemFactory.create(malformedText, informationData, highlight, callback) @@ -210,17 +208,6 @@ class MessageItemFactory @Inject constructor( } } - private fun getLastMessageContent(event: TimelineEvent): MessageContent? { - return with(event) { - // Iterate on event types which are not part of the matrix sdk, otherwise fallback to the sdk method - when (root.getClearType()) { - STATE_ROOM_VOICE_BROADCAST_INFO -> - (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel<MessageVoiceBroadcastInfoContent>() - else -> event.getLastMessageContent() - } - } - } - private fun buildLocationItem( locationContent: MessageLocationContent, informationData: MessageInformationData, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt index 7b9bd4530b..eb531b6f1b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt @@ -19,6 +19,7 @@ package im.vector.app.features.home.room.detail.timeline.format import dagger.Lazy import im.vector.app.EmojiSpanify import im.vector.app.R +import im.vector.app.core.extensions.getVectorLastMessageContent import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.StringProvider import im.vector.app.features.html.EventHtmlRenderer @@ -34,7 +35,6 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent import org.matrix.android.sdk.api.session.room.model.message.MessageType import org.matrix.android.sdk.api.session.room.model.relation.ReactionContent import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent -import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent import org.matrix.android.sdk.api.session.room.timeline.getTextDisplayableContent import javax.inject.Inject @@ -60,7 +60,7 @@ class DisplayableEventFormatter @Inject constructor( return when (timelineEvent.root.getClearType()) { EventType.MESSAGE -> { - timelineEvent.getLastMessageContent()?.let { messageContent -> + timelineEvent.getVectorLastMessageContent()?.let { messageContent -> when (messageContent.msgType) { MessageType.MSGTYPE_TEXT -> { val body = messageContent.getTextDisplayableContent() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt index ddb98c42c6..50b4366e98 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt @@ -18,6 +18,7 @@ package im.vector.app.features.home.room.detail.timeline.helper import im.vector.app.core.date.DateFormatKind import im.vector.app.core.date.VectorDateFormatter +import im.vector.app.core.extensions.getVectorLastMessageContent import im.vector.app.core.extensions.localDateTime import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFactoryParams import im.vector.app.features.home.room.detail.timeline.item.E2EDecoration @@ -41,7 +42,6 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageType import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationRequestContent 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.getLastMessageContent import org.matrix.android.sdk.api.session.room.timeline.hasBeenEdited import javax.inject.Inject @@ -123,7 +123,11 @@ class MessageInformationDataFactory @Inject constructor( isLastFromThisSender = isLastFromThisSender, e2eDecoration = e2eDecoration, sendStateDecoration = sendStateDecoration, - messageType = if (event.root.isSticker()) { MessageType.MSGTYPE_STICKER_LOCAL } else { event.root.getMsgType() } + messageType = if (event.root.isSticker()) { + MessageType.MSGTYPE_STICKER_LOCAL + } else { + event.root.getMsgType() + } ) } @@ -230,7 +234,7 @@ class MessageInformationDataFactory @Inject constructor( EventType.KEY_VERIFICATION_DONE, EventType.KEY_VERIFICATION_CANCEL -> true EventType.MESSAGE -> { - event.getLastMessageContent() is MessageVerificationRequestContent + event.getVectorLastMessageContent() is MessageVerificationRequestContent } else -> false } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt index 14a02c7172..379e5b3b91 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt @@ -18,6 +18,7 @@ package im.vector.app.features.home.room.detail.timeline.style import android.content.res.Resources import im.vector.app.R +import im.vector.app.core.extensions.getVectorLastMessageContent import im.vector.app.core.extensions.localDateTime import im.vector.app.core.resources.LocaleProvider import im.vector.app.core.resources.isRTL @@ -29,7 +30,6 @@ 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.model.message.MessageVerificationRequestContent import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent -import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent import org.matrix.android.sdk.api.session.room.timeline.isEdition import org.matrix.android.sdk.api.session.room.timeline.isRootThread import javax.inject.Inject @@ -126,7 +126,7 @@ class TimelineMessageLayoutFactory @Inject constructor( isLastFromThisSender = isLastFromThisSender ) - val messageContent = event.getLastMessageContent() + val messageContent = event.getVectorLastMessageContent() TimelineMessageLayout.Bubble( showAvatar = showInformation && !isSentByMe, showDisplayName = showInformation && !isSentByMe, @@ -167,7 +167,7 @@ class TimelineMessageLayoutFactory @Inject constructor( private fun TimelineEvent.shouldBuildBubbleLayout(): Boolean { val type = root.getClearType() if (type in EVENT_TYPES_WITH_BUBBLE_LAYOUT) { - val messageContent = getLastMessageContent() + val messageContent = getVectorLastMessageContent() return messageContent?.msgType !in MSG_TYPES_WITHOUT_BUBBLE_LAYOUT } return false @@ -212,7 +212,7 @@ class TimelineMessageLayoutFactory @Inject constructor( EventType.KEY_VERIFICATION_DONE, EventType.KEY_VERIFICATION_CANCEL -> true EventType.MESSAGE -> { - event.getLastMessageContent() is MessageVerificationRequestContent + event.getVectorLastMessageContent() is MessageVerificationRequestContent } else -> false } 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 90138fd495..4ee7da4b64 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 @@ -17,6 +17,7 @@ package im.vector.app.features.notifications import android.net.Uri import im.vector.app.R +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 @@ -45,7 +46,6 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachme import org.matrix.android.sdk.api.session.room.sender.SenderInfo import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.getEditedEventId -import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent import org.matrix.android.sdk.api.util.toMatrixItem import timber.log.Timber import java.util.UUID @@ -231,7 +231,7 @@ class NotifiableEventResolver @Inject constructor( private suspend fun TimelineEvent.downloadAndExportImage(session: Session): Uri? { return kotlin.runCatching { - getLastMessageContent()?.takeAs<MessageWithAttachmentContent>()?.let { imageMessage -> + getVectorLastMessageContent()?.takeAs<MessageWithAttachmentContent>()?.let { imageMessage -> val fileService = session.fileService() fileService.downloadFile(imageMessage) fileService.getTemporarySharableURI(imageMessage) diff --git a/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewModel.kt b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewModel.kt index ec064877a9..1b2f0d7d08 100644 --- a/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollViewModel.kt @@ -22,6 +22,7 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.core.di.MavericksAssistedViewModelFactory 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.features.poll.PollMode import org.matrix.android.sdk.api.session.Session @@ -29,7 +30,6 @@ 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.message.PollType -import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent class CreatePollViewModel @AssistedInject constructor( @Assisted private val initialState: CreatePollViewState, @@ -72,7 +72,7 @@ class CreatePollViewModel @AssistedInject constructor( private fun initializeEditedPoll(eventId: String) { val event = room.getTimelineEvent(eventId) ?: return - val content = event.getLastMessageContent() as? MessagePollContent ?: return + val content = event.getVectorLastMessageContent() as? MessagePollContent ?: return val pollCreationInfo = content.getBestPollCreationInfo() val pollType = pollCreationInfo?.kind ?: PollType.DISCLOSED_UNSTABLE From 521af709887a01fbb8fe29d1c8246bc38d42e0eb Mon Sep 17 00:00:00 2001 From: Florian Renaud <florianr@element.io> Date: Tue, 4 Oct 2022 23:55:26 +0200 Subject: [PATCH 162/187] Pass VoiceBroadcastState in MessageVoiceBroadcastItem --- .../home/room/detail/timeline/factory/MessageItemFactory.kt | 2 +- .../room/detail/timeline/item/MessageVoiceBroadcastItem.kt | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) 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 12fd286054..e7833c2824 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 @@ -719,7 +719,7 @@ class MessageItemFactory @Inject constructor( return MessageVoiceBroadcastItem_() .attributes(attributes) .highlighted(highlight) - .playingState(messageContent.voiceBroadcastStateStr.toEpoxyCharSequence()) + .voiceBroadcastState(messageContent.voiceBroadcastState) .leftGuideline(avatarSizeProvider.leftGuideline) .callback(callback) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastItem.kt index 0476ea2a93..bd6a5b7bdb 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastItem.kt @@ -25,7 +25,6 @@ import im.vector.app.R import im.vector.app.features.home.room.detail.RoomDetailAction import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState -import im.vector.lib.core.utils.epoxy.charsequence.EpoxyCharSequence @EpoxyModelClass abstract class MessageVoiceBroadcastItem : AbsMessageItem<MessageVoiceBroadcastItem.Holder>() { @@ -34,7 +33,7 @@ abstract class MessageVoiceBroadcastItem : AbsMessageItem<MessageVoiceBroadcastI var callback: TimelineEventController.Callback? = null @EpoxyAttribute - var playingState: EpoxyCharSequence? = null + var voiceBroadcastState: VoiceBroadcastState? = null override fun bind(holder: Holder) { super.bind(holder) @@ -44,8 +43,7 @@ abstract class MessageVoiceBroadcastItem : AbsMessageItem<MessageVoiceBroadcastI @SuppressLint("SetTextI18n") // Temporary text private fun bindVoiceBroadcastItem(holder: Holder) { with(holder) { - currentStateText.text = "Voice Broadcast state: ${playingState?.charSequence ?: "None"}" - val voiceBroadcastState = VoiceBroadcastState.values().find { it.value == playingState?.charSequence } + currentStateText.text = "Voice Broadcast state: ${voiceBroadcastState?.value ?: "None"}" playButton.isEnabled = voiceBroadcastState == VoiceBroadcastState.PAUSED pauseButton.isEnabled = voiceBroadcastState == VoiceBroadcastState.STARTED || voiceBroadcastState == VoiceBroadcastState.RESUMED stopButton.isEnabled = voiceBroadcastState == VoiceBroadcastState.STARTED || From 30f33d36de361f1de587cfd1b1b45857711a2266 Mon Sep 17 00:00:00 2001 From: Florian Renaud <florianr@element.io> Date: Wed, 5 Oct 2022 00:01:04 +0200 Subject: [PATCH 163/187] Use session.getRoom(roomId) --- .../voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt | 3 ++- .../voicebroadcast/usecase/ResumeVoiceBroadcastUseCase.kt | 3 ++- .../voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt | 3 ++- .../voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt index 9c3fb3a3c3..eaa7392140 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt @@ -23,6 +23,7 @@ import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent import timber.log.Timber @@ -33,7 +34,7 @@ class PauseVoiceBroadcastUseCase @Inject constructor( ) { suspend fun execute(roomId: String) { - val room = session.roomService().getRoom(roomId) ?: return + val room = session.getRoom(roomId) ?: return Timber.d("## PauseVoiceBroadcastUseCase: Pause voice broadcast requested") diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCase.kt index 67839ccc5f..645496bd7d 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCase.kt @@ -23,6 +23,7 @@ import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent import timber.log.Timber @@ -33,7 +34,7 @@ class ResumeVoiceBroadcastUseCase @Inject constructor( ) { suspend fun execute(roomId: String) { - val room = session.roomService().getRoom(roomId) ?: return + val room = session.getRoom(roomId) ?: return Timber.d("## ResumeVoiceBroadcastUseCase: Resume voice broadcast requested") diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt index 2daf1a412d..dd7bb8cdf4 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt @@ -23,6 +23,7 @@ import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.Room import timber.log.Timber import javax.inject.Inject @@ -32,7 +33,7 @@ class StartVoiceBroadcastUseCase @Inject constructor( ) { suspend fun execute(roomId: String) { - val room = session.roomService().getRoom(roomId) ?: return + val room = session.getRoom(roomId) ?: return Timber.d("## StartVoiceBroadcastUseCase: Start voice broadcast requested") diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt index 88a484c5a9..dee822e880 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt @@ -23,6 +23,7 @@ import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent import timber.log.Timber @@ -33,7 +34,7 @@ class StopVoiceBroadcastUseCase @Inject constructor( ) { suspend fun execute(roomId: String) { - val room = session.roomService().getRoom(roomId) ?: return + val room = session.getRoom(roomId) ?: return Timber.d("## StopVoiceBroadcastUseCase: Stop voice broadcast requested") From b286a52f5b107a15833b54bfc71c0f017b2cf22c Mon Sep 17 00:00:00 2001 From: Florian Renaud <florianr@element.io> Date: Wed, 5 Oct 2022 00:28:42 +0200 Subject: [PATCH 164/187] Catch error and return Result in useCases --- .../voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt | 4 ++-- .../voicebroadcast/usecase/ResumeVoiceBroadcastUseCase.kt | 4 ++-- .../voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt | 4 ++-- .../voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt index eaa7392140..8f61284423 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt @@ -33,8 +33,8 @@ class PauseVoiceBroadcastUseCase @Inject constructor( private val session: Session, ) { - suspend fun execute(roomId: String) { - val room = session.getRoom(roomId) ?: return + suspend fun execute(roomId: String): Result<Unit> = runCatching { + val room = session.getRoom(roomId) ?: error("Unknown roomId: $roomId") Timber.d("## PauseVoiceBroadcastUseCase: Pause voice broadcast requested") diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCase.kt index 645496bd7d..d0d82b42c3 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCase.kt @@ -33,8 +33,8 @@ class ResumeVoiceBroadcastUseCase @Inject constructor( private val session: Session, ) { - suspend fun execute(roomId: String) { - val room = session.getRoom(roomId) ?: return + suspend fun execute(roomId: String): Result<Unit> = runCatching { + val room = session.getRoom(roomId) ?: error("Unknown roomId: $roomId") Timber.d("## ResumeVoiceBroadcastUseCase: Resume voice broadcast requested") diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt index dd7bb8cdf4..0b8328cd4b 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt @@ -32,8 +32,8 @@ class StartVoiceBroadcastUseCase @Inject constructor( private val session: Session, ) { - suspend fun execute(roomId: String) { - val room = session.getRoom(roomId) ?: return + suspend fun execute(roomId: String): Result<Unit> = runCatching { + val room = session.getRoom(roomId) ?: error("Unknown roomId: $roomId") Timber.d("## StartVoiceBroadcastUseCase: Start voice broadcast requested") diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt index dee822e880..8b22193770 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt @@ -33,8 +33,8 @@ class StopVoiceBroadcastUseCase @Inject constructor( private val session: Session, ) { - suspend fun execute(roomId: String) { - val room = session.getRoom(roomId) ?: return + suspend fun execute(roomId: String): Result<Unit> = runCatching { + val room = session.getRoom(roomId) ?: error("Unknown roomId: $roomId") Timber.d("## StopVoiceBroadcastUseCase: Stop voice broadcast requested") From cc1bb234060f6f2d36eb7ecfff971a52558eab86 Mon Sep 17 00:00:00 2001 From: Florian Renaud <florianr@element.io> Date: Tue, 4 Oct 2022 11:22:20 +0200 Subject: [PATCH 165/187] Aggregate voice broadcast state events --- .../timeline/factory/MessageItemFactory.kt | 12 ++++++-- .../timeline/helper/TimelineEventsGroups.kt | 29 +++++++++++++++---- .../item/MessageVoiceBroadcastItem.kt | 2 ++ 3 files changed, 35 insertions(+), 8 deletions(-) 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 e7833c2824..06da69fc1a 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 @@ -43,7 +43,9 @@ import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStat import im.vector.app.features.home.room.detail.timeline.helper.LocationPinProvider import im.vector.app.features.home.room.detail.timeline.helper.MessageInformationDataFactory import im.vector.app.features.home.room.detail.timeline.helper.MessageItemAttributesFactory +import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventsGroup import im.vector.app.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider +import im.vector.app.features.home.room.detail.timeline.helper.VoiceBroadcastEventsGroup import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem import im.vector.app.features.home.room.detail.timeline.item.MessageAudioItem import im.vector.app.features.home.room.detail.timeline.item.MessageAudioItem_ @@ -81,6 +83,7 @@ import im.vector.app.features.media.VideoContentRenderer import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.voice.AudioWaveformView import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent +import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence import me.gujun.android.span.span import org.matrix.android.sdk.api.MatrixUrls.isMxcUrl @@ -200,7 +203,7 @@ class MessageItemFactory @Inject constructor( is MessagePollContent -> buildPollItem(messageContent, informationData, highlight, callback, attributes) is MessageLocationContent -> buildLocationItem(messageContent, informationData, highlight, attributes) is MessageBeaconInfoContent -> liveLocationShareMessageItemFactory.create(params.event, highlight, attributes) - is MessageVoiceBroadcastInfoContent -> buildVoiceBroadcastItem(messageContent, highlight, callback, attributes) + is MessageVoiceBroadcastInfoContent -> buildVoiceBroadcastItem(messageContent, params.eventsGroup, highlight, callback, attributes) else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes) } return messageItem?.apply { @@ -712,14 +715,19 @@ class MessageItemFactory @Inject constructor( private fun buildVoiceBroadcastItem( messageContent: MessageVoiceBroadcastInfoContent, + eventsGroup: TimelineEventsGroup?, highlight: Boolean, callback: TimelineEventController.Callback?, attributes: AbsMessageItem.Attributes, ): MessageVoiceBroadcastItem? { + if (messageContent.voiceBroadcastState != VoiceBroadcastState.STARTED) return null + val voiceBroadcastEventsGroup = eventsGroup?.let { VoiceBroadcastEventsGroup(it) } ?: return null + val mostRecentEvent = voiceBroadcastEventsGroup.getLastEvent() + val mostRecentMessageContent = (mostRecentEvent.getVectorLastMessageContent() as? MessageVoiceBroadcastInfoContent) ?: return null return MessageVoiceBroadcastItem_() .attributes(attributes) .highlighted(highlight) - .voiceBroadcastState(messageContent.voiceBroadcastState) + .voiceBroadcastState(mostRecentMessageContent.voiceBroadcastState) .leftGuideline(avatarSizeProvider.leftGuideline) .callback(callback) } 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 4ff8a9fa43..c25bc83fee 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 @@ -17,6 +17,9 @@ package im.vector.app.features.home.room.detail.timeline.helper import im.vector.app.core.utils.TextUtils +import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent +import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState 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.toModel @@ -54,12 +57,18 @@ class TimelineEventsGroups { private fun TimelineEvent.getGroupIdOrNull(): String? { val type = root.getClearType() val content = root.getClearContent() - return if (EventType.isCallEvent(type)) { - (content?.get("call_id") as? String) - } else if (type == EventType.STATE_ROOM_WIDGET || type == EventType.STATE_ROOM_WIDGET_LEGACY) { - root.stateKey - } else { - null + return when { + EventType.isCallEvent(type) -> (content?.get("call_id") as? String) + type == STATE_ROOM_VOICE_BROADCAST_INFO -> { + root.content.toModel<MessageVoiceBroadcastInfoContent>() + ?.takeUnless { it.voiceBroadcastState == VoiceBroadcastState.STARTED } + ?.relatesTo?.eventId + ?: eventId + } + type == EventType.STATE_ROOM_WIDGET || type == EventType.STATE_ROOM_WIDGET_LEGACY -> root.stateKey + else -> { + null + } } } @@ -128,3 +137,11 @@ class CallSignalingEventsGroup(private val group: TimelineEventsGroup) { return group.events.firstOrNull { it.root.getClearType() == EventType.CALL_REJECT } } } + +class VoiceBroadcastEventsGroup(private val group: TimelineEventsGroup) { + fun getLastEvent(): TimelineEvent { + return group.events + .find { it.root.getClearContent().toModel<MessageVoiceBroadcastInfoContent>()?.voiceBroadcastState == VoiceBroadcastState.STOPPED } + ?: group.events.maxBy { it.root.originServerTs ?: 0L } + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastItem.kt index bd6a5b7bdb..14a4fc6b07 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageVoiceBroadcastItem.kt @@ -35,6 +35,8 @@ abstract class MessageVoiceBroadcastItem : AbsMessageItem<MessageVoiceBroadcastI @EpoxyAttribute var voiceBroadcastState: VoiceBroadcastState? = null + override fun isCacheable(): Boolean = false + override fun bind(holder: Holder) { super.bind(holder) bindVoiceBroadcastItem(holder) From 8d3d275921835f9f8fda73e20c9ff75d25cfa65c Mon Sep 17 00:00:00 2001 From: Florian Renaud <florianr@element.io> Date: Tue, 4 Oct 2022 11:44:54 +0200 Subject: [PATCH 166/187] Add changelog file --- changelog.d/7283.wip | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7283.wip diff --git a/changelog.d/7283.wip b/changelog.d/7283.wip new file mode 100644 index 0000000000..f7cbd323f1 --- /dev/null +++ b/changelog.d/7283.wip @@ -0,0 +1 @@ +[Voice Broadcast] Aggregate state events in the timeline From 0a047b60459db10f77b46dcb736bd953dfc1971b Mon Sep 17 00:00:00 2001 From: Florian Renaud <florianr@element.io> Date: Wed, 5 Oct 2022 15:44:58 +0200 Subject: [PATCH 167/187] Use VoiceBroadcastEvent wrapper --- .../detail/timeline/helper/TimelineEventsGroups.kt | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) 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 c25bc83fee..bd211a4513 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 @@ -18,8 +18,8 @@ package im.vector.app.features.home.room.detail.timeline.helper import im.vector.app.core.utils.TextUtils import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO -import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent 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.toModel @@ -59,12 +59,7 @@ class TimelineEventsGroups { val content = root.getClearContent() return when { EventType.isCallEvent(type) -> (content?.get("call_id") as? String) - type == STATE_ROOM_VOICE_BROADCAST_INFO -> { - root.content.toModel<MessageVoiceBroadcastInfoContent>() - ?.takeUnless { it.voiceBroadcastState == VoiceBroadcastState.STARTED } - ?.relatesTo?.eventId - ?: eventId - } + type == STATE_ROOM_VOICE_BROADCAST_INFO -> root.asVoiceBroadcastEvent()?.reference?.eventId type == EventType.STATE_ROOM_WIDGET || type == EventType.STATE_ROOM_WIDGET_LEGACY -> root.stateKey else -> { null @@ -140,8 +135,7 @@ class CallSignalingEventsGroup(private val group: TimelineEventsGroup) { class VoiceBroadcastEventsGroup(private val group: TimelineEventsGroup) { fun getLastEvent(): TimelineEvent { - return group.events - .find { it.root.getClearContent().toModel<MessageVoiceBroadcastInfoContent>()?.voiceBroadcastState == VoiceBroadcastState.STOPPED } + return group.events.find { it.root.asVoiceBroadcastEvent()?.content?.voiceBroadcastState == VoiceBroadcastState.STOPPED } ?: group.events.maxBy { it.root.originServerTs ?: 0L } } } From e08028378b53a68c3c5fcc62e91998c9025f5808 Mon Sep 17 00:00:00 2001 From: Benoit Marty <benoit@matrix.org> Date: Fri, 7 Oct 2022 11:26:37 +0200 Subject: [PATCH 168/187] Fix broken Breadcrumbs --- .../app/features/home/room/detail/TimelineFragment.kt | 6 +++--- .../home/room/detail/composer/MessageComposerFragment.kt | 6 +++--- .../room/detail/composer/voice/VoiceRecorderFragment.kt | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) 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 7ea837c035..b02fed8445 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 @@ -53,8 +53,8 @@ import com.airbnb.epoxy.OnModelBuildFinishedListener import com.airbnb.epoxy.addGlidePreloader import com.airbnb.epoxy.glidePreloader import com.airbnb.mvrx.Fail -import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.args +import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint @@ -256,8 +256,8 @@ class TimelineFragment : private val timelineArgs: TimelineArgs by args() - private val timelineViewModel: TimelineViewModel by activityViewModel() - private val messageComposerViewModel: MessageComposerViewModel by activityViewModel() + private val timelineViewModel: TimelineViewModel by fragmentViewModel() + private val messageComposerViewModel: MessageComposerViewModel by fragmentViewModel() private val debouncer = Debouncer(createUIHandler()) private lateinit var scrollOnNewMessageCallback: ScrollOnNewMessageCallback diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt index 21a87f092f..e6d5e1e4a2 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt @@ -41,7 +41,7 @@ import androidx.core.view.isInvisible import androidx.core.view.isVisible import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope -import com.airbnb.mvrx.activityViewModel +import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.vanniktech.emoji.EmojiPopup @@ -156,8 +156,8 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A private lateinit var attachmentsHelper: AttachmentsHelper private lateinit var attachmentTypeSelector: AttachmentTypeSelectorView - private val timelineViewModel: TimelineViewModel by activityViewModel() - private val messageComposerViewModel: MessageComposerViewModel by activityViewModel() + private val timelineViewModel: TimelineViewModel by parentFragmentViewModel() + private val messageComposerViewModel: MessageComposerViewModel by parentFragmentViewModel() private lateinit var sharedActionViewModel: MessageSharedActionViewModel override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentComposerBinding { 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 ef253f87a6..4a4f025688 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 @@ -21,7 +21,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.core.view.isVisible -import com.airbnb.mvrx.activityViewModel +import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R @@ -46,8 +46,8 @@ class VoiceRecorderFragment : VectorBaseFragment<FragmentVoiceRecorderBinding>() @Inject lateinit var audioMessagePlaybackTracker: AudioMessagePlaybackTracker @Inject lateinit var clock: Clock - private val timelineViewModel: TimelineViewModel by activityViewModel() - private val messageComposerViewModel: MessageComposerViewModel by activityViewModel() + private val timelineViewModel: TimelineViewModel by parentFragmentViewModel() + private val messageComposerViewModel: MessageComposerViewModel by parentFragmentViewModel() private val permissionVoiceMessageLauncher = registerForPermissionsResult { allGranted, deniedPermanently -> if (allGranted) { From bbc4b35b23de1b141d3bd3c384464d03448d78cd Mon Sep 17 00:00:00 2001 From: Benoit Marty <benoit@matrix.org> Date: Fri, 7 Oct 2022 11:27:08 +0200 Subject: [PATCH 169/187] Remove unused val. --- .../im/vector/app/features/home/room/detail/TimelineFragment.kt | 2 -- 1 file changed, 2 deletions(-) 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 b02fed8445..c34d60de71 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 @@ -282,8 +282,6 @@ class TimelineFragment : private lateinit var callActionsHandler: StartCallActionsHandler private val currentCallsViewPresenter = CurrentCallsViewPresenter() - private val isEmojiKeyboardVisible: Boolean - get() = vectorPreferences.showEmojiKeyboard() private val lazyLoadedViews = RoomDetailLazyLoadedViews() From 824752a4eccf04cdc80279e9a0163a6e01924369 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Thu, 6 Oct 2022 17:48:00 +0200 Subject: [PATCH 170/187] Fix UI for long session names in SessionInfoView --- vector/src/main/res/layout/view_session_info.xml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/vector/src/main/res/layout/view_session_info.xml b/vector/src/main/res/layout/view_session_info.xml index 18daae825a..be51bc6915 100644 --- a/vector/src/main/res/layout/view_session_info.xml +++ b/vector/src/main/res/layout/view_session_info.xml @@ -5,6 +5,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/bg_current_session" + android:paddingHorizontal="24dp" android:paddingBottom="16dp"> <ImageView @@ -26,6 +27,9 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="4dp" + android:ellipsize="end" + android:gravity="center" + android:maxLines="2" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/sessionInfoDeviceTypeImageView" @@ -64,7 +68,6 @@ style="@style/TextAppearance.Vector.Body.DevicesManagement" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginHorizontal="32dp" android:layout_marginTop="12dp" android:gravity="center" app:layout_constraintEnd_toEndOf="parent" @@ -77,7 +80,6 @@ style="@style/TextAppearance.Vector.Body.DevicesManagement" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginHorizontal="32dp" android:layout_marginTop="12dp" android:visibility="gone" app:layout_constraintEnd_toEndOf="parent" @@ -92,7 +94,6 @@ style="@style/TextAppearance.Vector.Body.DevicesManagement" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginHorizontal="32dp" android:gravity="center" android:visibility="gone" app:layout_constraintEnd_toEndOf="parent" @@ -105,7 +106,6 @@ android:id="@+id/sessionInfoVerifySessionButton" android:layout_width="0dp" android:layout_height="52dp" - android:layout_marginHorizontal="24dp" android:layout_marginTop="16dp" android:text="@string/device_manager_verify_session" app:layout_constraintEnd_toEndOf="parent" @@ -117,7 +117,6 @@ style="@style/Widget.Vector.Button.Text" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginHorizontal="24dp" android:layout_marginTop="8dp" android:text="@string/device_manager_view_details" app:layout_constraintEnd_toEndOf="parent" From bcf315aece5f69595ac8792f409a4770197dda20 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Fri, 7 Oct 2022 10:11:04 +0200 Subject: [PATCH 171/187] Fix UI for long session names in SessionDetailsContentItem --- .../layout/item_session_details_content.xml | 22 +++++++++++++------ .../layout/item_session_details_header.xml | 3 ++- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/vector/src/main/res/layout/item_session_details_content.xml b/vector/src/main/res/layout/item_session_details_content.xml index fefae65b3d..98a21aa923 100644 --- a/vector/src/main/res/layout/item_session_details_content.xml +++ b/vector/src/main/res/layout/item_session_details_content.xml @@ -3,8 +3,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="wrap_content" - android:paddingTop="14dp"> + android:layout_height="wrap_content"> <TextView android:id="@+id/sessionDetailsContentTitle" @@ -12,9 +11,10 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="@dimen/layout_horizontal_margin" + app:layout_constraintBottom_toTopOf="@id/sessionDetailsContentDivider" app:layout_constraintEnd_toStartOf="@id/sessionDetailsContentDescription" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" + app:layout_constraintTop_toTopOf="@id/sessionDetailsContentTop" tools:text="Session name" /> <TextView @@ -25,26 +25,34 @@ android:layout_marginStart="8dp" android:layout_marginEnd="@dimen/layout_horizontal_margin" android:gravity="end" + app:layout_constraintBottom_toTopOf="@id/sessionDetailsContentDivider" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/sessionDetailsContentTitle" - app:layout_constraintTop_toTopOf="parent" - tools:text="Element Web: Firefox on macOS" /> + app:layout_constraintTop_toTopOf="@id/sessionDetailsContentTop" + tools:text="Element Web: Firefox" /> <View android:id="@+id/sessionDetailsContentDivider" android:layout_width="0dp" android:layout_height="1dp" - android:layout_marginTop="14dp" android:background="@drawable/divider_horizontal" app:layout_constraintEnd_toEndOf="@id/sessionDetailsContentDescription" app:layout_constraintStart_toStartOf="@id/sessionDetailsContentTitle" - app:layout_constraintTop_toBottomOf="@id/sessionDetailsContentBarrier" /> + app:layout_constraintTop_toTopOf="@id/sessionDetailsContentBarrier" /> <androidx.constraintlayout.widget.Barrier android:id="@+id/sessionDetailsContentBarrier" android:layout_width="wrap_content" android:layout_height="wrap_content" app:barrierDirection="bottom" + app:barrierMargin="14dp" app:constraint_referenced_ids="sessionDetailsContentTitle, sessionDetailsContentDescription" /> + <androidx.constraintlayout.widget.Guideline + android:id="@+id/sessionDetailsContentTop" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + app:layout_constraintGuide_begin="14dp" /> + </androidx.constraintlayout.widget.ConstraintLayout> diff --git a/vector/src/main/res/layout/item_session_details_header.xml b/vector/src/main/res/layout/item_session_details_header.xml index 571a541b2b..64e0efac1f 100644 --- a/vector/src/main/res/layout/item_session_details_header.xml +++ b/vector/src/main/res/layout/item_session_details_header.xml @@ -3,7 +3,8 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="wrap_content"> + android:layout_height="wrap_content" + android:paddingBottom="10dp"> <TextView android:id="@+id/sessionDetailsHeaderTitle" From 3ceb19486af5112b6161025ae8ab7e2f68328076 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <maxime.naturel@niji.fr> Date: Fri, 7 Oct 2022 11:29:33 +0200 Subject: [PATCH 172/187] Adding changelog entry --- changelog.d/7310.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7310.bugfix diff --git a/changelog.d/7310.bugfix b/changelog.d/7310.bugfix new file mode 100644 index 0000000000..3570b2d3ad --- /dev/null +++ b/changelog.d/7310.bugfix @@ -0,0 +1 @@ +[Device Management] Long session names not handled well From b1d3cc85c23d74b3c869f4427a69113a6fd7fa26 Mon Sep 17 00:00:00 2001 From: Benoit Marty <benoit@matrix.org> Date: Fri, 7 Oct 2022 14:31:19 +0200 Subject: [PATCH 173/187] Remove the workaround, this is fixed in `androidx.sharetarget:sharetarget:1.2.0`. (with exported="true" declared in the library) https://android-review.googlesource.com/c/platform/frameworks/support/+/1742473 --- vector-app/src/main/AndroidManifest.xml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/vector-app/src/main/AndroidManifest.xml b/vector-app/src/main/AndroidManifest.xml index 2767b20404..7a515449b4 100644 --- a/vector-app/src/main/AndroidManifest.xml +++ b/vector-app/src/main/AndroidManifest.xml @@ -66,14 +66,6 @@ android:resource="@xml/sdk_provider_paths" /> </provider> - <!-- Temporary fix for Android 12. android:exported has to be explicitly set when targeting Android 12 - Do it for services coming from dependencies - BEGIN --> - <service - android:name="androidx.sharetarget.ChooserTargetServiceCompat" - android:exported="false" - tools:node="merge" /> - <!-- Temporary fix for Android 12 change - END --> - </application> </manifest> From 42d0e4f265bd2eaa300fa0ad879d1f82ecc021a2 Mon Sep 17 00:00:00 2001 From: ericdecanini <eddecanini@gmail.com> Date: Fri, 7 Oct 2022 09:09:19 -0400 Subject: [PATCH 174/187] Fixes bindings --- .../src/main/java/im/vector/app/core/di/SingletonModule.kt | 5 +++++ 1 file changed, 5 insertions(+) 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 384c584e0c..dbfda024d8 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 @@ -34,6 +34,8 @@ import im.vector.app.SpaceStateHandler import im.vector.app.SpaceStateHandlerImpl import im.vector.app.config.Config import im.vector.app.core.debug.FlipperProxy +import im.vector.app.core.device.DefaultGetDeviceInfoUseCase +import im.vector.app.core.device.GetDeviceInfoUseCase import im.vector.app.core.dispatchers.CoroutineDispatchers import im.vector.app.core.error.DefaultErrorFormatter import im.vector.app.core.error.ErrorFormatter @@ -112,6 +114,9 @@ abstract class VectorBindModule { @Binds abstract fun bindSpaceStateHandler(spaceStateHandlerImpl: SpaceStateHandlerImpl): SpaceStateHandler + + @Binds + abstract fun bindGetDeviceInfoUseCase(getDeviceInfoUseCase: DefaultGetDeviceInfoUseCase): GetDeviceInfoUseCase } @InstallIn(SingletonComponent::class) From 9ec2b6e8d59186ba8fcc33e79f07bdeaae76f589 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 7 Oct 2022 23:11:33 +0000 Subject: [PATCH 175/187] Bump glide from 4.14.1 to 4.14.2 Bumps `glide` from 4.14.1 to 4.14.2. Updates `glide` from 4.14.1 to 4.14.2 - [Release notes](https://github.com/bumptech/glide/releases) - [Commits](https://github.com/bumptech/glide/compare/v4.14.1...v4.14.2) Updates `compiler` from 4.14.1 to 4.14.2 - [Release notes](https://github.com/bumptech/glide/releases) - [Commits](https://github.com/bumptech/glide/compare/v4.14.1...v4.14.2) --- updated-dependencies: - dependency-name: com.github.bumptech.glide:glide dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: com.github.bumptech.glide:compiler dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index 359cf577ef..ede2e83bbc 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -22,7 +22,7 @@ def flowBinding = "1.2.0" def flipper = "0.164.0" def epoxy = "5.0.0" def mavericks = "3.0.1" -def glide = "4.14.1" +def glide = "4.14.2" def bigImageViewer = "1.8.1" def jjwt = "0.11.5" // Temporary version to unblock #6929. Once 0.16.0 is released we should use it, and revert From 90afced6aa2df36d7f4a9a73f97b3df4d2291510 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 7 Oct 2022 23:12:38 +0000 Subject: [PATCH 176/187] Bump flipper from 0.164.0 to 0.169.0 Bumps `flipper` from 0.164.0 to 0.169.0. Updates `flipper` from 0.164.0 to 0.169.0 - [Release notes](https://github.com/facebook/flipper/releases) - [Commits](https://github.com/facebook/flipper/compare/v0.164.0...v0.169.0) Updates `flipper-network-plugin` from 0.164.0 to 0.169.0 - [Release notes](https://github.com/facebook/flipper/releases) - [Commits](https://github.com/facebook/flipper/compare/v0.164.0...v0.169.0) --- 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] <support@github.com> --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index 359cf577ef..8ea80991ff 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -19,7 +19,7 @@ def markwon = "4.6.2" def moshi = "1.14.0" def lifecycle = "2.5.1" def flowBinding = "1.2.0" -def flipper = "0.164.0" +def flipper = "0.169.0" def epoxy = "5.0.0" def mavericks = "3.0.1" def glide = "4.14.1" From d191bfeaf71eb64f37c5b0c0cce36e9f3810f300 Mon Sep 17 00:00:00 2001 From: ericdecanini <eddecanini@gmail.com> Date: Sun, 9 Oct 2022 09:46:07 -0400 Subject: [PATCH 177/187] Fixes legal copy --- .../android/sdk/internal/database/mapper/PushersMapperTest.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/mapper/PushersMapperTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/mapper/PushersMapperTest.kt index 5bca9248d6..4abaa7374d 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/mapper/PushersMapperTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/mapper/PushersMapperTest.kt @@ -1,11 +1,11 @@ /* - * Copyright (c) 2022 New Vector Ltd + * Copyright 2020 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 + * 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, From 11e251fc1dee3e937730865234ef4c8291bc67f4 Mon Sep 17 00:00:00 2001 From: ericdecanini <eddecanini@gmail.com> Date: Sun, 9 Oct 2022 09:48:18 -0400 Subject: [PATCH 178/187] Fixes legal copy --- .../org/matrix/android/sdk/test/fixtures/JsonPusherFixture.kt | 4 ++-- .../matrix/android/sdk/test/fixtures/PusherEntityFixture.kt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/JsonPusherFixture.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/JsonPusherFixture.kt index 56b5dcc940..55f392c141 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/JsonPusherFixture.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/JsonPusherFixture.kt @@ -1,11 +1,11 @@ /* - * Copyright (c) 2022 New Vector Ltd + * Copyright 2020 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 + * 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, diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/PusherEntityFixture.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/PusherEntityFixture.kt index 9e3fc555be..7db4184dde 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/PusherEntityFixture.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/PusherEntityFixture.kt @@ -1,11 +1,11 @@ /* - * Copyright (c) 2022 New Vector Ltd + * Copyright 2020 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 + * 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, From cd2e693f0c71ec5886c7dbbb6cb134941821fbe5 Mon Sep 17 00:00:00 2001 From: ericdecanini <eddecanini@gmail.com> Date: Sun, 9 Oct 2022 09:48:36 -0400 Subject: [PATCH 179/187] Fixes legal copy --- .../android/sdk/internal/database/mapper/PushersMapperTest.kt | 2 +- .../org/matrix/android/sdk/test/fixtures/JsonPusherFixture.kt | 2 +- .../org/matrix/android/sdk/test/fixtures/PusherEntityFixture.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/mapper/PushersMapperTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/mapper/PushersMapperTest.kt index 4abaa7374d..08ed20a766 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/mapper/PushersMapperTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/mapper/PushersMapperTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Matrix.org Foundation C.I.C. + * Copyright 2022 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. diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/JsonPusherFixture.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/JsonPusherFixture.kt index 55f392c141..8e679ff91c 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/JsonPusherFixture.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/JsonPusherFixture.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Matrix.org Foundation C.I.C. + * Copyright 2022 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. diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/PusherEntityFixture.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/PusherEntityFixture.kt index 7db4184dde..8d048d4c9a 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/PusherEntityFixture.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/PusherEntityFixture.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Matrix.org Foundation C.I.C. + * Copyright 2022 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 78555ec6ba56d3223415187c1a4c509da1d30d32 Mon Sep 17 00:00:00 2001 From: ericdecanini <eddecanini@gmail.com> Date: Sun, 9 Oct 2022 10:59:11 -0400 Subject: [PATCH 180/187] Rearranges imports --- .../src/test/java/im/vector/app/test/fakes/FakeCryptoService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeCryptoService.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeCryptoService.kt index 88ae174e32..e96a58faa0 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeCryptoService.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeCryptoService.kt @@ -17,8 +17,8 @@ package im.vector.app.test.fakes import androidx.lifecycle.MutableLiveData -import io.mockk.every import im.vector.app.test.fixtures.CryptoDeviceInfoFixture.aCryptoDeviceInfo +import io.mockk.every import io.mockk.mockk import io.mockk.slot import org.matrix.android.sdk.api.MatrixCallback From 31d4fc9ab0eaa5c6048c3b0b4aa21082bec20a42 Mon Sep 17 00:00:00 2001 From: ericdecanini <eddecanini@gmail.com> Date: Sun, 9 Oct 2022 12:45:11 -0400 Subject: [PATCH 181/187] Fixes lint error --- .../im/vector/app/core/device/DefaultGetDeviceInfoUseCaseTest.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/vector/src/test/java/im/vector/app/core/device/DefaultGetDeviceInfoUseCaseTest.kt b/vector/src/test/java/im/vector/app/core/device/DefaultGetDeviceInfoUseCaseTest.kt index 0675556bec..0673fbadb5 100644 --- a/vector/src/test/java/im/vector/app/core/device/DefaultGetDeviceInfoUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/core/device/DefaultGetDeviceInfoUseCaseTest.kt @@ -32,7 +32,6 @@ class DefaultGetDeviceInfoUseCaseTest { @Test fun `when execute, then get crypto device info`() { - val result = getDeviceInfoUseCase.execute() result shouldBeEqualTo cryptoService.cryptoDeviceInfo From c94884b9d3c7de64948fb5518885b3afa38faa5c Mon Sep 17 00:00:00 2001 From: ericdecanini <eddecanini@gmail.com> Date: Sun, 9 Oct 2022 14:55:25 -0400 Subject: [PATCH 182/187] Fixes error --- .../src/main/java/im/vector/app/core/resources/LocaleProvider.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/java/im/vector/app/core/resources/LocaleProvider.kt b/vector/src/main/java/im/vector/app/core/resources/LocaleProvider.kt index 3bdbc64eb4..7777068c63 100644 --- a/vector/src/main/java/im/vector/app/core/resources/LocaleProvider.kt +++ b/vector/src/main/java/im/vector/app/core/resources/LocaleProvider.kt @@ -31,6 +31,7 @@ interface LocaleProvider { class DefaultLocaleProvider @Inject constructor(private val resources: Resources) : LocaleProvider { override fun current(): Locale { + "" return ConfigurationCompat.getLocales(resources.configuration).get(0) ?: Locale.getDefault() } } From 618cf7ac0ead409c754f2f6edd7f7bcf5ec4fc73 Mon Sep 17 00:00:00 2001 From: ericdecanini <eddecanini@gmail.com> Date: Sun, 9 Oct 2022 14:55:43 -0400 Subject: [PATCH 183/187] Fixes error --- .../src/main/java/im/vector/app/core/resources/LocaleProvider.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/core/resources/LocaleProvider.kt b/vector/src/main/java/im/vector/app/core/resources/LocaleProvider.kt index 7777068c63..3bdbc64eb4 100644 --- a/vector/src/main/java/im/vector/app/core/resources/LocaleProvider.kt +++ b/vector/src/main/java/im/vector/app/core/resources/LocaleProvider.kt @@ -31,7 +31,6 @@ interface LocaleProvider { class DefaultLocaleProvider @Inject constructor(private val resources: Resources) : LocaleProvider { override fun current(): Locale { - "" return ConfigurationCompat.getLocales(resources.configuration).get(0) ?: Locale.getDefault() } } From 5430ca2966ceb0fe7e06723da077c7aa4287bd4e Mon Sep 17 00:00:00 2001 From: Vri <vrifox@vrifox.cc> Date: Mon, 10 Oct 2022 16:18:37 +0200 Subject: [PATCH 184/187] correct login_set_email_notice Corrected the string as noted in this comment by @lvre (https://translate.element.io/translate/element-android/element-app/en/?checksum=8cfc63845b17eca4#comments) over on Weblate. --- library/ui-strings/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 71ccf5b234..7387d24147 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -2115,7 +2115,7 @@ <string name="login_reset_password_cancel_confirmation_content">Your password is not yet changed.\n\nStop the password change process?</string> <string name="login_set_email_title">Set email address</string> - <string name="login_set_email_notice">Set an email address to recover your account. Later, you can optionally allow people you know to discover you by your this address.</string> + <string name="login_set_email_notice">Set an email address to recover your account. Later, you can optionally allow people you know to discover you by this address.</string> <string name="login_set_email_mandatory_hint">Email</string> <string name="login_set_email_optional_hint">Email (optional)</string> <string name="login_set_email_submit">Next</string> From 267431d06e30134f59360963318324ba1e82c427 Mon Sep 17 00:00:00 2001 From: ericdecanini <eddecanini@gmail.com> Date: Mon, 10 Oct 2022 11:37:39 -0400 Subject: [PATCH 185/187] Fixes error --- vector-app/src/fdroid/java/im/vector/app/di/FlavorModule.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vector-app/src/fdroid/java/im/vector/app/di/FlavorModule.kt b/vector-app/src/fdroid/java/im/vector/app/di/FlavorModule.kt index 6f76913649..63b4c2a3cd 100644 --- a/vector-app/src/fdroid/java/im/vector/app/di/FlavorModule.kt +++ b/vector-app/src/fdroid/java/im/vector/app/di/FlavorModule.kt @@ -23,6 +23,10 @@ import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import im.vector.app.core.pushers.FcmHelper +import im.vector.app.core.resources.AppNameProvider +import im.vector.app.core.resources.DefaultAppNameProvider +import im.vector.app.core.resources.DefaultLocaleProvider +import im.vector.app.core.resources.LocaleProvider import im.vector.app.core.services.GuardServiceStarter import im.vector.app.fdroid.service.FDroidGuardServiceStarter import im.vector.app.features.home.NightlyProxy From 2fe636e93ba4de2381cfbc394483845947fb5272 Mon Sep 17 00:00:00 2001 From: Eric Decanini <eddecanini@gmail.com> Date: Mon, 10 Oct 2022 19:21:34 -0400 Subject: [PATCH 186/187] Adds Push Notification toggle to Device Manager (#7261) * Adds push notifications switch * Adds functionality to Push notification toggle * Adds DefaultPushersServiceTest for togglePusher * Adds DefaultTogglePusherTaskTest * Adds SessionOverviewViewModelTest for toggling pusher * Hides pusher toggle if there are no pushers of the device * Adds changelog file * Edits changelog file * Fixes copyrights * Unregisters checkedChangelistener in onDetachedFromWindow for switch view * Fixes post merge errors * Fixes legal copies * Removes unused imports * Fixes lint errors * Fixes test errors * Fixes error * Fixes error * Fixes error * Fixes error * Fixes error --- changelog.d/7261.wip | 1 + .../src/main/res/values/strings.xml | 2 + .../stylable_session_overview_entry_view.xml | 6 + .../sdk/api/session/pushers/PushersService.kt | 8 + .../session/pushers/DefaultPushersService.kt | 19 +++ .../internal/session/pushers/PushersModule.kt | 3 + .../session/pushers/TogglePusherTask.kt | 52 ++++++ .../pushers/DefaultPushersServiceTest.kt | 66 ++++++++ .../pushers/DefaultTogglePusherTaskTest.kt | 64 ++++++++ .../sdk/test/fakes/FakeAddPusherTask.kt | 22 +++ .../sdk/test/fakes/FakeGetPushersTask.kt | 22 +++ .../sdk/test/fakes/FakeRemovePusherTask.kt | 22 +++ .../sdk/test/fakes/FakeTaskExecutor.kt | 25 +++ .../sdk/test/fakes/FakeTogglePusherTask.kt | 35 ++++ .../internal/FakePushGatewayNotifyTask.kt | 22 +++ .../sdk/test/fixtures/PusherFixture.kt | 50 ++++++ vector/build.gradle | 1 + .../v2/overview/SessionOverviewAction.kt | 5 + .../SessionOverviewEntrySwitchView.kt | 86 ++++++++++ .../v2/overview/SessionOverviewFragment.kt | 49 +++++- .../v2/overview/SessionOverviewViewModel.kt | 21 +++ .../v2/overview/SessionOverviewViewState.kt | 2 + .../res/layout/fragment_session_overview.xml | 10 ++ .../view_session_overview_entry_switch.xml | 51 ++++++ .../overview/SessionOverviewViewModelTest.kt | 150 +++++------------- .../app/test/fakes/FakePushersService.kt | 16 ++ .../vector/app/test/fixtures/PusherFixture.kt | 50 ++++++ 27 files changed, 741 insertions(+), 119 deletions(-) create mode 100644 changelog.d/7261.wip create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/TogglePusherTask.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersServiceTest.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultTogglePusherTaskTest.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeAddPusherTask.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeGetPushersTask.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRemovePusherTask.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeTaskExecutor.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeTogglePusherTask.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/FakePushGatewayNotifyTask.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/PusherFixture.kt create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewEntrySwitchView.kt create mode 100644 vector/src/main/res/layout/view_session_overview_entry_switch.xml create mode 100644 vector/src/test/java/im/vector/app/test/fixtures/PusherFixture.kt diff --git a/changelog.d/7261.wip b/changelog.d/7261.wip new file mode 100644 index 0000000000..f7063fcc1b --- /dev/null +++ b/changelog.d/7261.wip @@ -0,0 +1 @@ +Adds pusher toggle setting to device manager v2 diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 63a469a8da..63c1f8a8bb 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3304,6 +3304,8 @@ <string name="device_manager_session_overview_signout">Sign out of this session</string> <string name="device_manager_session_details_title">Session details</string> <string name="device_manager_session_details_description">Application, device, and activity information.</string> + <string name="device_manager_push_notifications_title">Push notifications</string> + <string name="device_manager_push_notifications_description">Receive push notifications on this session.</string> <string name="device_manager_session_details_session_name">Session name</string> <string name="device_manager_session_details_session_id">Session ID</string> <string name="device_manager_session_details_session_last_activity">Last activity</string> diff --git a/library/ui-styles/src/main/res/values/stylable_session_overview_entry_view.xml b/library/ui-styles/src/main/res/values/stylable_session_overview_entry_view.xml index d3884f247d..6428cd6eac 100644 --- a/library/ui-styles/src/main/res/values/stylable_session_overview_entry_view.xml +++ b/library/ui-styles/src/main/res/values/stylable_session_overview_entry_view.xml @@ -6,4 +6,10 @@ <attr name="sessionOverviewEntryDescription" format="string" /> </declare-styleable> + <declare-styleable name="SessionOverviewEntrySwitchView"> + <attr name="sessionOverviewEntrySwitchTitle" format="string" /> + <attr name="sessionOverviewEntrySwitchDescription" format="string" /> + <attr name="sessionOverviewEntrySwitchEnabled" format="boolean" /> + </declare-styleable> + </resources> diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt index d7958ea3cd..6a27f7af61 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt @@ -67,6 +67,14 @@ interface PushersService { append: Boolean = true ) + /** + * Enables or disables a registered pusher. + * + * @param pusher The pusher being toggled + * @param enable Whether the pusher should be enabled or disabled + */ + suspend fun togglePusher(pusher: Pusher, enable: Boolean) + /** * Directly ask the push gateway to send a push to this device. * If successful, the push gateway has accepted the request. In this case, the app should receive a Push with the provided eventId. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt index 082b5b63eb..e89cfa508c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt @@ -42,6 +42,7 @@ internal class DefaultPushersService @Inject constructor( private val getPusherTask: GetPushersTask, private val pushGatewayNotifyTask: PushGatewayNotifyTask, private val addPusherTask: AddPusherTask, + private val togglePusherTask: TogglePusherTask, private val removePusherTask: RemovePusherTask, private val taskExecutor: TaskExecutor ) : PushersService { @@ -108,6 +109,24 @@ internal class DefaultPushersService @Inject constructor( ) } + override suspend fun togglePusher(pusher: Pusher, enable: Boolean) { + togglePusherTask.execute(TogglePusherTask.Params(pusher.toJsonPusher(), enable)) + } + + private fun Pusher.toJsonPusher() = JsonPusher( + pushKey = pushKey, + kind = kind, + appId = appId, + appDisplayName = appDisplayName, + deviceDisplayName = deviceDisplayName, + profileTag = profileTag, + lang = lang, + data = JsonPusherData(data.url, data.format), + append = false, + enabled = enabled, + deviceId = deviceId, + ) + private fun enqueueAddPusher(pusher: JsonPusher): UUID { val params = AddPusherWorker.Params(sessionId, pusher) val request = workManagerProvider.matrixOneTimeWorkRequestBuilder<AddPusherWorker>() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt index 4528c95e69..37c1c0c3ad 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt @@ -68,6 +68,9 @@ internal abstract class PushersModule { @Binds abstract fun bindAddPusherTask(task: DefaultAddPusherTask): AddPusherTask + @Binds + abstract fun bindTogglePusherTask(task: DefaultTogglePusherTask): TogglePusherTask + @Binds abstract fun bindRemovePusherTask(task: DefaultRemovePusherTask): RemovePusherTask diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/TogglePusherTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/TogglePusherTask.kt new file mode 100644 index 0000000000..87836e1c76 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/TogglePusherTask.kt @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2021 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.pushers + +import com.zhuinden.monarchy.Monarchy +import org.matrix.android.sdk.internal.database.model.PusherEntity +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.RequestExecutor +import org.matrix.android.sdk.internal.task.Task +import org.matrix.android.sdk.internal.util.awaitTransaction +import javax.inject.Inject + +internal interface TogglePusherTask : Task<TogglePusherTask.Params, Unit> { + data class Params(val pusher: JsonPusher, val enable: Boolean) +} + +internal class DefaultTogglePusherTask @Inject constructor( + private val pushersAPI: PushersAPI, + @SessionDatabase private val monarchy: Monarchy, + private val requestExecutor: RequestExecutor, + private val globalErrorReceiver: GlobalErrorReceiver +) : TogglePusherTask { + + override suspend fun execute(params: TogglePusherTask.Params) { + val pusher = params.pusher.copy(enabled = params.enable) + + requestExecutor.executeRequest(globalErrorReceiver) { + pushersAPI.setPusher(pusher) + } + + monarchy.awaitTransaction { realm -> + val entity = PusherEntity.where(realm, params.pusher.pushKey).findFirst() + entity?.apply { enabled = params.enable }?.let { realm.insertOrUpdate(it) } + } + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersServiceTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersServiceTest.kt new file mode 100644 index 0000000000..a00ac3a17d --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersServiceTest.kt @@ -0,0 +1,66 @@ +/* + * Copyright 2022 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.pushers + +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.matrix.android.sdk.test.fakes.FakeAddPusherTask +import org.matrix.android.sdk.test.fakes.FakeGetPushersTask +import org.matrix.android.sdk.test.fakes.FakeMonarchy +import org.matrix.android.sdk.test.fakes.FakeRemovePusherTask +import org.matrix.android.sdk.test.fakes.FakeTaskExecutor +import org.matrix.android.sdk.test.fakes.FakeTogglePusherTask +import org.matrix.android.sdk.test.fakes.FakeWorkManagerProvider +import org.matrix.android.sdk.test.fakes.internal.FakePushGatewayNotifyTask +import org.matrix.android.sdk.test.fixtures.PusherFixture + +@OptIn(ExperimentalCoroutinesApi::class) +class DefaultPushersServiceTest { + + private val workManagerProvider = FakeWorkManagerProvider() + private val monarchy = FakeMonarchy() + private val sessionId = "" + private val getPushersTask = FakeGetPushersTask() + private val pushGatewayNotifyTask = FakePushGatewayNotifyTask() + private val addPusherTask = FakeAddPusherTask() + private val togglePusherTask = FakeTogglePusherTask() + private val removePusherTask = FakeRemovePusherTask() + private val taskExecutor = FakeTaskExecutor() + + private val pushersService = DefaultPushersService( + workManagerProvider.instance, + monarchy.instance, + sessionId, + getPushersTask, + pushGatewayNotifyTask, + addPusherTask, + togglePusherTask, + removePusherTask, + taskExecutor.instance, + ) + + @Test + fun `when togglePusher, then execute task`() = runTest { + val pusher = PusherFixture.aPusher() + val enable = true + + pushersService.togglePusher(pusher, enable) + + togglePusherTask.verifyExecution(pusher, enable) + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultTogglePusherTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultTogglePusherTaskTest.kt new file mode 100644 index 0000000000..3c54f6f1e1 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultTogglePusherTaskTest.kt @@ -0,0 +1,64 @@ +/* + * Copyright 2022 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.pushers + +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.amshove.kluent.shouldBeEqualTo +import org.junit.Test +import org.matrix.android.sdk.internal.database.model.PusherEntity +import org.matrix.android.sdk.internal.database.model.PusherEntityFields +import org.matrix.android.sdk.test.fakes.FakeGlobalErrorReceiver +import org.matrix.android.sdk.test.fakes.FakeMonarchy +import org.matrix.android.sdk.test.fakes.FakePushersAPI +import org.matrix.android.sdk.test.fakes.FakeRequestExecutor +import org.matrix.android.sdk.test.fakes.givenEqualTo +import org.matrix.android.sdk.test.fakes.givenFindFirst +import org.matrix.android.sdk.test.fixtures.JsonPusherFixture.aJsonPusher +import org.matrix.android.sdk.test.fixtures.PusherEntityFixture.aPusherEntity + +@OptIn(ExperimentalCoroutinesApi::class) +class DefaultTogglePusherTaskTest { + + private val pushersAPI = FakePushersAPI() + private val monarchy = FakeMonarchy() + private val requestExecutor = FakeRequestExecutor() + private val globalErrorReceiver = FakeGlobalErrorReceiver() + + private val togglePusherTask = DefaultTogglePusherTask(pushersAPI, monarchy.instance, requestExecutor, globalErrorReceiver) + + @Test + fun `execution toggles enable on both local and remote`() = runTest { + val jsonPusher = aJsonPusher(enabled = false) + val params = TogglePusherTask.Params(aJsonPusher(), true) + + val pusherEntity = aPusherEntity(enabled = false) + monarchy.givenWhere<PusherEntity>() + .givenEqualTo(PusherEntityFields.PUSH_KEY, jsonPusher.pushKey) + .givenFindFirst(pusherEntity) + + togglePusherTask.execute(params) + + val expectedPayload = jsonPusher.copy(enabled = true) + pushersAPI.verifySetPusher(expectedPayload) + monarchy.verifyInsertOrUpdate<PusherEntity> { + withArg { actual -> + actual.enabled shouldBeEqualTo true + } + } + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeAddPusherTask.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeAddPusherTask.kt new file mode 100644 index 0000000000..16cdd7a626 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeAddPusherTask.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2022 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.mockk +import org.matrix.android.sdk.internal.session.pushers.AddPusherTask + +class FakeAddPusherTask : AddPusherTask by mockk() diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeGetPushersTask.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeGetPushersTask.kt new file mode 100644 index 0000000000..d5a41bb0e0 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeGetPushersTask.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2022 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.mockk +import org.matrix.android.sdk.internal.session.pushers.GetPushersTask + +class FakeGetPushersTask : GetPushersTask by mockk() diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRemovePusherTask.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRemovePusherTask.kt new file mode 100644 index 0000000000..55a7607a03 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRemovePusherTask.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2022 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.mockk +import org.matrix.android.sdk.internal.session.pushers.RemovePusherTask + +class FakeRemovePusherTask : RemovePusherTask by mockk() diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeTaskExecutor.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeTaskExecutor.kt new file mode 100644 index 0000000000..543dda8a4f --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeTaskExecutor.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2022 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.mockk +import org.matrix.android.sdk.internal.task.TaskExecutor + +internal class FakeTaskExecutor { + + val instance: TaskExecutor = mockk() +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeTogglePusherTask.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeTogglePusherTask.kt new file mode 100644 index 0000000000..b1e059a40e --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeTogglePusherTask.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2022 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.coVerify +import io.mockk.mockk +import io.mockk.slot +import org.amshove.kluent.shouldBeEqualTo +import org.matrix.android.sdk.api.session.pushers.Pusher +import org.matrix.android.sdk.internal.session.pushers.TogglePusherTask + +class FakeTogglePusherTask : TogglePusherTask by mockk(relaxed = true) { + + fun verifyExecution(pusher: Pusher, enable: Boolean) { + val slot = slot<TogglePusherTask.Params>() + coVerify { execute(capture(slot)) } + val params = slot.captured + params.pusher.pushKey shouldBeEqualTo pusher.pushKey + params.enable shouldBeEqualTo enable + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/FakePushGatewayNotifyTask.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/FakePushGatewayNotifyTask.kt new file mode 100644 index 0000000000..46a106dcb2 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/FakePushGatewayNotifyTask.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2022 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.internal + +import io.mockk.mockk +import org.matrix.android.sdk.internal.session.pushers.gateway.PushGatewayNotifyTask + +class FakePushGatewayNotifyTask : PushGatewayNotifyTask by mockk() diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/PusherFixture.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/PusherFixture.kt new file mode 100644 index 0000000000..0ac7885062 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/PusherFixture.kt @@ -0,0 +1,50 @@ +/* + * Copyright 2022 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.fixtures + +import org.matrix.android.sdk.api.session.pushers.Pusher +import org.matrix.android.sdk.api.session.pushers.PusherData +import org.matrix.android.sdk.api.session.pushers.PusherState + +object PusherFixture { + + fun aPusher( + pushKey: String = "", + kind: String = "", + appId: String = "", + appDisplayName: String? = "", + deviceDisplayName: String? = "", + profileTag: String? = null, + lang: String? = "", + data: PusherData = PusherData("f.o/_matrix/push/v1/notify", ""), + enabled: Boolean = true, + deviceId: String? = "", + state: PusherState = PusherState.REGISTERED, + ) = Pusher( + pushKey, + kind, + appId, + appDisplayName, + deviceDisplayName, + profileTag, + lang, + data, + enabled, + deviceId, + state, + ) +} diff --git a/vector/build.gradle b/vector/build.gradle index 76f32a34db..37a98d8242 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -296,6 +296,7 @@ dependencies { // Plant Timber tree for test testImplementation libs.tests.timberJunitRule testImplementation libs.airbnb.mavericksTesting + testImplementation libs.androidx.coreTesting testImplementation(libs.jetbrains.coroutinesTest) { exclude group: "org.jetbrains.kotlinx", module: "kotlinx-coroutines-debug" } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewAction.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewAction.kt index 42e79ac89f..9a92d5b629 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewAction.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewAction.kt @@ -19,9 +19,14 @@ package im.vector.app.features.settings.devices.v2.overview import im.vector.app.core.platform.VectorViewModelAction sealed class SessionOverviewAction : VectorViewModelAction { + object VerifySession : SessionOverviewAction() object SignoutOtherSession : SessionOverviewAction() object SsoAuthDone : SessionOverviewAction() data class PasswordAuthDone(val password: String) : SessionOverviewAction() object ReAuthCancelled : SessionOverviewAction() + data class TogglePushNotifications( + val deviceId: String, + val enabled: Boolean, + ) : SessionOverviewAction() } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewEntrySwitchView.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewEntrySwitchView.kt new file mode 100644 index 0000000000..bbefd31dfe --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewEntrySwitchView.kt @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.settings.devices.v2.overview + +import android.content.Context +import android.content.res.TypedArray +import android.util.AttributeSet +import android.view.LayoutInflater +import android.widget.CompoundButton +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.content.res.use +import im.vector.app.R +import im.vector.app.core.extensions.setAttributeBackground +import im.vector.app.databinding.ViewSessionOverviewEntrySwitchBinding + +class SessionOverviewEntrySwitchView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : ConstraintLayout(context, attrs, defStyleAttr) { + + private val binding = ViewSessionOverviewEntrySwitchBinding.inflate( + LayoutInflater.from(context), + this + ) + + init { + initBackground() + context.obtainStyledAttributes( + attrs, + R.styleable.SessionOverviewEntrySwitchView, + 0, + 0 + ).use { + setTitle(it) + setDescription(it) + setSwitchedEnabled(it) + } + } + + private fun initBackground() { + binding.root.setAttributeBackground(android.R.attr.selectableItemBackground) + } + + private fun setTitle(typedArray: TypedArray) { + val title = typedArray.getString(R.styleable.SessionOverviewEntrySwitchView_sessionOverviewEntrySwitchTitle) + binding.sessionsOverviewEntryTitle.text = title + } + + private fun setDescription(typedArray: TypedArray) { + val description = typedArray.getString(R.styleable.SessionOverviewEntrySwitchView_sessionOverviewEntrySwitchDescription) + binding.sessionsOverviewEntryDescription.text = description + } + + private fun setSwitchedEnabled(typedArray: TypedArray) { + val enabled = typedArray.getBoolean(R.styleable.SessionOverviewEntrySwitchView_sessionOverviewEntrySwitchEnabled, true) + binding.sessionsOverviewEntrySwitch.isChecked = enabled + } + + fun setChecked(checked: Boolean) { + binding.sessionsOverviewEntrySwitch.isChecked = checked + } + + fun setOnCheckedChangeListener(listener: CompoundButton.OnCheckedChangeListener?) { + binding.sessionsOverviewEntrySwitch.setOnCheckedChangeListener(listener) + } + + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + binding.sessionsOverviewEntrySwitch.setOnCheckedChangeListener(null) + } +} diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt index 58b0a13706..7510880087 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt @@ -24,6 +24,7 @@ import android.view.View import android.view.ViewGroup import android.widget.Toast import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.isGone import androidx.core.view.isVisible import com.airbnb.mvrx.Success import com.airbnb.mvrx.fragmentViewModel @@ -41,12 +42,14 @@ import im.vector.app.core.resources.StringProvider import im.vector.app.databinding.FragmentSessionOverviewBinding import im.vector.app.features.auth.ReAuthActivity import im.vector.app.features.crypto.recover.SetupMode +import im.vector.app.features.settings.devices.v2.DeviceFullInfo import im.vector.app.features.settings.devices.v2.list.SessionInfoViewState import im.vector.app.features.settings.devices.v2.more.SessionLearnMoreBottomSheet import im.vector.app.features.workers.signout.SignOutUiWorker import org.matrix.android.sdk.api.auth.data.LoginFlowTypes import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel +import org.matrix.android.sdk.api.session.pushers.Pusher import javax.inject.Inject /** @@ -174,9 +177,14 @@ class SessionOverviewFragment : override fun invalidate() = withState(viewModel) { state -> updateToolbar(state) - updateEntryDetails(state.deviceId) updateSessionInfo(state) updateLoading(state.isLoading) + updatePushNotificationToggle(state.deviceId, state.pushers.invoke().orEmpty()) + if (state.deviceInfo is Success) { + renderSessionInfo(state.isCurrentSessionTrusted, state.deviceInfo.invoke()) + } else { + hideSessionInfo() + } } private fun updateToolbar(viewState: SessionOverviewViewState) { @@ -189,12 +197,6 @@ class SessionOverviewFragment : } } - private fun updateEntryDetails(deviceId: String) { - views.sessionOverviewEntryDetails.setOnClickListener { - viewNavigator.goToSessionDetails(requireContext(), deviceId) - } - } - private fun updateSessionInfo(viewState: SessionOverviewViewState) { if (viewState.deviceInfo is Success) { views.sessionOverviewInfo.isVisible = true @@ -217,6 +219,35 @@ class SessionOverviewFragment : } } + private fun updatePushNotificationToggle(deviceId: String, pushers: List<Pusher>) { + views.sessionOverviewPushNotifications.apply { + if (pushers.isEmpty()) { + isVisible = false + } else { + val allPushersAreEnabled = pushers.all { it.enabled } + setOnCheckedChangeListener(null) + setChecked(allPushersAreEnabled) + post { + setOnCheckedChangeListener { _, isChecked -> + viewModel.handle(SessionOverviewAction.TogglePushNotifications(deviceId, isChecked)) + } + } + } + } + } + + private fun renderSessionInfo(isCurrentSession: Boolean, deviceFullInfo: DeviceFullInfo) { + views.sessionOverviewInfo.isVisible = true + val viewState = SessionInfoViewState( + isCurrentSession = isCurrentSession, + deviceFullInfo = deviceFullInfo, + isDetailsButtonVisible = false, + isLearnMoreLinkVisible = true, + isLastSeenDetailsVisible = true, + ) + views.sessionOverviewInfo.render(viewState, dateFormatter, drawableProvider, colorProvider, stringProvider) + } + private fun updateLoading(isLoading: Boolean) { if (isLoading) { showLoading(null) @@ -275,4 +306,8 @@ class SessionOverviewFragment : ) SessionLearnMoreBottomSheet.show(childFragmentManager, args) } + + private fun hideSessionInfo() { + views.sessionOverviewInfo.isGone = true + } } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt index bd5c7725eb..a7b0435e29 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt @@ -43,14 +43,17 @@ import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.failure.Failure +import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel import org.matrix.android.sdk.api.session.uia.DefaultBaseAuth +import org.matrix.android.sdk.flow.flow import timber.log.Timber import javax.net.ssl.HttpsURLConnection import kotlin.coroutines.Continuation class SessionOverviewViewModel @AssistedInject constructor( @Assisted val initialState: SessionOverviewViewState, + private val session: Session, private val stringProvider: StringProvider, private val getDeviceFullInfoUseCase: GetDeviceFullInfoUseCase, private val checkIfCurrentSessionCanBeVerifiedUseCase: CheckIfCurrentSessionCanBeVerifiedUseCase, @@ -73,6 +76,7 @@ class SessionOverviewViewModel @AssistedInject constructor( init { observeSessionInfo(initialState.deviceId) observeCurrentSessionInfo() + observePushers(initialState.deviceId) } private fun observeSessionInfo(deviceId: String) { @@ -94,6 +98,13 @@ class SessionOverviewViewModel @AssistedInject constructor( } } + private fun observePushers(deviceId: String) { + session.flow() + .livePushers() + .map { it.filter { pusher -> pusher.deviceId == deviceId } } + .execute { copy(pushers = it) } + } + override fun handle(action: SessionOverviewAction) { when (action) { is SessionOverviewAction.VerifySession -> handleVerifySessionAction() @@ -101,6 +112,7 @@ class SessionOverviewViewModel @AssistedInject constructor( SessionOverviewAction.SsoAuthDone -> handleSsoAuthDone() is SessionOverviewAction.PasswordAuthDone -> handlePasswordAuthDone(action) SessionOverviewAction.ReAuthCancelled -> handleReAuthCancelled() + is SessionOverviewAction.TogglePushNotifications -> handleTogglePusherAction(action) } } @@ -198,4 +210,13 @@ class SessionOverviewViewModel @AssistedInject constructor( private fun handleReAuthCancelled() { pendingAuthHandler.reAuthCancelled() } + + private fun handleTogglePusherAction(action: SessionOverviewAction.TogglePushNotifications) { + viewModelScope.launch { + val devicePushers = awaitState().pushers.invoke()?.filter { it.deviceId == action.deviceId } + devicePushers?.forEach { pusher -> + session.pushersService().togglePusher(pusher, action.enabled) + } + } + } } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewState.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewState.kt index 07423888b5..c2d4a858b3 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewState.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewState.kt @@ -20,12 +20,14 @@ import com.airbnb.mvrx.Async import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized import im.vector.app.features.settings.devices.v2.DeviceFullInfo +import org.matrix.android.sdk.api.session.pushers.Pusher data class SessionOverviewViewState( val deviceId: String, val isCurrentSessionTrusted: Boolean = false, val deviceInfo: Async<DeviceFullInfo> = Uninitialized, val isLoading: Boolean = false, + val pushers: Async<List<Pusher>> = Uninitialized, ) : MavericksState { constructor(args: SessionOverviewArgs) : this( deviceId = args.deviceId diff --git a/vector/src/main/res/layout/fragment_session_overview.xml b/vector/src/main/res/layout/fragment_session_overview.xml index 0a9dd61fe0..80ad744d01 100644 --- a/vector/src/main/res/layout/fragment_session_overview.xml +++ b/vector/src/main/res/layout/fragment_session_overview.xml @@ -31,6 +31,16 @@ app:sessionOverviewEntryDescription="@string/device_manager_session_details_description" app:sessionOverviewEntryTitle="@string/device_manager_session_details_title" /> + <im.vector.app.features.settings.devices.v2.overview.SessionOverviewEntrySwitchView + android:id="@+id/sessionOverviewPushNotifications" + android:layout_width="0dp" + android:layout_height="wrap_content" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/sessionOverviewEntryDetails" + app:sessionOverviewEntrySwitchDescription="@string/device_manager_push_notifications_description" + app:sessionOverviewEntrySwitchTitle="@string/device_manager_push_notifications_title" /> + <Button android:id="@+id/sessionOverviewSignout" style="@style/Widget.Vector.Button.Text.Destructive" diff --git a/vector/src/main/res/layout/view_session_overview_entry_switch.xml b/vector/src/main/res/layout/view_session_overview_entry_switch.xml new file mode 100644 index 0000000000..34d68abf6e --- /dev/null +++ b/vector/src/main/res/layout/view_session_overview_entry_switch.xml @@ -0,0 +1,51 @@ +<?xml version="1.0" encoding="utf-8"?> +<merge xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout"> + + <TextView + android:id="@+id/sessionsOverviewEntryTitle" + style="@style/TextAppearance.Vector.Subtitle.DevicesManagement" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginHorizontal="@dimen/layout_horizontal_margin" + android:layout_marginTop="20dp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + tools:text="Push notifications" /> + + <TextView + android:id="@+id/sessionsOverviewEntryDescription" + style="@style/TextAppearance.Vector.Body.DevicesManagement" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="4dp" + app:layout_constraintEnd_toEndOf="@id/sessionsOverviewEntryTitle" + app:layout_constraintStart_toStartOf="@id/sessionsOverviewEntryTitle" + app:layout_constraintTop_toBottomOf="@id/sessionsOverviewEntryTitle" + tools:text="Receive push notifications on this session." /> + + <androidx.appcompat.widget.SwitchCompat + android:id="@+id/sessionsOverviewEntrySwitch" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="16dp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" /> + + <View + android:id="@+id/sessionsOverviewEntryDivider" + android:layout_width="0dp" + android:layout_height="1dp" + android:layout_marginTop="20dp" + android:background="@drawable/divider_horizontal" + app:layout_constraintEnd_toEndOf="@id/sessionsOverviewEntryTitle" + app:layout_constraintStart_toStartOf="@id/sessionsOverviewEntryTitle" + app:layout_constraintTop_toBottomOf="@id/sessionsOverviewEntryDescription" /> + +</merge> diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModelTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModelTest.kt index 3454b41ee0..da602ae227 100644 --- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModelTest.kt @@ -17,9 +17,10 @@ package im.vector.app.features.settings.devices.v2.overview import android.os.SystemClock +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Success import com.airbnb.mvrx.test.MavericksTestRule -import im.vector.app.R import im.vector.app.features.settings.devices.v2.DeviceFullInfo import im.vector.app.features.settings.devices.v2.RefreshDevicesUseCase import im.vector.app.features.settings.devices.v2.signout.InterceptSignoutFlowResponseUseCase @@ -28,8 +29,10 @@ import im.vector.app.features.settings.devices.v2.signout.SignoutSessionUseCase import im.vector.app.features.settings.devices.v2.verification.CheckIfCurrentSessionCanBeVerifiedUseCase import im.vector.app.test.fakes.FakeActiveSessionHolder import im.vector.app.test.fakes.FakePendingAuthHandler +import im.vector.app.test.fakes.FakeSession import im.vector.app.test.fakes.FakeStringProvider import im.vector.app.test.fakes.FakeVerificationService +import im.vector.app.test.fixtures.PusherFixture.aPusher import im.vector.app.test.test import im.vector.app.test.testDispatcher import io.mockk.coEvery @@ -52,10 +55,8 @@ import org.junit.Test import org.matrix.android.sdk.api.auth.UIABaseAuth import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse -import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel import org.matrix.android.sdk.api.session.uia.DefaultBaseAuth -import javax.net.ssl.HttpsURLConnection import kotlin.coroutines.Continuation private const val A_SESSION_ID_1 = "session-id-1" @@ -69,12 +70,16 @@ class SessionOverviewViewModelTest { @get:Rule val mavericksTestRule = MavericksTestRule(testDispatcher = testDispatcher) + @get:Rule + val instantTaskExecutorRule = InstantTaskExecutorRule() + private val args = SessionOverviewArgs( deviceId = A_SESSION_ID_1 ) + private val fakeSession = FakeSession() + private val getDeviceFullInfoUseCase = mockk<GetDeviceFullInfoUseCase>(relaxed = true) private val fakeActiveSessionHolder = FakeActiveSessionHolder() private val fakeStringProvider = FakeStringProvider() - private val getDeviceFullInfoUseCase = mockk<GetDeviceFullInfoUseCase>() private val checkIfCurrentSessionCanBeVerifiedUseCase = mockk<CheckIfCurrentSessionCanBeVerifiedUseCase>() private val signoutSessionUseCase = mockk<SignoutSessionUseCase>() private val interceptSignoutFlowResponseUseCase = mockk<InterceptSignoutFlowResponseUseCase>() @@ -83,6 +88,7 @@ class SessionOverviewViewModelTest { private fun createViewModel() = SessionOverviewViewModel( initialState = SessionOverviewViewState(args), + session = fakeSession, stringProvider = fakeStringProvider.instance, getDeviceFullInfoUseCase = getDeviceFullInfoUseCase, checkIfCurrentSessionCanBeVerifiedUseCase = checkIfCurrentSessionCanBeVerifiedUseCase, @@ -108,8 +114,7 @@ class SessionOverviewViewModelTest { } @Test - fun `given the viewModel has been initialized then viewState is updated with session info and current session verification status`() { - // Given + fun `given the viewModel has been initialized then viewState is updated with session info`() { val deviceFullInfo = mockk<DeviceFullInfo>() every { getDeviceFullInfoUseCase.execute(A_SESSION_ID_1) } returns flowOf(deviceFullInfo) givenCurrentSessionIsTrusted() @@ -117,12 +122,11 @@ class SessionOverviewViewModelTest { deviceId = A_SESSION_ID_1, deviceInfo = Success(deviceFullInfo), isCurrentSessionTrusted = true, + pushers = Loading(), ) - // When val viewModel = createViewModel() - // Then viewModel.test() .assertLatestState { state -> state == expectedState } .finish() @@ -199,110 +203,6 @@ class SessionOverviewViewModelTest { .finish() } - @Test - fun `given another session and no reAuth is needed when handling signout action then signout process is performed`() { - // Given - val deviceFullInfo = mockk<DeviceFullInfo>() - every { deviceFullInfo.isCurrentDevice } returns false - every { getDeviceFullInfoUseCase.execute(A_SESSION_ID_1) } returns flowOf(deviceFullInfo) - givenSignoutSuccess(A_SESSION_ID_1) - every { refreshDevicesUseCase.execute() } just runs - val signoutAction = SessionOverviewAction.SignoutOtherSession - givenCurrentSessionIsTrusted() - val expectedViewState = SessionOverviewViewState( - deviceId = A_SESSION_ID_1, - isCurrentSessionTrusted = true, - deviceInfo = Success(deviceFullInfo), - isLoading = false, - ) - - // When - val viewModel = createViewModel() - val viewModelTest = viewModel.test() - viewModel.handle(signoutAction) - - // Then - viewModelTest - .assertStatesChanges( - expectedViewState, - { copy(isLoading = true) }, - { copy(isLoading = false) } - ) - .assertEvent { it is SessionOverviewViewEvent.SignoutSuccess } - .finish() - verify { - refreshDevicesUseCase.execute() - } - } - - @Test - fun `given another session and server error during signout when handling signout action then signout process is performed`() { - // Given - val deviceFullInfo = mockk<DeviceFullInfo>() - every { deviceFullInfo.isCurrentDevice } returns false - every { getDeviceFullInfoUseCase.execute(A_SESSION_ID_1) } returns flowOf(deviceFullInfo) - val serverError = Failure.OtherServerError(errorBody = "", httpCode = HttpsURLConnection.HTTP_UNAUTHORIZED) - givenSignoutError(A_SESSION_ID_1, serverError) - val signoutAction = SessionOverviewAction.SignoutOtherSession - givenCurrentSessionIsTrusted() - val expectedViewState = SessionOverviewViewState( - deviceId = A_SESSION_ID_1, - isCurrentSessionTrusted = true, - deviceInfo = Success(deviceFullInfo), - isLoading = false, - ) - fakeStringProvider.given(R.string.authentication_error, AUTH_ERROR_MESSAGE) - - // When - val viewModel = createViewModel() - val viewModelTest = viewModel.test() - viewModel.handle(signoutAction) - - // Then - viewModelTest - .assertStatesChanges( - expectedViewState, - { copy(isLoading = true) }, - { copy(isLoading = false) } - ) - .assertEvent { it is SessionOverviewViewEvent.SignoutError && it.error.message == AUTH_ERROR_MESSAGE } - .finish() - } - - @Test - fun `given another session and unexpected error during signout when handling signout action then signout process is performed`() { - // Given - val deviceFullInfo = mockk<DeviceFullInfo>() - every { deviceFullInfo.isCurrentDevice } returns false - every { getDeviceFullInfoUseCase.execute(A_SESSION_ID_1) } returns flowOf(deviceFullInfo) - val error = Exception() - givenSignoutError(A_SESSION_ID_1, error) - val signoutAction = SessionOverviewAction.SignoutOtherSession - givenCurrentSessionIsTrusted() - val expectedViewState = SessionOverviewViewState( - deviceId = A_SESSION_ID_1, - isCurrentSessionTrusted = true, - deviceInfo = Success(deviceFullInfo), - isLoading = false, - ) - fakeStringProvider.given(R.string.matrix_error, AN_ERROR_MESSAGE) - - // When - val viewModel = createViewModel() - val viewModelTest = viewModel.test() - viewModel.handle(signoutAction) - - // Then - viewModelTest - .assertStatesChanges( - expectedViewState, - { copy(isLoading = true) }, - { copy(isLoading = false) } - ) - .assertEvent { it is SessionOverviewViewEvent.SignoutError && it.error.message == AN_ERROR_MESSAGE } - .finish() - } - @Test fun `given another session and reAuth is needed during signout when handling signout action then requestReAuth is sent and pending auth is stored`() { // Given @@ -447,4 +347,30 @@ class SessionOverviewViewModelTest { every { deviceFullInfo.roomEncryptionTrustLevel } returns RoomEncryptionTrustLevel.Trusted every { getDeviceFullInfoUseCase.execute(A_SESSION_ID_2) } returns flowOf(deviceFullInfo) } + + @Test + fun `when viewModel init, then observe pushers and emit to state`() { + val pushers = listOf(aPusher(deviceId = A_SESSION_ID_1)) + fakeSession.pushersService().givenPushersLive(pushers) + + val viewModel = createViewModel() + + viewModel.test() + .assertLatestState { state -> state.pushers.invoke() == pushers } + .finish() + } + + @Test + fun `when handle TogglePushNotifications, then toggle enabled for device pushers`() { + val pushers = listOf( + aPusher(deviceId = A_SESSION_ID_1, enabled = false), + aPusher(deviceId = "another id", enabled = false) + ) + fakeSession.pushersService().givenPushersLive(pushers) + + val viewModel = createViewModel() + viewModel.handle(SessionOverviewAction.TogglePushNotifications(A_SESSION_ID_1, true)) + + fakeSession.pushersService().verifyOnlyTogglePusherCalled(pushers.first(), true) + } } diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakePushersService.kt b/vector/src/test/java/im/vector/app/test/fakes/FakePushersService.kt index 9e11b86871..60d50eab03 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakePushersService.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakePushersService.kt @@ -16,14 +16,30 @@ package im.vector.app.test.fakes +import androidx.lifecycle.liveData +import io.mockk.Ordering +import io.mockk.coVerify +import io.mockk.every import io.mockk.mockk import io.mockk.slot import io.mockk.verify import org.matrix.android.sdk.api.session.pushers.HttpPusher +import org.matrix.android.sdk.api.session.pushers.Pusher import org.matrix.android.sdk.api.session.pushers.PushersService class FakePushersService : PushersService by mockk(relaxed = true) { + fun givenPushersLive(pushers: List<Pusher>) { + every { getPushersLive() } returns liveData { emit(pushers) } + } + + fun verifyOnlyTogglePusherCalled(pusher: Pusher, enable: Boolean) { + coVerify(ordering = Ordering.ALL) { + getPushersLive() // verifies only getPushersLive and the following togglePusher was called + togglePusher(pusher, enable) + } + } + fun verifyEnqueueAddHttpPusher(): HttpPusher { val httpPusherSlot = slot<HttpPusher>() verify { enqueueAddHttpPusher(capture(httpPusherSlot)) } diff --git a/vector/src/test/java/im/vector/app/test/fixtures/PusherFixture.kt b/vector/src/test/java/im/vector/app/test/fixtures/PusherFixture.kt new file mode 100644 index 0000000000..300c66eca3 --- /dev/null +++ b/vector/src/test/java/im/vector/app/test/fixtures/PusherFixture.kt @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.test.fixtures + +import org.matrix.android.sdk.api.session.pushers.Pusher +import org.matrix.android.sdk.api.session.pushers.PusherData +import org.matrix.android.sdk.api.session.pushers.PusherState + +object PusherFixture { + + fun aPusher( + pushKey: String = "", + kind: String = "", + appId: String = "", + appDisplayName: String? = "", + deviceDisplayName: String? = "", + profileTag: String? = null, + lang: String? = "", + data: PusherData = PusherData("f.o/_matrix/push/v1/notify", ""), + enabled: Boolean = true, + deviceId: String? = "", + state: PusherState = PusherState.REGISTERED, + ) = Pusher( + pushKey, + kind, + appId, + appDisplayName, + deviceDisplayName, + profileTag, + lang, + data, + enabled, + deviceId, + state, + ) +} From def67b2e7d78797ec570269ff1cedbfc66abf7b4 Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa <jorgem@element.io> Date: Tue, 11 Oct 2022 17:05:47 +0200 Subject: [PATCH 187/187] Integrate WYSIWYG editor (#7288) * Add WYSIWYG lib dependency * Replace EditText with RichTextEditor * Add bold button, fix sending formatting messages issues * Add missing inline formatting buttons, make scrollview horizontal * Disable autocomplete for rich text editor * Add formatted text to messages sent, replies, quotes and edited messages. * Several fixes * Add changelog * Try to fix lint issues * Address review comments. * Exclude Epoxy KSP generated files from ktlint checks --- build.gradle | 3 + changelog.d/7288.feature | 1 + changelog.d/7288.sdk | 10 + dependencies.gradle | 1 + dependencies_groups.gradle | 1 + .../src/main/res/values/strings.xml | 3 + .../room/model/relation/RelationService.kt | 10 +- .../sdk/api/session/room/send/SendService.kt | 11 +- .../session/room/timeline/TimelineEvent.kt | 12 +- .../room/relation/DefaultRelationService.kt | 9 +- .../session/room/relation/EventEditor.kt | 13 +- .../session/room/send/DefaultSendService.kt | 9 +- .../room/send/LocalEchoEventFactory.kt | 23 +- .../src/main/res/values/config-settings.xml | 2 + vector/build.gradle | 4 + .../app/features/command/CommandParser.kt | 54 +++-- .../app/features/command/ParsedCommand.kt | 1 + .../detail/composer/MessageComposerAction.kt | 2 +- .../composer/MessageComposerFragment.kt | 130 ++++++----- .../detail/composer/MessageComposerView.kt | 151 +++---------- .../composer/MessageComposerViewModel.kt | 44 +++- .../composer/PlainTextComposerLayout.kt | 185 ++++++++++++++++ .../detail/composer/RichTextComposerLayout.kt | 202 ++++++++++++++++++ .../composer/voice/VoiceRecorderFragment.kt | 12 ++ .../features/settings/VectorPreferences.kt | 5 + .../main/res/drawable/ic_composer_bold.xml | 10 + .../main/res/drawable/ic_composer_italic.xml | 10 + .../drawable/ic_composer_strikethrough.xml | 12 ++ .../res/drawable/ic_composer_underlined.xml | 13 ++ .../res/layout/composer_rich_text_layout.xml | 156 ++++++++++++++ ...ich_text_layout_constraint_set_compact.xml | 200 +++++++++++++++++ ...ch_text_layout_constraint_set_expanded.xml | 198 +++++++++++++++++ .../src/main/res/layout/fragment_composer.xml | 32 ++- .../res/layout/view_rich_text_menu_button.xml | 10 + .../src/main/res/xml/vector_settings_labs.xml | 7 + .../app/features/command/CommandParserTest.kt | 2 +- 36 files changed, 1316 insertions(+), 232 deletions(-) create mode 100644 changelog.d/7288.feature create mode 100644 changelog.d/7288.sdk create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/composer/PlainTextComposerLayout.kt create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/composer/RichTextComposerLayout.kt create mode 100644 vector/src/main/res/drawable/ic_composer_bold.xml create mode 100644 vector/src/main/res/drawable/ic_composer_italic.xml create mode 100644 vector/src/main/res/drawable/ic_composer_strikethrough.xml create mode 100644 vector/src/main/res/drawable/ic_composer_underlined.xml create mode 100644 vector/src/main/res/layout/composer_rich_text_layout.xml create mode 100644 vector/src/main/res/layout/composer_rich_text_layout_constraint_set_compact.xml create mode 100644 vector/src/main/res/layout/composer_rich_text_layout_constraint_set_expanded.xml create mode 100644 vector/src/main/res/layout/view_rich_text_menu_button.xml diff --git a/build.gradle b/build.gradle index 9e0b3d1282..d38d430b25 100644 --- a/build.gradle +++ b/build.gradle @@ -148,6 +148,9 @@ allprojects { // To have XML report for Danger reporter(org.jlleitschuh.gradle.ktlint.reporter.ReporterType.CHECKSTYLE) } + filter { + exclude { element -> element.file.path.contains("$buildDir/generated/") } + } disabledRules = [ // TODO Re-enable these 4 rules after reformatting project "indent", diff --git a/changelog.d/7288.feature b/changelog.d/7288.feature new file mode 100644 index 0000000000..be00e26179 --- /dev/null +++ b/changelog.d/7288.feature @@ -0,0 +1 @@ +Add WYSIWYG editor. diff --git a/changelog.d/7288.sdk b/changelog.d/7288.sdk new file mode 100644 index 0000000000..9c4a33ad22 --- /dev/null +++ b/changelog.d/7288.sdk @@ -0,0 +1,10 @@ +Add `formattedText` or similar optional parameters in several methods: + +* RelationService: + * editTextMessage + * editReply + * replyToMessage +* SendService: + * sendQuotedTextMessage + +This allows us to send any HTML formatted text message without needing to rely on automatic Markdown > HTML translation. All these new parameters have a `null` value by default, so previous calls to these API methods remain compatible. diff --git a/dependencies.gradle b/dependencies.gradle index a5cfa18791..59e64ee4dc 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -102,6 +102,7 @@ ext.libs = [ ], element : [ 'opusencoder' : "io.element.android:opusencoder:1.1.0", + 'wysiwyg' : "io.element.android:wysiwyg:0.1.0" ], squareup : [ 'moshi' : "com.squareup.moshi:moshi:$moshi", diff --git a/dependencies_groups.gradle b/dependencies_groups.gradle index 991d54d9af..e614bf1329 100644 --- a/dependencies_groups.gradle +++ b/dependencies_groups.gradle @@ -178,6 +178,7 @@ ext.groups = [ 'org.apache.httpcomponents', 'org.apache.sanselan', 'org.bouncycastle', + 'org.ccil.cowan.tagsoup', 'org.checkerframework', 'org.codehaus', 'org.codehaus.groovy', diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 63c1f8a8bb..7f9a4f7687 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -446,6 +446,9 @@ <string name="labs_enable_deferred_dm_title">Enable deferred DMs</string> <string name="labs_enable_deferred_dm_summary">Create DM only on first message</string> + <string name="labs_enable_rich_text_editor_title">Enable rich text editor</string> + <string name="labs_enable_rich_text_editor_summary">Use a rich text editor to send formatted messages</string> + <!-- Home fragment --> <string name="invitations_header">Invites</string> <string name="low_priority_header">Low priority</string> diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt index d34ea3c7d3..e7fcabf386 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt @@ -91,7 +91,8 @@ interface RelationService { * Edit a text message body. Limited to "m.text" contentType. * @param targetEvent The event to edit * @param msgType the message type - * @param newBodyText The edited body + * @param newBodyText The edited body in plain text + * @param newFormattedBodyText The edited body with format * @param newBodyAutoMarkdown true to parse markdown on the new body * @param compatibilityBodyText The text that will appear on clients that don't support yet edition */ @@ -99,6 +100,7 @@ interface RelationService { targetEvent: TimelineEvent, msgType: String, newBodyText: CharSequence, + newFormattedBodyText: CharSequence? = null, newBodyAutoMarkdown: Boolean, compatibilityBodyText: String = "* $newBodyText" ): Cancelable @@ -108,13 +110,15 @@ interface RelationService { * This method will take the new body (stripped from fallbacks) and re-add them before sending. * @param replyToEdit The event to edit * @param originalTimelineEvent the message that this reply (being edited) is relating to - * @param newBodyText The edited body (stripped from in reply to content) + * @param newBodyText The plain text edited body (stripped from in reply to content) + * @param newFormattedBodyText The formatted edited body (stripped from in reply to content) * @param compatibilityBodyText The text that will appear on clients that don't support yet edition */ fun editReply( replyToEdit: TimelineEvent, originalTimelineEvent: TimelineEvent, newBodyText: String, + newFormattedBodyText: String? = null, compatibilityBodyText: String = "* $newBodyText" ): Cancelable @@ -133,6 +137,7 @@ interface RelationService { * by the sdk into pills. * @param eventReplied the event referenced by the reply * @param replyText the reply text + * @param replyFormattedText the reply text, formatted * @param autoMarkdown If true, the SDK will generate a formatted HTML message from the body text if markdown syntax is present * @param showInThread If true, relation will be added to the reply in order to be visible from within threads * @param rootThreadEventId If show in thread is true then we need the rootThreadEventId to generate the relation @@ -140,6 +145,7 @@ interface RelationService { fun replyToMessage( eventReplied: TimelineEvent, replyText: CharSequence, + replyFormattedText: CharSequence? = null, autoMarkdown: Boolean = false, showInThread: Boolean = false, rootThreadEventId: String? = null diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt index 9cf062356f..de9bcfbf0d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt @@ -60,12 +60,19 @@ interface SendService { /** * Method to quote an events content. * @param quotedEvent The event to which we will quote it's content. - * @param text the text message to send + * @param text the plain text message to send + * @param formattedText the formatted text message to send * @param autoMarkdown If true, the SDK will generate a formatted HTML message from the body text if markdown syntax is present * @param rootThreadEventId when this param is not null, the message will be sent in this specific thread * @return a [Cancelable] */ - fun sendQuotedTextMessage(quotedEvent: TimelineEvent, text: String, autoMarkdown: Boolean, rootThreadEventId: String? = null): Cancelable + fun sendQuotedTextMessage( + quotedEvent: TimelineEvent, + text: String, + formattedText: String? = null, + autoMarkdown: Boolean, + rootThreadEventId: String? = null + ): Cancelable /** * Method to send a media asynchronously. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt index d391abf1e6..7341fd922e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt @@ -33,6 +33,7 @@ import org.matrix.android.sdk.api.session.room.model.ReadReceipt import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent import org.matrix.android.sdk.api.session.room.model.message.MessageContent +import org.matrix.android.sdk.api.session.room.model.message.MessageContentWithFormattedBody import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent import org.matrix.android.sdk.api.session.room.model.message.MessageStickerContent import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent @@ -181,7 +182,8 @@ fun TimelineEvent.isRootThread(): Boolean { * Get the latest message body, after a possible edition, stripping the reply prefix if necessary. */ fun TimelineEvent.getTextEditableContent(): String { - val lastContentBody = getLastMessageContent()?.body ?: return "" + val lastMessageContent = getLastMessageContent() + val lastContentBody = lastMessageContent.getFormattedBody() ?: return "" return if (isReply()) { extractUsefulTextFromReply(lastContentBody) } else { @@ -199,3 +201,11 @@ fun MessageContent.getTextDisplayableContent(): String { ?: (this as MessageTextContent?)?.matrixFormattedBody?.let { ContentUtils.formatSpoilerTextFromHtml(it) } ?: body } + +fun MessageContent?.getFormattedBody(): String? { + return if (this is MessageContentWithFormattedBody) { + formattedBody + } else { + this?.body + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt index 9839a44427..ddf3e41dff 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt @@ -105,19 +105,21 @@ internal class DefaultRelationService @AssistedInject constructor( targetEvent: TimelineEvent, msgType: String, newBodyText: CharSequence, + newFormattedBodyText: CharSequence?, newBodyAutoMarkdown: Boolean, compatibilityBodyText: String ): Cancelable { - return eventEditor.editTextMessage(targetEvent, msgType, newBodyText, newBodyAutoMarkdown, compatibilityBodyText) + return eventEditor.editTextMessage(targetEvent, msgType, newBodyText, newFormattedBodyText, newBodyAutoMarkdown, compatibilityBodyText) } override fun editReply( replyToEdit: TimelineEvent, originalTimelineEvent: TimelineEvent, newBodyText: String, + newFormattedBodyText: String?, compatibilityBodyText: String ): Cancelable { - return eventEditor.editReply(replyToEdit, originalTimelineEvent, newBodyText, compatibilityBodyText) + return eventEditor.editReply(replyToEdit, originalTimelineEvent, newBodyText, newFormattedBodyText, compatibilityBodyText) } override suspend fun fetchEditHistory(eventId: String): List<Event> { @@ -127,6 +129,7 @@ internal class DefaultRelationService @AssistedInject constructor( override fun replyToMessage( eventReplied: TimelineEvent, replyText: CharSequence, + replyFormattedText: CharSequence?, autoMarkdown: Boolean, showInThread: Boolean, rootThreadEventId: String? @@ -135,6 +138,7 @@ internal class DefaultRelationService @AssistedInject constructor( roomId = roomId, eventReplied = eventReplied, replyText = replyText, + replyTextFormatted = replyFormattedText, autoMarkdown = autoMarkdown, rootThreadEventId = rootThreadEventId, showInThread = showInThread @@ -178,6 +182,7 @@ internal class DefaultRelationService @AssistedInject constructor( roomId = roomId, eventReplied = eventReplied, replyText = replyInThreadText, + replyTextFormatted = formattedText, autoMarkdown = autoMarkdown, rootThreadEventId = rootThreadEventId, showInThread = false diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/EventEditor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/EventEditor.kt index 795e9003ce..c83539c8fd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/EventEditor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/EventEditor.kt @@ -23,6 +23,7 @@ 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.util.Cancelable import org.matrix.android.sdk.api.util.NoOpCancellable +import org.matrix.android.sdk.api.util.TextContent import org.matrix.android.sdk.internal.database.mapper.toEntity import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository @@ -42,19 +43,25 @@ internal class EventEditor @Inject constructor( targetEvent: TimelineEvent, msgType: String, newBodyText: CharSequence, + newBodyFormattedText: CharSequence?, newBodyAutoMarkdown: Boolean, compatibilityBodyText: String ): Cancelable { val roomId = targetEvent.roomId if (targetEvent.root.sendState.hasFailed()) { // We create a new in memory event for the EventSenderProcessor but we keep the eventId of the failed event. - val editedEvent = eventFactory.createTextEvent(roomId, msgType, newBodyText, newBodyAutoMarkdown).copy( + val editedEvent = if (newBodyFormattedText != null) { + val content = TextContent(newBodyText.toString(), newBodyFormattedText.toString()) + eventFactory.createFormattedTextEvent(roomId, content, msgType) + } else { + eventFactory.createTextEvent(roomId, msgType, newBodyText, newBodyAutoMarkdown) + }.copy( eventId = targetEvent.eventId ) return sendFailedEvent(targetEvent, editedEvent) } else if (targetEvent.root.sendState.isSent()) { val event = eventFactory - .createReplaceTextEvent(roomId, targetEvent.eventId, newBodyText, newBodyAutoMarkdown, msgType, compatibilityBodyText) + .createReplaceTextEvent(roomId, targetEvent.eventId, newBodyText, newBodyFormattedText, newBodyAutoMarkdown, msgType, compatibilityBodyText) return sendReplaceEvent(event) } else { // Should we throw? @@ -100,6 +107,7 @@ internal class EventEditor @Inject constructor( replyToEdit: TimelineEvent, originalTimelineEvent: TimelineEvent, newBodyText: String, + newBodyFormattedText: String?, compatibilityBodyText: String ): Cancelable { val roomId = replyToEdit.roomId @@ -109,6 +117,7 @@ internal class EventEditor @Inject constructor( roomId = roomId, eventReplied = originalTimelineEvent, replyText = newBodyText, + replyTextFormatted = newBodyFormattedText, autoMarkdown = false, showInThread = false )?.copy( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt index 418000abed..a3f2825a0c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt @@ -99,11 +99,18 @@ internal class DefaultSendService @AssistedInject constructor( .let { sendEvent(it) } } - override fun sendQuotedTextMessage(quotedEvent: TimelineEvent, text: String, autoMarkdown: Boolean, rootThreadEventId: String?): Cancelable { + override fun sendQuotedTextMessage( + quotedEvent: TimelineEvent, + text: String, + formattedText: String?, + autoMarkdown: Boolean, + rootThreadEventId: String? + ): Cancelable { return localEchoEventFactory.createQuotedTextEvent( roomId = roomId, quotedEvent = quotedEvent, text = text, + formattedText = formattedText, autoMarkdown = autoMarkdown, rootThreadEventId = rootThreadEventId ) 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 4fbc91e9ec..4d5e574592 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 @@ -124,19 +124,23 @@ internal class LocalEchoEventFactory @Inject constructor( roomId: String, targetEventId: String, newBodyText: CharSequence, + newBodyFormattedText: CharSequence?, newBodyAutoMarkdown: Boolean, msgType: String, compatibilityText: String ): Event { + val content = if (newBodyFormattedText != null) { + TextContent(newBodyText.toString(), newBodyFormattedText.toString()).toMessageTextContent(msgType) + } else { + createTextContent(newBodyText, newBodyAutoMarkdown).toMessageTextContent(msgType) + }.toContent() return createMessageEvent( roomId, MessageTextContent( msgType = msgType, body = compatibilityText, relatesTo = RelationDefaultContent(RelationType.REPLACE, targetEventId), - newContent = createTextContent(newBodyText, newBodyAutoMarkdown) - .toMessageTextContent(msgType) - .toContent() + newContent = content, ) ) } @@ -581,6 +585,7 @@ internal class LocalEchoEventFactory @Inject constructor( roomId: String, eventReplied: TimelineEvent, replyText: CharSequence, + replyTextFormatted: CharSequence?, autoMarkdown: Boolean, rootThreadEventId: String? = null, showInThread: Boolean @@ -594,7 +599,7 @@ internal class LocalEchoEventFactory @Inject constructor( val body = bodyForReply(eventReplied.getLastMessageContent(), eventReplied.isReply()) // As we always supply formatted body for replies we should force the MarkdownParser to produce html. - val replyTextFormatted = markdownParser.parse(replyText, force = true, advanced = autoMarkdown).takeFormatted() + val finalReplyTextFormatted = replyTextFormatted?.toString() ?: markdownParser.parse(replyText, force = true, advanced = autoMarkdown).takeFormatted() // Body of the original message may not have formatted version, so may also have to convert to html. val bodyFormatted = body.formattedText ?: markdownParser.parse(body.text, force = true, advanced = autoMarkdown).takeFormatted() val replyFormatted = buildFormattedReply( @@ -602,7 +607,7 @@ internal class LocalEchoEventFactory @Inject constructor( userLink, userId, bodyFormatted, - replyTextFormatted + finalReplyTextFormatted ) // // > <@alice:example.org> This is the original body @@ -765,18 +770,20 @@ internal class LocalEchoEventFactory @Inject constructor( roomId: String, quotedEvent: TimelineEvent, text: String, + formattedText: String?, autoMarkdown: Boolean, rootThreadEventId: String? ): Event { val messageContent = quotedEvent.getLastMessageContent() - val textMsg = messageContent?.body + val textMsg = if (messageContent is MessageContentWithFormattedBody) { messageContent.formattedBody } else { messageContent?.body } val quoteText = legacyRiotQuoteText(textMsg, text) + val quoteFormattedText = "<blockquote>$textMsg</blockquote>$formattedText" return if (rootThreadEventId != null) { createMessageEvent( roomId, markdownParser - .parse(quoteText, force = true, advanced = autoMarkdown) + .parse(quoteText, force = true, advanced = autoMarkdown).copy(formattedText = quoteFormattedText) .toThreadTextContent( rootThreadEventId = rootThreadEventId, latestThreadEventId = localEchoRepository.getLatestThreadEvent(rootThreadEventId), @@ -786,7 +793,7 @@ internal class LocalEchoEventFactory @Inject constructor( } else { createFormattedTextEvent( roomId, - markdownParser.parse(quoteText, force = true, advanced = autoMarkdown), + markdownParser.parse(quoteText, force = true, advanced = autoMarkdown).copy(formattedText = quoteFormattedText), MessageType.MSGTYPE_TEXT ) } diff --git a/vector-config/src/main/res/values/config-settings.xml b/vector-config/src/main/res/values/config-settings.xml index c69452e3d0..11fbb2f147 100755 --- a/vector-config/src/main/res/values/config-settings.xml +++ b/vector-config/src/main/res/values/config-settings.xml @@ -43,6 +43,8 @@ <bool name="settings_labs_new_app_layout_default">true</bool> <bool name="settings_timeline_show_live_sender_info_visible">true</bool> <bool name="settings_timeline_show_live_sender_info_default">false</bool> + <bool name="settings_labs_rich_text_editor_visible">true</bool> + <bool name="settings_labs_rich_text_editor_default">false</bool> <!-- Level 1: Advanced settings --> <!-- Level 1: Help and about --> diff --git a/vector/build.gradle b/vector/build.gradle index 37a98d8242..833d06f6d6 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -104,6 +104,7 @@ android { } } dependencies { + implementation project(":vector-config") api project(":matrix-sdk-android") implementation project(":matrix-sdk-android-flow") @@ -143,6 +144,9 @@ dependencies { // Opus Encoder implementation libs.element.opusencoder + // WYSIWYG Editor + implementation libs.element.wysiwyg + // Log api libs.jakewharton.timber 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 81950fe86c..e08bc9fb64 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 @@ -18,6 +18,7 @@ package im.vector.app.features.command 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 org.matrix.android.sdk.api.MatrixPatterns import org.matrix.android.sdk.api.MatrixUrls.isMxcUrl @@ -30,39 +31,30 @@ class CommandParser @Inject constructor() { /** * Convert the text message into a Slash command. * - * @param textMessage the text message + * @param textMessage the text message in plain text + * @param formattedMessage the text messaged in HTML format * @param isInThreadTimeline true if the user is currently typing in a thread * @return a parsed slash command (ok or error) */ - fun parseSlashCommand(textMessage: CharSequence, isInThreadTimeline: Boolean): ParsedCommand { + @Suppress("NAME_SHADOWING") + fun parseSlashCommand(textMessage: CharSequence, formattedMessage: String?, isInThreadTimeline: Boolean): ParsedCommand { // check if it has the Slash marker - return if (!textMessage.startsWith("/")) { + val message = formattedMessage ?: textMessage + return if (!message.startsWith("/")) { ParsedCommand.ErrorNotACommand } else { // "/" only - if (textMessage.length == 1) { + if (message.length == 1) { return ParsedCommand.ErrorEmptySlashCommand } // Exclude "//" - if ("/" == textMessage.substring(1, 2)) { + if ("/" == message.substring(1, 2)) { return ParsedCommand.ErrorNotACommand } - val messageParts = try { - textMessage.split("\\s+".toRegex()).dropLastWhile { it.isEmpty() } - } catch (e: Exception) { - Timber.e(e, "## parseSlashCommand() : split failed") - null - } - - // test if the string cut fails - if (messageParts.isNullOrEmpty()) { - return ParsedCommand.ErrorEmptySlashCommand - } - + val (messageParts, message) = extractMessage(message.toString()) ?: return ParsedCommand.ErrorEmptySlashCommand val slashCommand = messageParts.first() - val message = textMessage.substring(slashCommand.length).trim() getNotSupportedByThreads(isInThreadTimeline, slashCommand)?.let { return ParsedCommand.ErrorCommandNotSupportedInThreads(it) @@ -71,7 +63,12 @@ class CommandParser @Inject constructor() { when { Command.PLAIN.matches(slashCommand) -> { if (message.isNotEmpty()) { - ParsedCommand.SendPlainText(message = message) + if (formattedMessage != null) { + val trimmedPlainTextMessage = extractMessage(textMessage.toString())?.second.orEmpty() + ParsedCommand.SendFormattedText(message = trimmedPlainTextMessage, formattedMessage = message) + } else { + ParsedCommand.SendPlainText(message = message) + } } else { ParsedCommand.ErrorSyntax(Command.PLAIN) } @@ -415,6 +412,25 @@ class CommandParser @Inject constructor() { } } + private fun extractMessage(message: String): Pair<List<String>, String>? { + val messageParts = try { + message.split("\\s+".toRegex()).dropLastWhile { it.isEmpty() } + } catch (e: Exception) { + Timber.e(e, "## parseSlashCommand() : split failed") + null + } + + // test if the string cut fails + if (messageParts.isNullOrEmpty()) { + return null + } + + val slashCommand = messageParts.first() + val trimmedMessage = message.substring(slashCommand.length).trim() + + return messageParts to trimmedMessage + } + private val notSupportedThreadsCommands: List<Command> by lazy { Command.values().filter { !it.isThreadCommand diff --git a/vector/src/main/java/im/vector/app/features/command/ParsedCommand.kt b/vector/src/main/java/im/vector/app/features/command/ParsedCommand.kt index eee786253b..670eddefda 100644 --- a/vector/src/main/java/im/vector/app/features/command/ParsedCommand.kt +++ b/vector/src/main/java/im/vector/app/features/command/ParsedCommand.kt @@ -39,6 +39,7 @@ sealed interface ParsedCommand { // Valid commands: data class SendPlainText(val message: CharSequence) : ParsedCommand + data class SendFormattedText(val message: CharSequence, val formattedMessage: String) : ParsedCommand data class SendEmote(val message: CharSequence) : ParsedCommand data class SendRainbow(val message: CharSequence) : ParsedCommand data class SendRainbowEmote(val message: CharSequence) : ParsedCommand diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerAction.kt index 97e6657fc2..82adcd014a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerAction.kt @@ -23,7 +23,7 @@ import org.matrix.android.sdk.api.session.content.ContentAttachmentData import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent sealed class MessageComposerAction : VectorViewModelAction { - data class SendMessage(val text: CharSequence, val autoMarkdown: Boolean) : MessageComposerAction() + data class SendMessage(val text: CharSequence, val formattedText: String?, val autoMarkdown: Boolean) : MessageComposerAction() data class EnterEditMode(val eventId: String) : MessageComposerAction() data class EnterQuoteMode(val eventId: String) : MessageComposerAction() data class EnterReplyMode(val eventId: String) : MessageComposerAction() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt index 4573dc25c1..b3abfa480e 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt @@ -37,6 +37,7 @@ import androidx.annotation.RequiresApi import androidx.annotation.StringRes import androidx.core.content.ContextCompat import androidx.core.text.buildSpannedString +import androidx.core.view.isGone import androidx.core.view.isInvisible import androidx.core.view.isVisible import androidx.lifecycle.Lifecycle @@ -161,6 +162,14 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A private val messageComposerViewModel: MessageComposerViewModel by parentFragmentViewModel() private lateinit var sharedActionViewModel: MessageSharedActionViewModel + private val composer: MessageComposerView get() { + return if (vectorPreferences.isRichTextEditorEnabled()) { + views.richTextComposerLayout + } else { + views.composerLayout + } + } + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentComposerBinding { return FragmentComposerBinding.inflate(inflater, container, false) } @@ -175,6 +184,9 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A setupComposer() setupEmojiButton() + views.composerLayout.isGone = vectorPreferences.isRichTextEditorEnabled() + views.richTextComposerLayout.isVisible = vectorPreferences.isRichTextEditorEnabled() + messageComposerViewModel.observeViewEvents { when (it) { is MessageComposerViewEvents.JoinRoomCommandSuccess -> handleJoinedToAnotherRoom(it) @@ -218,29 +230,33 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A if (withState(messageComposerViewModel) { it.isVoiceRecording } && requireActivity().isChangingConfigurations) { // we're rotating, maintain any active recordings } else { - messageComposerViewModel.handle(MessageComposerAction.OnEntersBackground(views.composerLayout.text.toString())) + messageComposerViewModel.handle(MessageComposerAction.OnEntersBackground(composer.text.toString())) } } override fun onDestroyView() { super.onDestroyView() - autoCompleter.clear() + if (!vectorPreferences.isRichTextEditorEnabled()) { + autoCompleter.clear() + } messageComposerViewModel.endAllVoiceActions() } override fun invalidate() = withState(timelineViewModel, messageComposerViewModel) { mainState, messageComposerState -> if (mainState.tombstoneEvent != null) return@withState - views.root.isInvisible = !messageComposerState.isComposerVisible - views.composerLayout.views.sendButton.isInvisible = !messageComposerState.isSendButtonVisible + composer.setInvisible(!messageComposerState.isComposerVisible) + composer.sendButton.isInvisible = !messageComposerState.isSendButtonVisible } private fun setupComposer() { - val composerEditText = views.composerLayout.views.composerEditText + val composerEditText = composer.editText composerEditText.setHint(R.string.room_message_placeholder) - autoCompleter.setup(composerEditText) + if (!vectorPreferences.isRichTextEditorEnabled()) { + autoCompleter.setup(composerEditText) + } observerUserTyping() @@ -257,20 +273,22 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A !keyEvent.isShiftPressed && keyEvent.keyCode == KeyEvent.KEYCODE_ENTER && resources.configuration.keyboard != Configuration.KEYBOARD_NOKEYS - if (isSendAction || externalKeyboardPressedEnter) { + val result = if (isSendAction || externalKeyboardPressedEnter) { sendTextMessage(v.text) true } else false + composer.setTextIfDifferent(null) + result } - views.composerLayout.views.composerEmojiButton.isVisible = vectorPreferences.showEmojiKeyboard() + composer.emojiButton?.isVisible = vectorPreferences.showEmojiKeyboard() val showKeyboard = withState(timelineViewModel) { it.showKeyboardWhenPresented } if (isThreadTimeLine() && showKeyboard) { // Show keyboard when the user started a thread - views.composerLayout.views.composerEditText.showKeyboard(andRequestFocus = true) + composerEditText.showKeyboard(andRequestFocus = true) } - views.composerLayout.callback = object : MessageComposerView.Callback { + composer.callback = object : PlainTextComposerLayout.Callback { override fun onAddAttachment() { if (!::attachmentTypeSelector.isInitialized) { attachmentTypeSelector = AttachmentTypeSelectorView(vectorBaseActivity, vectorBaseActivity.layoutInflater, this@MessageComposerFragment) @@ -286,15 +304,15 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A vectorFeatures.isVoiceBroadcastEnabled(), // TODO check user permission ) } - attachmentTypeSelector.show(views.composerLayout.views.attachmentButton) + attachmentTypeSelector.show(composer.attachmentButton) } override fun onExpandOrCompactChange() { - views.composerLayout.views.composerEmojiButton.isVisible = isEmojiKeyboardVisible + composer.emojiButton?.isVisible = isEmojiKeyboardVisible } override fun onSendMessage(text: CharSequence) { - sendTextMessage(text) + sendTextMessage(text, composer.formattedText) } override fun onCloseRelatedMessage() { @@ -311,16 +329,20 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A } } - private fun sendTextMessage(text: CharSequence) { + private fun sendTextMessage(text: CharSequence, formattedText: String? = null) { if (lockSendButton) { Timber.w("Send button is locked") return } if (text.isNotBlank()) { // We collapse ASAP, if not there will be a slight annoying delay - views.composerLayout.collapse(true) + composer.collapse(true) lockSendButton = true - messageComposerViewModel.handle(MessageComposerAction.SendMessage(text, vectorPreferences.isMarkdownEnabled())) + if (formattedText != null) { + messageComposerViewModel.handle(MessageComposerAction.SendMessage(text, formattedText, false)) + } else { + messageComposerViewModel.handle(MessageComposerAction.SendMessage(text, null, vectorPreferences.isMarkdownEnabled())) + } emojiPopup.dismiss() } } @@ -336,22 +358,22 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A return isHandled } - private fun renderRegularMode(content: String) { + private fun renderRegularMode(content: CharSequence) { autoCompleter.exitSpecialMode() - views.composerLayout.collapse() - views.composerLayout.setTextIfDifferent(content) - views.composerLayout.views.sendButton.contentDescription = getString(R.string.action_send) + composer.collapse() + composer.setTextIfDifferent(content) + composer.sendButton.contentDescription = getString(R.string.action_send) } private fun renderSpecialMode( event: TimelineEvent, @DrawableRes iconRes: Int, @StringRes descriptionRes: Int, - defaultContent: String + defaultContent: CharSequence, ) { autoCompleter.enterSpecialMode() // switch to expanded bar - views.composerLayout.views.composerRelatedMessageTitle.apply { + composer.composerRelatedMessageTitle.apply { text = event.senderInfo.disambiguatedDisplayName setTextColor(matrixItemColorProvider.getColor(MatrixItem.UserItem(event.root.senderId ?: "@"))) } @@ -369,32 +391,32 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A val document = parser.parse(messageContent.formattedBody ?: messageContent.body) formattedBody = eventHtmlRenderer.render(document, pillsPostProcessor) } - views.composerLayout.views.composerRelatedMessageContent.text = (formattedBody ?: nonFormattedBody) + composer.composerRelatedMessageContent.text = (formattedBody ?: nonFormattedBody) // Image Event val data = event.buildImageContentRendererData(dimensionConverter.dpToPx(66)) val isImageVisible = if (data != null) { - imageContentRenderer.render(data, ImageContentRenderer.Mode.THUMBNAIL, views.composerLayout.views.composerRelatedMessageImage) + imageContentRenderer.render(data, ImageContentRenderer.Mode.THUMBNAIL, composer.composerRelatedMessageImage) true } else { - imageContentRenderer.clear(views.composerLayout.views.composerRelatedMessageImage) + imageContentRenderer.clear(composer.composerRelatedMessageImage) false } - views.composerLayout.views.composerRelatedMessageImage.isVisible = isImageVisible + composer.composerRelatedMessageImage.isVisible = isImageVisible - views.composerLayout.setTextIfDifferent(defaultContent) + composer.replaceFormattedContent(defaultContent) - views.composerLayout.views.composerRelatedMessageActionIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), iconRes)) - views.composerLayout.views.sendButton.contentDescription = getString(descriptionRes) + composer.composerRelatedMessageActionIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), iconRes)) + composer.sendButton.contentDescription = getString(descriptionRes) - avatarRenderer.render(event.senderInfo.toMatrixItem(), views.composerLayout.views.composerRelatedMessageAvatar) + avatarRenderer.render(event.senderInfo.toMatrixItem(), composer.composerRelatedMessageAvatar) - views.composerLayout.expand { + composer.expand { if (isAdded) { // need to do it here also when not using quick reply focusComposerAndShowKeyboard() - views.composerLayout.views.composerRelatedMessageImage.isVisible = isImageVisible + composer.composerRelatedMessageImage.isVisible = isImageVisible } } focusComposerAndShowKeyboard() @@ -402,7 +424,7 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A private fun observerUserTyping() { if (isThreadTimeLine()) return - views.composerLayout.views.composerEditText.textChanges() + composer.editText.textChanges() .skipInitialValue() .debounce(300) .map { it.isNotEmpty() } @@ -412,7 +434,7 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A } .launchIn(viewLifecycleOwner.lifecycleScope) - views.composerLayout.views.composerEditText.focusChanges() + composer.editText.focusChanges() .onEach { timelineViewModel.handle(RoomDetailAction.ComposerFocusChange(it)) } @@ -420,18 +442,18 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A } private fun focusComposerAndShowKeyboard() { - if (views.composerLayout.isVisible) { - views.composerLayout.views.composerEditText.showKeyboard(andRequestFocus = true) + if (composer.isVisible) { + composer.editText.showKeyboard(andRequestFocus = true) } } private fun handleSendButtonVisibilityChanged(event: MessageComposerViewEvents.AnimateSendButtonVisibility) { if (event.isVisible) { - views.root.views.sendButton.alpha = 0f - views.root.views.sendButton.isVisible = true - views.root.views.sendButton.animate().alpha(1f).setDuration(150).start() + composer.sendButton.alpha = 0f + composer.sendButton.isVisible = true + composer.sendButton.animate().alpha(1f).setDuration(150).start() } else { - views.root.views.sendButton.isInvisible = true + composer.sendButton.isInvisible = true } } @@ -455,18 +477,18 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A rootView = views.root, keyboardAnimationStyle = R.style.emoji_fade_animation_style, onEmojiPopupShownListener = { - views.composerLayout.views.composerEmojiButton.apply { + composer.emojiButton?.apply { contentDescription = getString(R.string.a11y_close_emoji_picker) setImageResource(R.drawable.ic_keyboard) } }, onEmojiPopupDismissListener = lifecycleAwareDismissAction { - views.composerLayout.views.composerEmojiButton.apply { + composer.emojiButton?.apply { contentDescription = getString(R.string.a11y_open_emoji_picker) setImageResource(R.drawable.ic_insert_emoji) } }, - editText = views.composerLayout.views.composerEditText + editText = composer.editText ) } @@ -483,7 +505,7 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A } private fun setupEmojiButton() { - views.composerLayout.views.composerEmojiButton.debouncedClicks { + composer.emojiButton?.debouncedClicks { emojiPopup.toggle() } } @@ -494,7 +516,7 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A } private fun handleJoinedToAnotherRoom(action: MessageComposerViewEvents.JoinRoomCommandSuccess) { - views.composerLayout.setTextIfDifferent("") + composer.setTextIfDifferent("") lockSendButton = false navigator.openRoom(vectorBaseActivity, action.roomId) } @@ -549,7 +571,7 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A private fun handleSlashCommandResultOk(parsedCommand: ParsedCommand) { dismissLoadingDialog() - views.composerLayout.setTextIfDifferent("") + composer.setTextIfDifferent("") when (parsedCommand) { is ParsedCommand.DevTools -> { navigator.openDevTools(requireContext(), roomId) @@ -608,7 +630,7 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A override fun onContactAttachmentReady(contactAttachment: ContactAttachment) { val formattedContact = contactAttachment.toHumanReadable() - messageComposerViewModel.handle(MessageComposerAction.SendMessage(formattedContact, false)) + messageComposerViewModel.handle(MessageComposerAction.SendMessage(formattedContact, null, false)) } override fun onAttachmentError(throwable: Throwable) { @@ -718,13 +740,13 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A @SuppressLint("SetTextI18n") private fun insertUserDisplayNameInTextEditor(userId: String) { - val startToCompose = views.composerLayout.text.isNullOrBlank() + val startToCompose = composer.text.isNullOrBlank() if (startToCompose && userId == session.myUserId) { // Empty composer, current user: start an emote - views.composerLayout.views.composerEditText.setText("${Command.EMOTE.command} ") - views.composerLayout.views.composerEditText.setSelection(Command.EMOTE.command.length + 1) + composer.editText.setText("${Command.EMOTE.command} ") + composer.editText.setSelection(Command.EMOTE.command.length + 1) } else { val roomMember = timelineViewModel.getMember(userId) val displayName = sanitizeDisplayName(roomMember?.displayName ?: userId) @@ -737,7 +759,7 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A requireContext(), MatrixItem.UserItem(userId, displayName, roomMember?.avatarUrl) ) - .also { it.bind(views.composerLayout.views.composerEditText) }, + .also { it.bind(composer.editText) }, 0, displayName.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE @@ -747,11 +769,11 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A if (startToCompose) { if (displayName.startsWith("/")) { // Ensure displayName will not be interpreted as a Slash command - views.composerLayout.views.composerEditText.append("\\") + composer.editText.append("\\") } - views.composerLayout.views.composerEditText.append(pill) + composer.editText.append(pill) } else { - views.composerLayout.views.composerEditText.text?.insert(views.composerLayout.views.composerEditText.selectionStart, pill) + composer.editText.text?.insert(composer.editText.selectionStart, pill) } } focusComposerAndShowKeyboard() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerView.kt index 1935c9460b..09357191b4 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerView.kt @@ -1,11 +1,11 @@ /* - * Copyright 2019 New Vector Ltd + * 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 + * 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, @@ -16,137 +16,34 @@ package im.vector.app.features.home.room.detail.composer -import android.content.Context -import android.net.Uri import android.text.Editable -import android.util.AttributeSet -import android.view.ViewGroup -import androidx.constraintlayout.widget.ConstraintLayout -import androidx.constraintlayout.widget.ConstraintSet -import androidx.core.text.toSpannable -import androidx.transition.ChangeBounds -import androidx.transition.Fade -import androidx.transition.Transition -import androidx.transition.TransitionManager -import androidx.transition.TransitionSet -import im.vector.app.R -import im.vector.app.core.extensions.setTextIfDifferent -import im.vector.app.databinding.ComposerLayoutBinding +import android.widget.EditText +import android.widget.ImageButton +import android.widget.ImageView +import android.widget.TextView -/** - * Encapsulate the timeline composer UX. - */ -class MessageComposerView @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 -) : ConstraintLayout(context, attrs, defStyleAttr) { - - interface Callback : ComposerEditText.Callback { - fun onCloseRelatedMessage() - fun onSendMessage(text: CharSequence) - fun onAddAttachment() - fun onExpandOrCompactChange() - } - - val views: ComposerLayoutBinding - - var callback: Callback? = null - - private var currentConstraintSetId: Int = -1 - - private val animationDuration = 100L +interface MessageComposerView { val text: Editable? - get() = views.composerEditText.text + val formattedText: String? + val editText: EditText + val emojiButton: ImageButton? + val sendButton: ImageButton + val attachmentButton: ImageButton + val composerRelatedMessageTitle: TextView + val composerRelatedMessageContent: TextView + val composerRelatedMessageImage: ImageView + val composerRelatedMessageActionIcon: ImageView + val composerRelatedMessageAvatar: ImageView - init { - inflate(context, R.layout.composer_layout, this) - views = ComposerLayoutBinding.bind(this) + var callback: PlainTextComposerLayout.Callback? - collapse(false) + var isVisible: Boolean - views.composerEditText.callback = object : ComposerEditText.Callback { - override fun onRichContentSelected(contentUri: Uri): Boolean { - return callback?.onRichContentSelected(contentUri) ?: false - } + fun collapse(animate: Boolean = true, transitionComplete: (() -> Unit)? = null) + fun expand(animate: Boolean = true, transitionComplete: (() -> Unit)? = null) + fun setTextIfDifferent(text: CharSequence?): Boolean + fun replaceFormattedContent(text: CharSequence) - override fun onTextChanged(text: CharSequence) { - callback?.onTextChanged(text) - } - } - views.composerRelatedMessageCloseButton.setOnClickListener { - collapse() - callback?.onCloseRelatedMessage() - } - - views.sendButton.setOnClickListener { - val textMessage = text?.toSpannable() ?: "" - callback?.onSendMessage(textMessage) - } - - views.attachmentButton.setOnClickListener { - callback?.onAddAttachment() - } - } - - fun collapse(animate: Boolean = true, transitionComplete: (() -> Unit)? = null) { - if (currentConstraintSetId == R.layout.composer_layout_constraint_set_compact) { - // ignore we good - return - } - currentConstraintSetId = R.layout.composer_layout_constraint_set_compact - applyNewConstraintSet(animate, transitionComplete) - callback?.onExpandOrCompactChange() - } - - fun expand(animate: Boolean = true, transitionComplete: (() -> Unit)? = null) { - if (currentConstraintSetId == R.layout.composer_layout_constraint_set_expanded) { - // ignore we good - return - } - currentConstraintSetId = R.layout.composer_layout_constraint_set_expanded - applyNewConstraintSet(animate, transitionComplete) - callback?.onExpandOrCompactChange() - } - - fun setTextIfDifferent(text: CharSequence?): Boolean { - return views.composerEditText.setTextIfDifferent(text) - } - - private fun applyNewConstraintSet(animate: Boolean, transitionComplete: (() -> Unit)?) { - // val wasSendButtonInvisible = views.sendButton.isInvisible - if (animate) { - configureAndBeginTransition(transitionComplete) - } - ConstraintSet().also { - it.clone(context, currentConstraintSetId) - it.applyTo(this) - } - // Might be updated by view state just after, but avoid blinks - // views.sendButton.isInvisible = wasSendButtonInvisible - } - - private fun configureAndBeginTransition(transitionComplete: (() -> Unit)? = null) { - val transition = TransitionSet().apply { - ordering = TransitionSet.ORDERING_SEQUENTIAL - addTransition(ChangeBounds()) - addTransition(Fade(Fade.IN)) - duration = animationDuration - addListener(object : Transition.TransitionListener { - override fun onTransitionEnd(transition: Transition) { - transitionComplete?.invoke() - } - - override fun onTransitionResume(transition: Transition) {} - - override fun onTransitionPause(transition: Transition) {} - - override fun onTransitionCancel(transition: Transition) {} - - override fun onTransitionStart(transition: Transition) {} - }) - } - TransitionManager.beginDelayedTransition((parent as? ViewGroup ?: this), transition) - } + fun setInvisible(isInvisible: Boolean) } 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 c83f818ac8..b877c2979b 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 @@ -59,6 +59,7 @@ import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.RoomAvatarContent import org.matrix.android.sdk.api.session.room.model.RoomEncryptionAlgorithm import org.matrix.android.sdk.api.session.room.model.RoomMemberContent +import org.matrix.android.sdk.api.session.room.model.message.MessageContentWithFormattedBody import org.matrix.android.sdk.api.session.room.model.message.MessageType import org.matrix.android.sdk.api.session.room.model.relation.shouldRenderInThread import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper @@ -201,6 +202,7 @@ class MessageComposerViewModel @AssistedInject constructor( is SendMode.Regular -> { when (val parsedCommand = commandParser.parseSlashCommand( textMessage = action.text, + formattedMessage = action.formattedText, isInThreadTimeline = state.isInThreadTimeline() )) { is ParsedCommand.ErrorNotACommand -> { @@ -209,10 +211,15 @@ class MessageComposerViewModel @AssistedInject constructor( room.relationService().replyInThread( rootThreadEventId = state.rootThreadEventId, replyInThreadText = action.text, + formattedText = action.formattedText, autoMarkdown = action.autoMarkdown ) } else { - room.sendService().sendTextMessage(action.text, autoMarkdown = action.autoMarkdown) + if (action.formattedText != null) { + room.sendService().sendFormattedTextMessage(action.text.toString(), action.formattedText) + } else { + room.sendService().sendTextMessage(action.text, autoMarkdown = action.autoMarkdown) + } } _viewEvents.post(MessageComposerViewEvents.MessageSent) @@ -244,6 +251,24 @@ class MessageComposerViewModel @AssistedInject constructor( _viewEvents.post(MessageComposerViewEvents.MessageSent) popDraft() } + is ParsedCommand.SendFormattedText -> { + // Send the text message to the room, without markdown + if (state.rootThreadEventId != null) { + room.relationService().replyInThread( + rootThreadEventId = state.rootThreadEventId, + replyInThreadText = parsedCommand.message, + formattedText = parsedCommand.formattedMessage, + autoMarkdown = false + ) + } else { + room.sendService().sendFormattedTextMessage( + text = parsedCommand.message.toString(), + formattedText = parsedCommand.formattedMessage + ) + } + _viewEvents.post(MessageComposerViewEvents.MessageSent) + popDraft() + } is ParsedCommand.ChangeRoomName -> { handleChangeRoomNameSlashCommand(parsedCommand) } @@ -510,16 +535,24 @@ class MessageComposerViewModel @AssistedInject constructor( if (inReplyTo != null) { // TODO check if same content? room.getTimelineEvent(inReplyTo)?.let { - room.relationService().editReply(state.sendMode.timelineEvent, it, action.text.toString()) + room.relationService().editReply(state.sendMode.timelineEvent, it, action.text.toString(), action.formattedText) } } else { val messageContent = state.sendMode.timelineEvent.getVectorLastMessageContent() - val existingBody = messageContent?.body ?: "" - if (existingBody != action.text) { + val existingBody: String + val needsEdit = if (messageContent is MessageContentWithFormattedBody) { + existingBody = messageContent.formattedBody ?: "" + existingBody != action.formattedText + } else { + existingBody = messageContent?.body ?: "" + existingBody != action.text + } + if (needsEdit) { room.relationService().editTextMessage( state.sendMode.timelineEvent, messageContent?.msgType ?: MessageType.MSGTYPE_TEXT, action.text, + (messageContent as? MessageContentWithFormattedBody)?.formattedBody, action.autoMarkdown ) } else { @@ -533,6 +566,7 @@ class MessageComposerViewModel @AssistedInject constructor( room.sendService().sendQuotedTextMessage( quotedEvent = state.sendMode.timelineEvent, text = action.text.toString(), + formattedText = action.formattedText, autoMarkdown = action.autoMarkdown, rootThreadEventId = state.rootThreadEventId ) @@ -549,11 +583,13 @@ class MessageComposerViewModel @AssistedInject constructor( rootThreadEventId = it, replyInThreadText = action.text.toString(), autoMarkdown = action.autoMarkdown, + formattedText = action.formattedText, eventReplied = timelineEvent ) } ?: room.relationService().replyToMessage( eventReplied = timelineEvent, replyText = action.text.toString(), + replyFormattedText = action.formattedText, autoMarkdown = action.autoMarkdown, showInThread = showInThread, rootThreadEventId = rootThreadEventId diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/PlainTextComposerLayout.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/PlainTextComposerLayout.kt new file mode 100644 index 0000000000..acb5a1b42a --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/PlainTextComposerLayout.kt @@ -0,0 +1,185 @@ +/* + * Copyright 2019 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.composer + +import android.content.Context +import android.net.Uri +import android.text.Editable +import android.util.AttributeSet +import android.view.ViewGroup +import android.widget.EditText +import android.widget.ImageButton +import android.widget.ImageView +import android.widget.TextView +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.constraintlayout.widget.ConstraintSet +import androidx.core.text.toSpannable +import androidx.core.view.isInvisible +import androidx.core.view.isVisible +import androidx.transition.ChangeBounds +import androidx.transition.Fade +import androidx.transition.Transition +import androidx.transition.TransitionManager +import androidx.transition.TransitionSet +import im.vector.app.R +import im.vector.app.core.animations.SimpleTransitionListener +import im.vector.app.core.extensions.setTextIfDifferent +import im.vector.app.databinding.ComposerLayoutBinding + +/** + * Encapsulate the timeline composer UX. + */ +class PlainTextComposerLayout @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : ConstraintLayout(context, attrs, defStyleAttr), MessageComposerView { + + interface Callback : ComposerEditText.Callback { + fun onCloseRelatedMessage() + fun onSendMessage(text: CharSequence) + fun onAddAttachment() + fun onExpandOrCompactChange() + } + + private val views: ComposerLayoutBinding + + override var callback: Callback? = null + + private var currentConstraintSetId: Int = -1 + + private val animationDuration = 100L + + override val text: Editable? + get() = views.composerEditText.text + + override val formattedText: String? = null + + override val editText: EditText + get() = views.composerEditText + + override val emojiButton: ImageButton? + get() = views.composerEmojiButton + + override val sendButton: ImageButton + get() = views.sendButton + + override fun setInvisible(isInvisible: Boolean) { + this.isInvisible = isInvisible + } + override val attachmentButton: ImageButton + get() = views.attachmentButton + override val composerRelatedMessageActionIcon: ImageView + get() = views.composerRelatedMessageActionIcon + override val composerRelatedMessageAvatar: ImageView + get() = views.composerRelatedMessageAvatar + override val composerRelatedMessageContent: TextView + get() = views.composerRelatedMessageContent + override val composerRelatedMessageImage: ImageView + get() = views.composerRelatedMessageImage + override val composerRelatedMessageTitle: TextView + get() = views.composerRelatedMessageTitle + override var isVisible: Boolean + get() = views.root.isVisible + set(value) { views.root.isVisible = value } + + init { + inflate(context, R.layout.composer_layout, this) + views = ComposerLayoutBinding.bind(this) + + collapse(false) + + views.composerEditText.callback = object : ComposerEditText.Callback { + override fun onRichContentSelected(contentUri: Uri): Boolean { + return callback?.onRichContentSelected(contentUri) ?: false + } + + override fun onTextChanged(text: CharSequence) { + callback?.onTextChanged(text) + } + } + views.composerRelatedMessageCloseButton.setOnClickListener { + collapse() + callback?.onCloseRelatedMessage() + } + + views.sendButton.setOnClickListener { + val textMessage = text?.toSpannable() ?: "" + callback?.onSendMessage(textMessage) + } + + views.attachmentButton.setOnClickListener { + callback?.onAddAttachment() + } + } + + override fun replaceFormattedContent(text: CharSequence) { + setTextIfDifferent(text) + } + + override fun collapse(animate: Boolean, transitionComplete: (() -> Unit)?) { + if (currentConstraintSetId == R.layout.composer_layout_constraint_set_compact) { + // ignore we good + return + } + currentConstraintSetId = R.layout.composer_layout_constraint_set_compact + applyNewConstraintSet(animate, transitionComplete) + callback?.onExpandOrCompactChange() + } + + override fun expand(animate: Boolean, transitionComplete: (() -> Unit)?) { + if (currentConstraintSetId == R.layout.composer_layout_constraint_set_expanded) { + // ignore we good + return + } + currentConstraintSetId = R.layout.composer_layout_constraint_set_expanded + applyNewConstraintSet(animate, transitionComplete) + callback?.onExpandOrCompactChange() + } + + override fun setTextIfDifferent(text: CharSequence?): Boolean { + return views.composerEditText.setTextIfDifferent(text) + } + + private fun applyNewConstraintSet(animate: Boolean, transitionComplete: (() -> Unit)?) { + // val wasSendButtonInvisible = views.sendButton.isInvisible + if (animate) { + configureAndBeginTransition(transitionComplete) + } + ConstraintSet().also { + it.clone(context, currentConstraintSetId) + it.applyTo(this) + } + // Might be updated by view state just after, but avoid blinks + // views.sendButton.isInvisible = wasSendButtonInvisible + } + + private fun configureAndBeginTransition(transitionComplete: (() -> Unit)? = null) { + val transition = TransitionSet().apply { + ordering = TransitionSet.ORDERING_SEQUENTIAL + addTransition(ChangeBounds()) + addTransition(Fade(Fade.IN)) + duration = animationDuration + addListener(object : SimpleTransitionListener() { + override fun onTransitionEnd(transition: Transition) { + transitionComplete?.invoke() + } + }) + } + TransitionManager.beginDelayedTransition((parent as? ViewGroup ?: this), transition) + } +} 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 new file mode 100644 index 0000000000..76bdcfc9a8 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/RichTextComposerLayout.kt @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home.room.detail.composer + +import android.content.Context +import android.text.Editable +import android.text.TextWatcher +import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.ViewGroup +import android.widget.EditText +import android.widget.ImageButton +import android.widget.ImageView +import android.widget.TextView +import androidx.annotation.DrawableRes +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.constraintlayout.widget.ConstraintSet +import androidx.core.text.toSpannable +import androidx.core.view.isInvisible +import androidx.core.view.isVisible +import androidx.transition.ChangeBounds +import androidx.transition.Fade +import androidx.transition.Transition +import androidx.transition.TransitionManager +import androidx.transition.TransitionSet +import im.vector.app.R +import im.vector.app.core.animations.SimpleTransitionListener +import im.vector.app.core.extensions.setTextIfDifferent +import im.vector.app.databinding.ComposerRichTextLayoutBinding +import im.vector.app.databinding.ViewRichTextMenuButtonBinding +import io.element.android.wysiwyg.InlineFormat + +class RichTextComposerLayout @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : ConstraintLayout(context, attrs, defStyleAttr), MessageComposerView { + + private val views: ComposerRichTextLayoutBinding + + override var callback: PlainTextComposerLayout.Callback? = null + + private var currentConstraintSetId: Int = -1 + + private val animationDuration = 100L + + override val text: Editable? + get() = views.composerEditText.text + override val formattedText: String? + get() = views.composerEditText.getHtmlOutput() + override val editText: EditText + get() = views.composerEditText + override val emojiButton: ImageButton? + get() = null + override val sendButton: ImageButton + get() = views.sendButton + override val attachmentButton: ImageButton + get() = views.attachmentButton + override val composerRelatedMessageActionIcon: ImageView + get() = views.composerRelatedMessageActionIcon + override val composerRelatedMessageAvatar: ImageView + get() = views.composerRelatedMessageAvatar + override val composerRelatedMessageContent: TextView + get() = views.composerRelatedMessageContent + override val composerRelatedMessageImage: ImageView + get() = views.composerRelatedMessageImage + override val composerRelatedMessageTitle: TextView + get() = views.composerRelatedMessageTitle + override var isVisible: Boolean + get() = views.root.isVisible + set(value) { views.root.isVisible = value } + + init { + inflate(context, R.layout.composer_rich_text_layout, this) + views = ComposerRichTextLayoutBinding.bind(this) + + collapse(false) + + views.composerEditText.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} + override fun afterTextChanged(s: Editable) { + callback?.onTextChanged(s) + } + }) + + views.composerRelatedMessageCloseButton.setOnClickListener { + collapse() + callback?.onCloseRelatedMessage() + } + + views.sendButton.setOnClickListener { + val textMessage = text?.toSpannable() ?: "" + callback?.onSendMessage(textMessage) + } + + views.attachmentButton.setOnClickListener { + callback?.onAddAttachment() + } + + setupRichTextMenu() + } + + private fun setupRichTextMenu() { + addRichTextMenuItem(R.drawable.ic_composer_bold, "Bold") { + views.composerEditText.toggleInlineFormat(InlineFormat.Bold) + } + addRichTextMenuItem(R.drawable.ic_composer_italic, "Italic") { + views.composerEditText.toggleInlineFormat(InlineFormat.Italic) + } + addRichTextMenuItem(R.drawable.ic_composer_underlined, "Underline") { + views.composerEditText.toggleInlineFormat(InlineFormat.Underline) + } + addRichTextMenuItem(R.drawable.ic_composer_strikethrough, "Strikethrough") { + views.composerEditText.toggleInlineFormat(InlineFormat.StrikeThrough) + } + } + + private fun addRichTextMenuItem(@DrawableRes iconId: Int, description: String, action: () -> Unit) { + val inflater = LayoutInflater.from(context) + val button = ViewRichTextMenuButtonBinding.inflate(inflater, views.richTextMenu, true) + with(button.root) { + contentDescription = description + setImageResource(iconId) + setOnClickListener { + action() + } + } + } + + override fun replaceFormattedContent(text: CharSequence) { + views.composerEditText.setHtml(text.toString()) + } + + override fun collapse(animate: Boolean, transitionComplete: (() -> Unit)?) { + if (currentConstraintSetId == R.layout.composer_rich_text_layout_constraint_set_compact) { + // ignore we good + return + } + currentConstraintSetId = R.layout.composer_rich_text_layout_constraint_set_compact + applyNewConstraintSet(animate, transitionComplete) + } + + override fun expand(animate: Boolean, transitionComplete: (() -> Unit)?) { + if (currentConstraintSetId == R.layout.composer_rich_text_layout_constraint_set_expanded) { + // ignore we good + return + } + currentConstraintSetId = R.layout.composer_rich_text_layout_constraint_set_expanded + applyNewConstraintSet(animate, transitionComplete) + } + + override fun setTextIfDifferent(text: CharSequence?): Boolean { + return views.composerEditText.setTextIfDifferent(text) + } + + private fun applyNewConstraintSet(animate: Boolean, transitionComplete: (() -> Unit)?) { + // val wasSendButtonInvisible = views.sendButton.isInvisible + if (animate) { + configureAndBeginTransition(transitionComplete) + } + ConstraintSet().also { + it.clone(context, currentConstraintSetId) + it.applyTo(this) + } + // Might be updated by view state just after, but avoid blinks + // views.sendButton.isInvisible = wasSendButtonInvisible + } + + private fun configureAndBeginTransition(transitionComplete: (() -> Unit)? = null) { + val transition = TransitionSet().apply { + ordering = TransitionSet.ORDERING_SEQUENTIAL + addTransition(ChangeBounds()) + addTransition(Fade(Fade.IN)) + duration = animationDuration + addListener(object : SimpleTransitionListener() { + override fun onTransitionEnd(transition: Transition) { + transitionComplete?.invoke() + } + }) + } + TransitionManager.beginDelayedTransition((parent as? ViewGroup ?: this), transition) + } + + override fun setInvisible(isInvisible: Boolean) { + this.isInvisible = isInvisible + } +} 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 4a4f025688..25764f3654 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 @@ -37,6 +37,9 @@ import im.vector.app.features.home.room.detail.TimelineViewModel import im.vector.app.features.home.room.detail.composer.MessageComposerAction import im.vector.app.features.home.room.detail.composer.MessageComposerViewEvents import im.vector.app.features.home.room.detail.composer.MessageComposerViewModel +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 javax.inject.Inject @@ -70,6 +73,15 @@ class VoiceRecorderFragment : VectorBaseFragment<FragmentVoiceRecorderBinding>() else -> Unit } } + + messageComposerViewModel.onEach(MessageComposerViewState::sendMode, MessageComposerViewState::canSendMessage) { mode, canSend -> + if (!canSend.boolean()) { + return@onEach + } + if (mode is SendMode.Voice) { + views.voiceMessageRecorderView.isVisible = true + } + } } override fun onResume() { 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 b7812b9ebb..1cbb8509df 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 @@ -71,6 +71,7 @@ class VectorPreferences @Inject constructor( const val SETTINGS_LABS_PREFERENCE_KEY = "SETTINGS_LABS_PREFERENCE_KEY" const val SETTINGS_LABS_NEW_APP_LAYOUT_KEY = "SETTINGS_LABS_NEW_APP_LAYOUT_KEY" const val SETTINGS_LABS_DEFERRED_DM_KEY = "SETTINGS_LABS_DEFERRED_DM_KEY" + const val SETTINGS_LABS_RICH_TEXT_EDITOR_KEY = "SETTINGS_LABS_RICH_TEXT_EDITOR_KEY" const val SETTINGS_CRYPTOGRAPHY_PREFERENCE_KEY = "SETTINGS_CRYPTOGRAPHY_PREFERENCE_KEY" const val SETTINGS_CRYPTOGRAPHY_DIVIDER_PREFERENCE_KEY = "SETTINGS_CRYPTOGRAPHY_DIVIDER_PREFERENCE_KEY" const val SETTINGS_CRYPTOGRAPHY_MANAGE_PREFERENCE_KEY = "SETTINGS_CRYPTOGRAPHY_MANAGE_PREFERENCE_KEY" @@ -1182,4 +1183,8 @@ class VectorPreferences @Inject constructor( fun showLiveSenderInfo(): Boolean { return defaultPrefs.getBoolean(SETTINGS_TIMELINE_SHOW_LIVE_SENDER_INFO, getDefault(R.bool.settings_timeline_show_live_sender_info_default)) } + + fun isRichTextEditorEnabled(): Boolean { + return defaultPrefs.getBoolean(SETTINGS_LABS_RICH_TEXT_EDITOR_KEY, getDefault(R.bool.settings_labs_rich_text_editor_default)) + } } diff --git a/vector/src/main/res/drawable/ic_composer_bold.xml b/vector/src/main/res/drawable/ic_composer_bold.xml new file mode 100644 index 0000000000..3d9a10d16b --- /dev/null +++ b/vector/src/main/res/drawable/ic_composer_bold.xml @@ -0,0 +1,10 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="44dp" + android:height="44dp" + android:viewportWidth="44" + android:viewportHeight="44"> + <path + android:pathData="M16,14.5C16,13.672 16.672,13 17.5,13H22.288C25.139,13 27.25,15.466 27.25,18.25C27.25,19.38 26.902,20.458 26.298,21.34C27.765,22.268 28.75,23.882 28.75,25.75C28.75,28.689 26.311,31 23.393,31H17.5C16.672,31 16,30.328 16,29.5V14.5ZM19,16V20.5H22.288C23.261,20.5 24.25,19.608 24.25,18.25C24.25,16.892 23.261,16 22.288,16H19ZM19,23.5V28H23.393C24.735,28 25.75,26.953 25.75,25.75C25.75,24.547 24.735,23.5 23.393,23.5H19Z" + android:fillColor="#8D97A5" + android:fillType="evenOdd"/> +</vector> diff --git a/vector/src/main/res/drawable/ic_composer_italic.xml b/vector/src/main/res/drawable/ic_composer_italic.xml new file mode 100644 index 0000000000..faa4f89cd4 --- /dev/null +++ b/vector/src/main/res/drawable/ic_composer_italic.xml @@ -0,0 +1,10 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="44dp" + android:height="44dp" + android:viewportWidth="44" + android:viewportHeight="44"> + <path + android:pathData="M22.619,14.999L19.747,29.005H17.2C16.758,29.005 16.4,29.363 16.4,29.805C16.4,30.247 16.758,30.605 17.2,30.605H20.389C20.397,30.605 20.405,30.605 20.412,30.605H23.6C24.042,30.605 24.4,30.247 24.4,29.805C24.4,29.363 24.042,29.005 23.6,29.005H21.381L24.253,14.999H26.8C27.242,14.999 27.6,14.64 27.6,14.199C27.6,13.757 27.242,13.399 26.8,13.399H23.615C23.604,13.398 23.594,13.398 23.583,13.399H20.4C19.958,13.399 19.6,13.757 19.6,14.199C19.6,14.64 19.958,14.999 20.4,14.999H22.619Z" + android:fillColor="#8D97A5" + android:fillType="evenOdd"/> +</vector> diff --git a/vector/src/main/res/drawable/ic_composer_strikethrough.xml b/vector/src/main/res/drawable/ic_composer_strikethrough.xml new file mode 100644 index 0000000000..3970c95381 --- /dev/null +++ b/vector/src/main/res/drawable/ic_composer_strikethrough.xml @@ -0,0 +1,12 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="44dp" + android:height="44dp" + android:viewportWidth="44" + android:viewportHeight="44"> + <path + android:pathData="M24.897,17.154C24.235,15.821 22.876,15.21 21.374,15.372C19.05,15.622 18.44,17.423 18.722,18.592C19.032,19.872 20.046,20.37 21.839,20.826H29.92C30.517,20.826 31,21.351 31,22C31,22.648 30.517,23.174 29.92,23.174H14.08C13.483,23.174 13,22.648 13,22C13,21.351 13.483,20.826 14.08,20.826H17.355C17.041,20.377 16.791,19.839 16.633,19.189C16.003,16.581 17.554,13.424 21.16,13.036C23.285,12.807 25.615,13.661 26.798,16.038C27.081,16.608 26.886,17.32 26.361,17.629C25.836,17.937 25.181,17.725 24.897,17.154Z" + android:fillColor="#8D97A5"/> + <path + android:pathData="M25.427,25.13H27.67C27.888,26.306 27.721,27.56 27.05,28.632C26.114,30.125 24.37,31 21.985,31C18.076,31 16.279,28.584 15.912,26.986C15.768,26.357 16.12,25.72 16.698,25.563C17.277,25.406 17.863,25.788 18.008,26.417C18.119,26.902 19.002,28.652 21.985,28.652C23.907,28.652 24.854,27.965 25.264,27.31C25.642,26.707 25.708,25.909 25.427,25.13Z" + android:fillColor="#8D97A5"/> +</vector> diff --git a/vector/src/main/res/drawable/ic_composer_underlined.xml b/vector/src/main/res/drawable/ic_composer_underlined.xml new file mode 100644 index 0000000000..fe18d60185 --- /dev/null +++ b/vector/src/main/res/drawable/ic_composer_underlined.xml @@ -0,0 +1,13 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="44dp" + android:height="44dp" + android:viewportWidth="44" + android:viewportHeight="44"> + <group> + <clip-path + android:pathData="M10,10h24v24h-24z"/> + <path + android:pathData="M22.79,26.95C25.82,26.56 28,23.84 28,20.79V14.25C28,13.56 27.44,13 26.75,13C26.06,13 25.5,13.56 25.5,14.25V20.9C25.5,22.57 24.37,24.09 22.73,24.42C20.48,24.89 18.5,23.17 18.5,21V14.25C18.5,13.56 17.94,13 17.25,13C16.56,13 16,13.56 16,14.25V21C16,24.57 19.13,27.42 22.79,26.95ZM15,30C15,30.55 15.45,31 16,31H28C28.55,31 29,30.55 29,30C29,29.45 28.55,29 28,29H16C15.45,29 15,29.45 15,30Z" + android:fillColor="#8D97A5"/> + </group> +</vector> diff --git a/vector/src/main/res/layout/composer_rich_text_layout.xml b/vector/src/main/res/layout/composer_rich_text_layout.xml new file mode 100644 index 0000000000..3130061c10 --- /dev/null +++ b/vector/src/main/res/layout/composer_rich_text_layout.xml @@ -0,0 +1,156 @@ +<?xml version="1.0" encoding="utf-8"?> +<merge xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + tools:constraintSet="@layout/composer_rich_text_layout_constraint_set_compact" + tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout"> + + <!-- ======================== + /!\ Constraints for this layout are defined in external layout files that are used as constraint set for animation. + /!\ These 3 files must be modified to stay coherent! + ======================== --> + <View + android:id="@+id/related_message_background" + android:layout_width="0dp" + android:layout_height="0dp" + android:background="?colorSurface" + tools:ignore="MissingConstraints" /> + + <View + android:id="@+id/related_message_background_top_separator" + android:layout_width="0dp" + android:layout_height="0dp" + android:background="?vctr_list_separator" + tools:ignore="MissingConstraints" /> + + <ImageView + android:id="@+id/composerRelatedMessageAvatar" + android:layout_width="0dp" + android:layout_height="0dp" + android:importantForAccessibility="no" + tools:ignore="MissingConstraints" /> + + <TextView + android:id="@+id/composerRelatedMessageTitle" + android:layout_width="0dp" + android:layout_height="0dp" + android:textStyle="bold" + tools:ignore="MissingConstraints" + tools:text="@tools:sample/first_names" + tools:visibility="gone" /> + + <TextView + android:id="@+id/composerRelatedMessageContent" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:ellipsize="end" + android:maxLines="3" + android:textColor="?vctr_message_text_color" + tools:ignore="MissingConstraints" + tools:text="@tools:sample/lorem" + tools:visibility="gone" /> + + <ImageView + android:id="@+id/composerRelatedMessageActionIcon" + android:layout_width="0dp" + android:layout_height="0dp" + android:importantForAccessibility="no" + app:tint="?vctr_content_primary" + tools:ignore="MissingConstraints,MissingPrefix" /> + + <ImageView + android:id="@+id/composerRelatedMessageImage" + android:layout_width="0dp" + android:layout_height="0dp" + android:importantForAccessibility="no" + tools:ignore="MissingPrefix" /> + + <ImageButton + android:id="@+id/composerRelatedMessageCloseButton" + android:layout_width="22dp" + android:layout_height="22dp" + android:background="?android:attr/selectableItemBackground" + android:contentDescription="@string/action_cancel" + android:src="@drawable/ic_close_round" + app:tint="?colorError" + tools:ignore="MissingConstraints,MissingPrefix" /> + + <androidx.constraintlayout.widget.Barrier + android:id="@+id/composer_preview_barrier" + android:layout_width="0dp" + android:layout_height="0dp" + app:barrierDirection="bottom" + app:barrierMargin="8dp" + app:constraint_referenced_ids="composerRelatedMessageContent,composerRelatedMessageActionIcon" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" /> + + <ImageButton + android:id="@+id/attachmentButton" + android:layout_width="0dp" + android:layout_height="0dp" + android:background="?android:attr/selectableItemBackground" + android:contentDescription="@string/option_send_files" + android:src="@drawable/ic_attachment" + tools:ignore="MissingConstraints" /> + + <FrameLayout + android:id="@+id/composerEditTextOuterBorder" + android:layout_width="0dp" + android:layout_height="0dp" + android:background="@drawable/bg_composer_edit_text" /> + + <io.element.android.wysiwyg.EditorEditText + android:id="@+id/composerEditText" + style="@style/Widget.Vector.EditText.Composer" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:nextFocusLeft="@id/composerEditText" + android:nextFocusUp="@id/composerEditText" + tools:hint="@string/room_message_placeholder" + tools:ignore="MissingConstraints" /> + + <ImageButton + android:id="@+id/sendButton" + android:layout_width="0dp" + android:layout_height="0dp" + android:background="@drawable/bg_send" + android:contentDescription="@string/action_send" + android:src="@drawable/ic_send" + tools:ignore="MissingConstraints" /> + + <HorizontalScrollView android:id="@+id/richTextMenuScrollView" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:scrollbars="none" + app:layout_constraintTop_toTopOf="@id/sendButton" + app:layout_constraintStart_toEndOf="@id/attachmentButton" + app:layout_constraintEnd_toStartOf="@id/sendButton" + app:layout_constraintBottom_toBottomOf="parent" + android:fillViewport="true"> + + <LinearLayout + android:id="@+id/richTextMenu" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal"> + + </LinearLayout> + + </HorizontalScrollView> + + <!-- + <ImageButton + android:id="@+id/voiceMessageMicButton" + android:layout_width="32dp" + android:layout_height="32dp" + android:layout_marginEnd="12dp" + android:layout_marginBottom="12dp" + android:background="?android:attr/selectableItemBackground" + android:contentDescription="@string/a11y_start_voice_message" + android:src="@drawable/ic_voice_mic" /> + --> + +</merge> diff --git a/vector/src/main/res/layout/composer_rich_text_layout_constraint_set_compact.xml b/vector/src/main/res/layout/composer_rich_text_layout_constraint_set_compact.xml new file mode 100644 index 0000000000..585ba2913e --- /dev/null +++ b/vector/src/main/res/layout/composer_rich_text_layout_constraint_set_compact.xml @@ -0,0 +1,200 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/composerLayout" + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent"> + + <View + android:id="@+id/related_message_background" + android:layout_width="0dp" + android:layout_height="0dp" + android:background="?colorSurface" + app:layout_constraintBottom_toTopOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + tools:layout_height="40dp" /> + + <View + android:id="@+id/related_message_background_top_separator" + android:layout_width="0dp" + android:layout_height="0dp" + android:background="?vctr_list_separator" + app:layout_constraintBottom_toTopOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" /> + + <ImageView + android:id="@+id/composerRelatedMessageAvatar" + android:layout_width="40dp" + android:layout_height="40dp" + android:importantForAccessibility="no" + android:visibility="invisible" + app:layout_constraintBottom_toTopOf="parent" + app:layout_constraintEnd_toStartOf="parent" /> + + <TextView + android:id="@+id/composerRelatedMessageTitle" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:textStyle="bold" + android:visibility="invisible" + app:layout_constraintBottom_toTopOf="@id/composerRelatedMessageContent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + tools:text="@tools:sample/first_names" /> + + <TextView + android:id="@+id/composerRelatedMessageContent" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:visibility="invisible" + app:layout_constraintBottom_toTopOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + tools:text="@tools:sample/lorem/random" /> + + <ImageView + android:id="@+id/composerRelatedMessageActionIcon" + android:layout_width="20dp" + android:layout_height="20dp" + android:layout_marginTop="8dp" + android:layout_marginBottom="38dp" + android:alpha="0" + android:importantForAccessibility="no" + app:layout_constraintEnd_toStartOf="parent" + app:layout_constraintTop_toBottomOf="parent" + app:tint="?vctr_content_primary" + tools:ignore="MissingConstraints,MissingPrefix" + tools:src="@drawable/ic_edit" /> + + <ImageView + android:id="@+id/composerRelatedMessageImage" + android:layout_width="0dp" + android:layout_height="0dp" + android:importantForAccessibility="no" + app:layout_constraintBottom_toTopOf="parent" + app:layout_constraintStart_toEndOf="parent" + tools:ignore="MissingPrefix" + tools:src="@tools:sample/backgrounds/scenic" /> + + <ImageButton + android:id="@+id/composerRelatedMessageCloseButton" + android:layout_width="22dp" + android:layout_height="22dp" + android:background="?android:attr/selectableItemBackground" + android:contentDescription="@string/action_cancel" + android:src="@drawable/ic_close_round" + android:visibility="invisible" + app:layout_constraintBottom_toTopOf="parent" + app:layout_constraintStart_toEndOf="parent" + app:tint="?colorError" + tools:ignore="MissingPrefix" + tools:visibility="visible" /> + + <androidx.constraintlayout.widget.Barrier + android:id="@+id/composer_preview_barrier" + android:layout_width="0dp" + android:layout_height="0dp" + app:barrierDirection="bottom" + app:barrierMargin="8dp" + app:constraint_referenced_ids="composerRelatedMessageContent,composerRelatedMessageActionIcon" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" /> + + <ImageButton + android:id="@+id/attachmentButton" + android:layout_width="@dimen/composer_attachment_size" + android:layout_height="@dimen/composer_attachment_size" + android:layout_margin="@dimen/composer_attachment_margin" + android:background="?android:attr/selectableItemBackground" + android:contentDescription="@string/option_send_files" + android:src="@drawable/ic_attachment" + app:layout_constraintBottom_toBottomOf="@id/sendButton" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="@id/sendButton" + app:layout_goneMarginBottom="57dp" + tools:ignore="MissingPrefix" /> + + <FrameLayout + android:id="@+id/composerEditTextOuterBorder" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_marginTop="8dp" + android:layout_marginBottom="8dp" + app:layout_constraintBottom_toBottomOf="@id/composerEditText" + app:layout_constraintStart_toStartOf="@id/composerEditText" + app:layout_constraintEnd_toEndOf="@id/composerEditText" + app:layout_constraintTop_toTopOf="@id/composerEditText" + app:layout_goneMarginEnd="12dp" /> + + <io.element.android.wysiwyg.EditorEditText + android:id="@+id/composerEditText" + style="@style/Widget.Vector.EditText.Composer" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:hint="@string/room_message_placeholder" + android:nextFocusLeft="@id/composerEditText" + android:nextFocusUp="@id/composerEditText" + android:layout_marginHorizontal="10dp" + app:layout_constraintVertical_bias="0" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + tools:text="@tools:sample/lorem/random" /> + + <ImageButton + android:id="@+id/sendButton" + android:layout_width="56dp" + android:layout_height="@dimen/composer_min_height" + android:layout_marginEnd="2dp" + android:background="@drawable/bg_send" + android:contentDescription="@string/action_send" + android:scaleType="center" + android:src="@drawable/ic_send" + android:visibility="invisible" + app:layout_constraintTop_toBottomOf="@id/composerEditText" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + tools:ignore="MissingPrefix" + tools:visibility="visible" /> + + <HorizontalScrollView android:id="@+id/richTextMenuScrollView" + android:layout_width="0dp" + android:layout_height="wrap_content" + app:layout_constraintTop_toTopOf="@id/sendButton" + app:layout_constraintStart_toEndOf="@id/attachmentButton" + app:layout_constraintEnd_toStartOf="@id/sendButton" + app:layout_constraintBottom_toBottomOf="parent" + android:fillViewport="true"> + + <LinearLayout + android:id="@+id/richTextMenu" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal"> + + </LinearLayout> + + </HorizontalScrollView> + + <!-- + <ImageButton + android:id="@+id/voiceMessageMicButton" + android:layout_width="32dp" + android:layout_height="32dp" + android:layout_marginEnd="12dp" + android:layout_marginBottom="12dp" + android:background="?android:attr/selectableItemBackground" + android:contentDescription="@string/a11y_start_voice_message" + android:src="@drawable/ic_voice_mic" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" /> + --> + +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/vector/src/main/res/layout/composer_rich_text_layout_constraint_set_expanded.xml b/vector/src/main/res/layout/composer_rich_text_layout_constraint_set_expanded.xml new file mode 100644 index 0000000000..f810b12ed1 --- /dev/null +++ b/vector/src/main/res/layout/composer_rich_text_layout_constraint_set_expanded.xml @@ -0,0 +1,198 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/composerLayout" + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent"> + + <View + android:id="@+id/related_message_background" + android:layout_width="match_parent" + android:layout_height="0dp" + android:background="?colorSurface" + app:layout_constraintBottom_toBottomOf="@id/composer_preview_barrier" + app:layout_constraintTop_toTopOf="parent" /> + + <View + android:id="@+id/related_message_background_top_separator" + android:layout_width="0dp" + android:layout_height="1dp" + android:background="?vctr_list_separator" + app:layout_constraintEnd_toEndOf="@id/related_message_background" + app:layout_constraintStart_toStartOf="@id/related_message_background" + app:layout_constraintTop_toTopOf="@id/related_message_background" /> + + <ImageView + android:id="@+id/composerRelatedMessageAvatar" + android:layout_width="40dp" + android:layout_height="40dp" + android:layout_marginStart="8dp" + android:layout_marginTop="8dp" + android:layout_marginBottom="8dp" + android:importantForAccessibility="no" + app:layout_constraintBottom_toTopOf="@id/composerRelatedMessageActionIcon" + app:layout_constraintEnd_toStartOf="@id/composerRelatedMessageTitle" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="@id/composerRelatedMessageTitle" + tools:src="@sample/user_round_avatars" /> + + <TextView + android:id="@+id/composerRelatedMessageTitle" + style="@style/Widget.Vector.TextView.Body" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_margin="8dp" + android:textStyle="bold" + app:layout_constraintEnd_toStartOf="@id/composerRelatedMessageCloseButton" + app:layout_constraintStart_toEndOf="@id/composerRelatedMessageAvatar" + app:layout_constraintTop_toTopOf="parent" + tools:text="@tools:sample/first_names" /> + + <ImageView + android:id="@+id/composerRelatedMessageImage" + android:layout_width="100dp" + android:layout_height="66dp" + android:layout_marginTop="6dp" + android:importantForAccessibility="no" + android:scaleType="centerCrop" + android:visibility="gone" + app:layout_constraintStart_toStartOf="@id/composerRelatedMessageTitle" + app:layout_constraintTop_toBottomOf="@id/composerRelatedMessageTitle" + tools:ignore="MissingPrefix" + tools:src="@tools:sample/backgrounds/scenic" + tools:visibility="visible" /> + + <TextView + android:id="@+id/composerRelatedMessageContent" + style="@style/Widget.Vector.TextView.Body" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:ellipsize="end" + android:maxLines="2" + android:textColor="?vctr_message_text_color" + app:layout_constrainedHeight="true" + app:layout_constraintEnd_toEndOf="@id/composerRelatedMessageTitle" + app:layout_constraintStart_toStartOf="@id/composerRelatedMessageTitle" + app:layout_constraintTop_toBottomOf="@id/composerRelatedMessageImage" + tools:text="@tools:sample/lorem/random" /> + + <ImageView + android:id="@+id/composerRelatedMessageActionIcon" + android:layout_width="10dp" + android:layout_height="10dp" + android:layout_marginTop="6dp" + android:layout_marginBottom="38dp" + android:alpha="1" + android:importantForAccessibility="no" + android:visibility="visible" + app:layout_constraintEnd_toEndOf="@id/composerRelatedMessageAvatar" + app:layout_constraintStart_toStartOf="@id/composerRelatedMessageAvatar" + app:layout_constraintTop_toBottomOf="@id/composerRelatedMessageAvatar" + app:tint="?vctr_content_primary" + tools:ignore="MissingPrefix" + tools:src="@drawable/ic_edit" /> + + <ImageButton + android:id="@+id/composerRelatedMessageCloseButton" + android:layout_width="48dp" + android:layout_height="48dp" + android:background="?android:attr/selectableItemBackground" + android:contentDescription="@string/action_cancel" + android:src="@drawable/ic_close_round" + app:layout_constraintBottom_toBottomOf="@id/composer_preview_barrier" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:tint="?colorError" + tools:ignore="MissingPrefix" /> + + <androidx.constraintlayout.widget.Barrier + android:id="@+id/composer_preview_barrier" + android:layout_width="0dp" + android:layout_height="0dp" + app:barrierDirection="bottom" + app:barrierMargin="8dp" + app:constraint_referenced_ids="composerRelatedMessageContent,composerRelatedMessageActionIcon" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" /> + + <ImageButton + android:id="@+id/attachmentButton" + android:layout_width="@dimen/composer_attachment_size" + android:layout_height="@dimen/composer_attachment_size" + android:layout_margin="@dimen/composer_attachment_margin" + android:background="?android:attr/selectableItemBackground" + android:contentDescription="@string/option_send_files" + android:src="@drawable/ic_attachment" + app:layout_constraintBottom_toBottomOf="@id/sendButton" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="@id/sendButton" + tools:ignore="MissingPrefix" /> + + <FrameLayout + android:id="@+id/composerEditTextOuterBorder" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_marginStart="8dp" + android:layout_marginEnd="8dp" + android:layout_marginTop="8dp" + android:layout_marginBottom="8dp" + app:layout_constraintBottom_toTopOf="@id/sendButton" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/composer_preview_barrier" + app:layout_goneMarginEnd="12dp" /> + + <io.element.android.wysiwyg.EditorEditText + android:id="@+id/composerEditText" + style="@style/Widget.Vector.EditText.Composer" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:nextFocusLeft="@id/composerEditText" + android:nextFocusUp="@id/composerEditText" + app:layout_constraintBottom_toTopOf="@id/sendButton" + app:layout_constraintEnd_toEndOf="@id/composerEditTextOuterBorder" + app:layout_constraintStart_toStartOf="@id/composerEditTextOuterBorder" + app:layout_constraintTop_toBottomOf="@id/composer_preview_barrier" + tools:text="@tools:sample/lorem/random" /> + + <ImageButton + android:id="@+id/sendButton" + android:layout_width="56dp" + android:layout_height="@dimen/composer_min_height" + android:layout_marginEnd="2dp" + android:background="@drawable/bg_send" + android:contentDescription="@string/action_send" + android:scaleType="center" + android:src="@drawable/ic_send" + android:visibility="invisible" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@id/composerEditText" + app:layout_constraintVertical_bias="1" + tools:ignore="MissingPrefix" + tools:visibility="visible" /> + + <HorizontalScrollView android:id="@+id/richTextMenuScrollView" + android:layout_width="0dp" + android:layout_height="wrap_content" + app:layout_constraintTop_toTopOf="@id/sendButton" + app:layout_constraintStart_toEndOf="@id/attachmentButton" + app:layout_constraintEnd_toStartOf="@id/sendButton" + app:layout_constraintBottom_toBottomOf="parent" + android:fillViewport="true"> + + <LinearLayout + android:id="@+id/richTextMenu" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal"> + + </LinearLayout> + + </HorizontalScrollView> + +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/vector/src/main/res/layout/fragment_composer.xml b/vector/src/main/res/layout/fragment_composer.xml index 0f79500da9..8703af7471 100644 --- a/vector/src/main/res/layout/fragment_composer.xml +++ b/vector/src/main/res/layout/fragment_composer.xml @@ -1,13 +1,29 @@ <?xml version="1.0" encoding="utf-8"?> -<im.vector.app.features.home.room.detail.composer.MessageComposerView +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" - android:id="@+id/composerLayout" android:layout_width="match_parent" - android:layout_height="wrap_content" - android:background="?android:colorBackground" - android:minHeight="56dp" - android:transitionName="composer" - android:visibility="gone" - tools:visibility="visible" /> + android:layout_height="wrap_content"> + + <im.vector.app.features.home.room.detail.composer.PlainTextComposerLayout + android:id="@+id/composerLayout" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="?android:colorBackground" + android:minHeight="56dp" + android:transitionName="composer" + android:visibility="gone" + tools:visibility="gone" /> + + <im.vector.app.features.home.room.detail.composer.RichTextComposerLayout + android:id="@+id/richTextComposerLayout" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="?android:colorBackground" + android:minHeight="56dp" + android:transitionName="composer" + android:visibility="gone" + tools:visibility="visible" /> + +</FrameLayout> diff --git a/vector/src/main/res/layout/view_rich_text_menu_button.xml b/vector/src/main/res/layout/view_rich_text_menu_button.xml new file mode 100644 index 0000000000..a63a01e7c2 --- /dev/null +++ b/vector/src/main/res/layout/view_rich_text_menu_button.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<ImageButton xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="48dp" + android:layout_height="48dp" + android:layout_marginHorizontal="2dp" + android:background="@android:color/transparent" + android:contentDescription="@string/app_name"> + <!-- The contentDescription attr is populated programmatically. This is just to fix lint issues. --> + +</ImageButton> diff --git a/vector/src/main/res/xml/vector_settings_labs.xml b/vector/src/main/res/xml/vector_settings_labs.xml index 9fac6d722a..a3420c5865 100644 --- a/vector/src/main/res/xml/vector_settings_labs.xml +++ b/vector/src/main/res/xml/vector_settings_labs.xml @@ -96,4 +96,11 @@ android:title="@string/labs_enable_deferred_dm_title" app:isPreferenceVisible="@bool/settings_labs_deferred_dm_visible" /> + <im.vector.app.core.preference.VectorSwitchPreference + android:defaultValue="@bool/settings_labs_rich_text_editor_default" + android:key="SETTINGS_LABS_RICH_TEXT_EDITOR_KEY" + android:summary="@string/labs_enable_rich_text_editor_summary" + android:title="@string/labs_enable_rich_text_editor_title" + app:isPreferenceVisible="@bool/settings_labs_rich_text_editor_visible" /> + </androidx.preference.PreferenceScreen> 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 c257377849..f502db85ca 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 @@ -71,7 +71,7 @@ class CommandParserTest { private fun test(message: String, expectedResult: ParsedCommand) { val commandParser = CommandParser() - val result = commandParser.parseSlashCommand(message, false) + val result = commandParser.parseSlashCommand(message, null, false) result shouldBeEqualTo expectedResult } }