From ab87937e5bff9edceed55bcfb225ad442dec5d05 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Wed, 20 Oct 2021 18:39:59 +0300 Subject: [PATCH 001/581] Threads init commit --- .../api/session/events/model/RelationType.kt | 3 + .../room/model/relation/RelationService.kt | 15 ++++ .../model/relation/threads/ThreadContent.kt | 28 ++++++ .../model/relation/threads/ThreadRelatesTo.kt | 31 +++++++ .../relation/threads/ThreadTextContent.kt | 28 ++++++ .../room/relation/DefaultRelationService.kt | 6 ++ .../room/send/LocalEchoEventFactory.kt | 12 +++ .../internal/session/room/send/TextContent.kt | 12 +++ vector/src/main/AndroidManifest.xml | 3 + .../im/vector/app/core/di/FragmentModule.kt | 6 ++ .../im/vector/app/core/di/ScreenComponent.kt | 4 + .../home/room/detail/RoomDetailFragment.kt | 12 +++ .../timeline/action/EventSharedAction.kt | 4 + .../action/MessageActionsViewModel.kt | 21 +++++ .../home/room/threads/RoomThreadsActivity.kt | 66 ++++++++++++++ .../detail/RoomThreadDetailActivity.kt | 64 ++++++++++++++ .../detail/RoomThreadDetailFragment.kt | 57 ++++++++++++ .../main/res/drawable/ic_reply_in_thread.xml | 24 +++++ .../layout/activity_room_thread_detail.xml | 88 +++++++++++++++++++ .../main/res/layout/activity_room_threads.xml | 88 +++++++++++++++++++ .../main/res/layout/fragment_room_detail.xml | 2 +- .../layout/fragment_room_thread_detail.xml | 33 +++++++ .../src/main/res/menu/menu_room_threads.xml | 9 ++ vector/src/main/res/values/strings.xml | 4 + 24 files changed, 619 insertions(+), 1 deletion(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/threads/ThreadContent.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/threads/ThreadRelatesTo.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/threads/ThreadTextContent.kt create mode 100644 vector/src/main/java/im/vector/app/features/home/room/threads/RoomThreadsActivity.kt create mode 100644 vector/src/main/java/im/vector/app/features/home/room/threads/detail/RoomThreadDetailActivity.kt create mode 100644 vector/src/main/java/im/vector/app/features/home/room/threads/detail/RoomThreadDetailFragment.kt create mode 100644 vector/src/main/res/drawable/ic_reply_in_thread.xml create mode 100644 vector/src/main/res/layout/activity_room_thread_detail.xml create mode 100644 vector/src/main/res/layout/activity_room_threads.xml create mode 100644 vector/src/main/res/layout/fragment_room_thread_detail.xml create mode 100644 vector/src/main/res/menu/menu_room_threads.xml diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/RelationType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/RelationType.kt index 7d827f871b2..653798c29cf 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/RelationType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/RelationType.kt @@ -28,6 +28,9 @@ object RelationType { /** Lets you define an event which references an existing event.*/ const val REFERENCE = "m.reference" + /** Lets you define an event which is a reply to an existing event.*/ + const val THREAD = "m.thread" + /** Lets you define an event which adds a response to an existing event.*/ const val RESPONSE = "org.matrix.response" } 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 59d84ef40fd..20e33fec8cd 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 @@ -44,6 +44,9 @@ import org.matrix.android.sdk.api.util.Optional * m.reference - lets you define an event which references an existing event. * When aggregated, currently doesn't do anything special, but in future could bundle chains of references (i.e. threads). * These are primarily intended for handling replies (and in future threads). + * + * m.thread - lets you define an event which is a thread reply to an existing event. + * When aggregated, returns the most thread event */ interface RelationService { @@ -123,4 +126,16 @@ interface RelationService { * @return the LiveData of EventAnnotationsSummary */ fun getEventAnnotationsSummaryLive(eventId: String): LiveData> + + /** + * Creates a thread reply for an existing timeline event + * The replyInThreadText can be a Spannable and contains special spans (MatrixItemSpan) that will be translated + * by the sdk into pills. + * @param eventToReplyInThread the event referenced by the thread reply + * @param replyInThreadText the reply text + * @param autoMarkdown If true, the SDK will generate a formatted HTML message from the body text if markdown syntax is present + */ + fun replyInThread(eventToReplyInThread: TimelineEvent, + replyInThreadText: CharSequence, + autoMarkdown: Boolean = false): Cancelable? } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/threads/ThreadContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/threads/ThreadContent.kt new file mode 100644 index 00000000000..9d0fd9508a4 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/threads/ThreadContent.kt @@ -0,0 +1,28 @@ +/* + * Copyright 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.api.session.room.model.relation.threads + +interface ThreadContent { + + companion object { + const val MSG_TYPE_JSON_KEY = "msgtype" + } + + val msgType: String + val body: String + val relatesTo: ThreadRelatesTo? +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/threads/ThreadRelatesTo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/threads/ThreadRelatesTo.kt new file mode 100644 index 00000000000..4a0d1e20545 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/threads/ThreadRelatesTo.kt @@ -0,0 +1,31 @@ +/* + * 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 org.matrix.android.sdk.api.session.room.model.relation.threads + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.events.model.RelationType +import org.matrix.android.sdk.api.session.room.model.relation.RelationContent +import org.matrix.android.sdk.api.session.room.model.relation.ReplyToContent + +@JsonClass(generateAdapter = true) +data class ThreadRelatesTo( + @Json(name = "rel_type") override val type: String? = RelationType.THREAD, + @Json(name = "event_id") override val eventId: String, + @Json(name = "m.in_reply_to") override val inReplyTo: ReplyToContent? = null, + @Json(name = "option") override val option: Int? = null +) : RelationContent diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/threads/ThreadTextContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/threads/ThreadTextContent.kt new file mode 100644 index 00000000000..9244b0bf7f6 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/threads/ThreadTextContent.kt @@ -0,0 +1,28 @@ +/* + * Copyright 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.api.session.room.model.relation.threads + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.room.model.message.MessageContent + +@JsonClass(generateAdapter = true) +data class ThreadTextContent( + @Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String, + @Json(name = "body") override val body: String, + @Json(name = "m.relates_to") override val relatesTo: ThreadRelatesTo? = null, +) : ThreadContent 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 07927b1412e..b3afc6ad469 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 @@ -38,6 +38,7 @@ import org.matrix.android.sdk.internal.database.model.TimelineEventEntity import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory +import org.matrix.android.sdk.internal.session.room.send.TextContent import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.configureWith @@ -158,6 +159,11 @@ internal class DefaultRelationService @AssistedInject constructor( } } + override fun replyInThread(eventToReplyInThread: TimelineEvent, replyInThreadText: CharSequence, autoMarkdown: Boolean): Cancelable? { + val event = eventFactory.createThreadTextEvent(eventToReplyInThread, TextContent(replyInThreadText.toString())) + return eventSenderProcessor.postEvent(event, cryptoSessionInfoProvider.isRoomEncrypted(roomId)) + } + /** * Saves the event in database as a local echo. * SendState is set to UNSENT and it's added to a the sendingTimelineEvents list of the room. 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 8dd0c593872..2e1a95feb5a 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 @@ -51,6 +51,8 @@ import org.matrix.android.sdk.api.session.room.model.relation.ReactionContent import org.matrix.android.sdk.api.session.room.model.relation.ReactionInfo import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent import org.matrix.android.sdk.api.session.room.model.relation.ReplyToContent +import org.matrix.android.sdk.api.session.room.model.relation.threads.ThreadTextContent +import org.matrix.android.sdk.api.session.room.model.relation.threads.ThreadRelatesTo 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.isReply @@ -58,6 +60,7 @@ import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.content.ThumbnailExtractor import org.matrix.android.sdk.internal.session.permalinks.PermalinkFactory import org.matrix.android.sdk.internal.session.room.send.pills.TextPillsUtils +import timber.log.Timber import javax.inject.Inject /** @@ -340,6 +343,15 @@ internal class LocalEchoEventFactory @Inject constructor( ) } + /** + * Creates a thread event related to the already existing event + */ + fun createThreadTextEvent(eventToReplyInThread: TimelineEvent, textContent: TextContent): Event = + createEvent( + eventToReplyInThread.roomId, + EventType.MESSAGE, + textContent.toThreadTextContent(eventToReplyInThread).toContent()) + private fun dummyOriginServerTs(): Long { return System.currentTimeMillis() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/TextContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/TextContent.kt index efc0b55abf8..c3f4f72834f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/TextContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/TextContent.kt @@ -16,9 +16,13 @@ package org.matrix.android.sdk.internal.session.room.send +import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.room.model.message.MessageFormat 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.threads.ThreadTextContent +import org.matrix.android.sdk.api.session.room.model.relation.threads.ThreadRelatesTo +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.util.ContentUtils.extractUsefulTextFromHtmlReply import org.matrix.android.sdk.api.util.ContentUtils.extractUsefulTextFromReply @@ -41,6 +45,14 @@ fun TextContent.toMessageTextContent(msgType: String = MessageType.MSGTYPE_TEXT) ) } +fun TextContent.toThreadTextContent(eventToReplyInThread: TimelineEvent, msgType: String = MessageType.MSGTYPE_TEXT): ThreadTextContent { + return ThreadTextContent( + msgType = msgType, + body = text, + relatesTo = ThreadRelatesTo(eventId = eventToReplyInThread.eventId) + ) +} + fun TextContent.removeInReplyFallbacks(): TextContent { return copy( text = extractUsefulTextFromReply(this.text), diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index 376e0e869a3..2b7b445ad5b 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -179,6 +179,9 @@ + + + diff --git a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt index 3bc8e30851c..4c80f4aa351 100644 --- a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt @@ -58,6 +58,7 @@ import im.vector.app.features.home.room.breadcrumbs.BreadcrumbsFragment import im.vector.app.features.home.room.detail.RoomDetailFragment import im.vector.app.features.home.room.detail.search.SearchFragment import im.vector.app.features.home.room.list.RoomListFragment +import im.vector.app.features.home.room.threads.detail.RoomThreadDetailFragment import im.vector.app.features.login.LoginCaptchaFragment import im.vector.app.features.login.LoginFragment import im.vector.app.features.login.LoginGenericTextInputFormFragment @@ -834,4 +835,9 @@ interface FragmentModule { @IntoMap @FragmentKey(SpaceLeaveAdvancedFragment::class) fun bindSpaceLeaveAdvancedFragment(fragment: SpaceLeaveAdvancedFragment): Fragment + + @Binds + @IntoMap + @FragmentKey(RoomThreadDetailFragment::class) + fun bindRoomThreadDetailFragment(fragment: RoomThreadDetailFragment): Fragment } diff --git a/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt b/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt index 76b511d2bd2..9a095a3c792 100644 --- a/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt +++ b/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt @@ -52,6 +52,8 @@ import im.vector.app.features.home.room.detail.widget.RoomWidgetsBottomSheet import im.vector.app.features.home.room.filtered.FilteredRoomsActivity import im.vector.app.features.home.room.list.RoomListModule import im.vector.app.features.home.room.list.actions.RoomListQuickActionsBottomSheet +import im.vector.app.features.home.room.threads.RoomThreadsActivity +import im.vector.app.features.home.room.threads.detail.RoomThreadDetailActivity import im.vector.app.features.invite.AutoAcceptInvites import im.vector.app.features.invite.InviteUsersToRoomActivity import im.vector.app.features.invite.VectorInviteView @@ -174,6 +176,8 @@ interface ScreenComponent { fun inject(activity: SpaceManageActivity) fun inject(activity: RoomJoinRuleActivity) fun inject(activity: SpaceLeaveAdvancedActivity) + fun inject(activity: RoomThreadsActivity) + fun inject(activity: RoomThreadDetailActivity) /* ========================================================================================== * BottomSheets diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index e9948e6cf4b..8380774a494 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -161,6 +161,8 @@ import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever import im.vector.app.features.home.room.detail.upgrade.MigrateRoomBottomSheet import im.vector.app.features.home.room.detail.views.RoomDetailLazyLoadedViews import im.vector.app.features.home.room.detail.widget.RoomWidgetsBottomSheet +import im.vector.app.features.home.room.threads.detail.RoomThreadDetailArgs +import im.vector.app.features.home.room.threads.detail.RoomThreadDetailActivity import im.vector.app.features.html.EventHtmlRenderer import im.vector.app.features.html.PillImageSpan import im.vector.app.features.html.PillsPostProcessor @@ -1957,6 +1959,16 @@ class RoomDetailFragment @Inject constructor( requireActivity().toast(R.string.error_voice_message_cannot_reply_or_edit) } } + is EventSharedAction.ReplyInThread -> { + if (!views.voiceMessageRecorderView.isActive()) { + context?.let { + val roomThreadDetailArgs = RoomThreadDetailArgs(roomDetailArgs.roomId,action.eventId) + startActivity(RoomThreadDetailActivity.newIntent(it, roomThreadDetailArgs)) + } + } else { + requireActivity().toast(R.string.error_voice_message_cannot_reply_or_edit) + } + } is EventSharedAction.CopyPermalink -> { val permalink = session.permalinkService().createPermalink(roomDetailArgs.roomId, action.eventId) copyToClipboard(requireContext(), permalink, false) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/EventSharedAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/EventSharedAction.kt index d9ee7f3ccf5..c57d8449740 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/EventSharedAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/EventSharedAction.kt @@ -48,6 +48,10 @@ sealed class EventSharedAction(@StringRes val titleRes: Int, data class Reply(val eventId: String) : EventSharedAction(R.string.reply, R.drawable.ic_reply) + data class ReplyInThread(val eventId: String) : + // TODO add translations + EventSharedAction(R.string.reply_in_thread, R.drawable.ic_reply_in_thread) + data class Share(val eventId: String, val messageContent: MessageContent) : EventSharedAction(R.string.share, R.drawable.ic_share) 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 b4fff6eb3d9..bdd51770588 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 @@ -326,6 +326,11 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted add(EventSharedAction.Reply(eventId)) } + // *** Testing Threads **** + if (canReplyInThread(timelineEvent, messageContent, actionPermissions)) { + add(EventSharedAction.ReplyInThread(eventId)) + } + if (canEdit(timelineEvent, session.myUserId, actionPermissions)) { add(EventSharedAction.Edit(eventId)) } @@ -412,6 +417,22 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted } } + private fun canReplyInThread(event: TimelineEvent, messageContent: MessageContent?, actionPermissions: ActionPermissions): Boolean { + // Only event of type EventType.MESSAGE are supported for the moment + if (event.root.getClearType() != EventType.MESSAGE) return false + if (!actionPermissions.canSendMessage) return false + return when (messageContent?.msgType) { + MessageType.MSGTYPE_TEXT, + MessageType.MSGTYPE_NOTICE, + MessageType.MSGTYPE_EMOTE, + MessageType.MSGTYPE_IMAGE, + MessageType.MSGTYPE_VIDEO, + MessageType.MSGTYPE_AUDIO, + MessageType.MSGTYPE_FILE -> true + else -> false + } + } + private fun canQuote(event: TimelineEvent, messageContent: MessageContent?, actionPermissions: ActionPermissions): Boolean { // Only event of type EventType.MESSAGE are supported for the moment if (event.root.getClearType() != EventType.MESSAGE) return false diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/RoomThreadsActivity.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/RoomThreadsActivity.kt new file mode 100644 index 00000000000..0ad1d02ffb3 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/RoomThreadsActivity.kt @@ -0,0 +1,66 @@ +/* + * Copyright 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.home.room.threads + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import androidx.appcompat.widget.SearchView +import im.vector.app.R +import im.vector.app.core.di.ScreenComponent +import im.vector.app.core.extensions.replaceFragment +import im.vector.app.core.platform.VectorBaseActivity +import im.vector.app.databinding.ActivityFilteredRoomsBinding +import im.vector.app.databinding.ActivityRoomThreadsBinding +import im.vector.app.features.home.RoomListDisplayMode +import im.vector.app.features.home.room.list.RoomListFragment +import im.vector.app.features.home.room.list.RoomListParams + +class RoomThreadsActivity : VectorBaseActivity() { + +// private val roomListFragment: RoomListFragment? +// get() { +// return supportFragmentManager.findFragmentByTag(FRAGMENT_TAG) as? RoomListFragment +// } + + override fun getBinding() = ActivityRoomThreadsBinding.inflate(layoutInflater) + + override fun getCoordinatorLayout() = views.coordinatorLayout + + override fun injectWith(injector: ScreenComponent) { + injector.inject(this) + } + + override fun getMenuRes() = R.menu.menu_room_threads + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + configureToolbar(views.roomThreadsToolbar) +// if (isFirstCreation()) { +// val params = RoomListParams(RoomListDisplayMode.FILTERED) +// replaceFragment(R.id.filteredRoomsFragmentContainer, RoomListFragment::class.java, params, FRAGMENT_TAG) +// } + } + + companion object { + private const val FRAGMENT_TAG = "RoomListFragment" + + fun newIntent(context: Context): Intent { + return Intent(context, RoomThreadsActivity::class.java) + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/detail/RoomThreadDetailActivity.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/detail/RoomThreadDetailActivity.kt new file mode 100644 index 00000000000..96b65d02726 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/detail/RoomThreadDetailActivity.kt @@ -0,0 +1,64 @@ +/* + * Copyright 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.home.room.threads.detail + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import im.vector.app.R +import im.vector.app.core.di.ScreenComponent +import im.vector.app.core.extensions.replaceFragment +import im.vector.app.core.platform.VectorBaseActivity +import im.vector.app.databinding.ActivityRoomThreadDetailBinding + +class RoomThreadDetailActivity : VectorBaseActivity() { + +// private val roomThreadDetailFragment: RoomThreadDetailFragment? +// get() { +// return supportFragmentManager.findFragmentByTag(FRAGMENT_TAG) as? RoomThreadDetailFragment +// } + + override fun getBinding() = ActivityRoomThreadDetailBinding.inflate(layoutInflater) + + override fun getCoordinatorLayout() = views.coordinatorLayout + + override fun injectWith(injector: ScreenComponent) { + injector.inject(this) + } + + override fun getMenuRes() = R.menu.menu_room_threads + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + configureToolbar(views.roomThreadDetailToolbar) + if (isFirstCreation()) { + val roomThreadDetailArgs: RoomThreadDetailArgs? = intent?.extras?.getParcelable(EXTRA_ROOM_THREAD_DETAIL_ARGS) + replaceFragment(R.id.roomThreadDetailFragmentContainer, RoomThreadDetailFragment::class.java, roomThreadDetailArgs, FRAGMENT_TAG) + } + } + + companion object { + private const val FRAGMENT_TAG = "RoomThreadDetailFragment" + const val EXTRA_ROOM_THREAD_DETAIL_ARGS = "EXTRA_ROOM_THREAD_DETAIL_ARGS" + + fun newIntent(context: Context, roomThreadDetailArgs: RoomThreadDetailArgs): Intent { + return Intent(context, RoomThreadDetailActivity::class.java).apply { + putExtra(EXTRA_ROOM_THREAD_DETAIL_ARGS, roomThreadDetailArgs) + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/detail/RoomThreadDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/detail/RoomThreadDetailFragment.kt new file mode 100644 index 00000000000..c50fdcd9ae2 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/detail/RoomThreadDetailFragment.kt @@ -0,0 +1,57 @@ +/* + * Copyright 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.home.room.threads.detail + +import android.annotation.SuppressLint +import android.os.Bundle +import android.os.Parcelable +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.airbnb.mvrx.args +import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.databinding.FragmentRoomThreadDetailBinding +import kotlinx.parcelize.Parcelize +import org.matrix.android.sdk.api.session.Session +import javax.inject.Inject + +@Parcelize +data class RoomThreadDetailArgs( + val roomId: String, + val eventId: String? = null, +) : Parcelable + +class RoomThreadDetailFragment @Inject constructor( + private val session: Session +) : + VectorBaseFragment() { + private val roomThreadDetailArgs: RoomThreadDetailArgs by args() + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRoomThreadDetailBinding { + return FragmentRoomThreadDetailBinding.inflate(inflater, container, false) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + } + + @SuppressLint("SetTextI18n") + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + views.testTextVeiwddasda.text = "${roomThreadDetailArgs.eventId} -- ${roomThreadDetailArgs.roomId}" + } +} diff --git a/vector/src/main/res/drawable/ic_reply_in_thread.xml b/vector/src/main/res/drawable/ic_reply_in_thread.xml new file mode 100644 index 00000000000..955dc27f45f --- /dev/null +++ b/vector/src/main/res/drawable/ic_reply_in_thread.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/activity_room_thread_detail.xml b/vector/src/main/res/layout/activity_room_thread_detail.xml new file mode 100644 index 00000000000..94c52ab959d --- /dev/null +++ b/vector/src/main/res/layout/activity_room_thread_detail.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/activity_room_threads.xml b/vector/src/main/res/layout/activity_room_threads.xml new file mode 100644 index 00000000000..b469c7de42f --- /dev/null +++ b/vector/src/main/res/layout/activity_room_threads.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/fragment_room_detail.xml b/vector/src/main/res/layout/fragment_room_detail.xml index c0ac3170e51..7725cd5e920 100644 --- a/vector/src/main/res/layout/fragment_room_detail.xml +++ b/vector/src/main/res/layout/fragment_room_detail.xml @@ -18,7 +18,7 @@ android:layout_height="wrap_content" android:minHeight="48dp" android:visibility="gone" - tools:visibility="visible" /> + tools:visibility="gone" /> + + + + + + + diff --git a/vector/src/main/res/menu/menu_room_threads.xml b/vector/src/main/res/menu/menu_room_threads.xml new file mode 100644 index 00000000000..3d4478332aa --- /dev/null +++ b/vector/src/main/res/menu/menu_room_threads.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 7fa4918266a..591cc152b90 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1021,6 +1021,9 @@ INVITED JOINED + + Filter Threads in room + Reason for reporting this content Do you want to hide all messages from this user?\n\nNote that this action will restart the app and it may take some time. @@ -2171,6 +2174,7 @@ Edit Reply + Reply In Thread Retry "Join a room to start using the app." From cb6376670b785dd8b288717c5d615ffcec2934ca Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Thu, 21 Oct 2021 12:25:43 +0300 Subject: [PATCH 002/581] Add room avatar to threads activities --- .../home/room/detail/RoomDetailFragment.kt | 8 +++-- .../home/room/detail/RoomDetailViewModel.kt | 2 ++ .../detail/RoomThreadDetailActivity.kt | 31 ++++++++++++++++--- .../detail/RoomThreadDetailFragment.kt | 13 ++------ .../detail/arguments/RoomThreadDetailArgs.kt | 28 +++++++++++++++++ 5 files changed, 65 insertions(+), 17 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/home/room/threads/detail/arguments/RoomThreadDetailArgs.kt diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index 8380774a494..9157ec3a2c7 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -161,7 +161,7 @@ import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever import im.vector.app.features.home.room.detail.upgrade.MigrateRoomBottomSheet import im.vector.app.features.home.room.detail.views.RoomDetailLazyLoadedViews import im.vector.app.features.home.room.detail.widget.RoomWidgetsBottomSheet -import im.vector.app.features.home.room.threads.detail.RoomThreadDetailArgs +import im.vector.app.features.home.room.threads.detail.arguments.RoomThreadDetailArgs import im.vector.app.features.home.room.threads.detail.RoomThreadDetailActivity import im.vector.app.features.html.EventHtmlRenderer import im.vector.app.features.html.PillImageSpan @@ -1962,7 +1962,11 @@ class RoomDetailFragment @Inject constructor( is EventSharedAction.ReplyInThread -> { if (!views.voiceMessageRecorderView.isActive()) { context?.let { - val roomThreadDetailArgs = RoomThreadDetailArgs(roomDetailArgs.roomId,action.eventId) + val roomThreadDetailArgs = RoomThreadDetailArgs( + roomId = roomDetailArgs.roomId, + displayName = roomDetailViewModel.getRoomSummary()?.displayName, + avatarUrl = roomDetailViewModel.getRoomSummary()?.avatarUrl, + eventId = action.eventId) startActivity(RoomThreadDetailActivity.newIntent(it, roomThreadDetailArgs)) } } else { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index 0c0e5ee6cd3..b9622ae9ae4 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -282,6 +282,8 @@ class RoomDetailViewModel @AssistedInject constructor( fun getOtherUserIds() = room.roomSummary()?.otherMemberIds + fun getRoomSummary() = room.roomSummary() + override fun handle(action: RoomDetailAction) { when (action) { is RoomDetailAction.ComposerFocusChange -> handleComposerFocusChange(action) diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/detail/RoomThreadDetailActivity.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/detail/RoomThreadDetailActivity.kt index 96b65d02726..25dd1e60313 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/detail/RoomThreadDetailActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/detail/RoomThreadDetailActivity.kt @@ -24,9 +24,16 @@ import im.vector.app.core.di.ScreenComponent import im.vector.app.core.extensions.replaceFragment import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.databinding.ActivityRoomThreadDetailBinding +import im.vector.app.features.home.AvatarRenderer +import im.vector.app.features.home.room.threads.detail.arguments.RoomThreadDetailArgs +import org.matrix.android.sdk.api.util.MatrixItem +import javax.inject.Inject class RoomThreadDetailActivity : VectorBaseActivity() { + @Inject + lateinit var avatarRenderer: AvatarRenderer + // private val roomThreadDetailFragment: RoomThreadDetailFragment? // get() { // return supportFragmentManager.findFragmentByTag(FRAGMENT_TAG) as? RoomThreadDetailFragment @@ -44,20 +51,34 @@ class RoomThreadDetailActivity : VectorBaseActivity() { +) : VectorBaseFragment() { + private val roomThreadDetailArgs: RoomThreadDetailArgs by args() override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRoomThreadDetailBinding { diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/detail/arguments/RoomThreadDetailArgs.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/detail/arguments/RoomThreadDetailArgs.kt new file mode 100644 index 00000000000..5fce24cd0de --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/detail/arguments/RoomThreadDetailArgs.kt @@ -0,0 +1,28 @@ +/* + * 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.home.room.threads.detail.arguments + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +data class RoomThreadDetailArgs( + val roomId: String, + val displayName: String?, + val avatarUrl: String?, + val eventId: String? = null, +) : Parcelable From a2a2315f9c00e63054224c3788d0bbf6b7b70214 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Thu, 21 Oct 2021 16:53:20 +0300 Subject: [PATCH 003/581] Make room thread detail text composer visible --- .../home/room/threads/detail/RoomThreadDetailActivity.kt | 1 - .../home/room/threads/detail/RoomThreadDetailFragment.kt | 7 +++++++ vector/src/main/res/layout/fragment_room_thread_detail.xml | 7 +++---- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/detail/RoomThreadDetailActivity.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/detail/RoomThreadDetailActivity.kt index 25dd1e60313..b8a58e178d7 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/detail/RoomThreadDetailActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/detail/RoomThreadDetailActivity.kt @@ -51,7 +51,6 @@ class RoomThreadDetailActivity : VectorBaseActivity Date: Thu, 4 Nov 2021 09:33:32 +0200 Subject: [PATCH 004/581] Add changelog file --- .../sdk/api/session/events/model/RelationType.kt | 3 ++- .../session/room/relation/DefaultRelationService.kt | 5 ++++- .../room/threads/detail/RoomThreadDetailFragment.kt | 2 +- .../main/res/layout/fragment_room_thread_detail.xml | 11 +++++------ 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/RelationType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/RelationType.kt index 653798c29cf..65462587666 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/RelationType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/RelationType.kt @@ -29,7 +29,8 @@ object RelationType { const val REFERENCE = "m.reference" /** Lets you define an event which is a reply to an existing event.*/ - const val THREAD = "m.thread" +// const val THREAD = "m.thread" + const val THREAD = "io.element.thread" /** Lets you define an event which adds a response to an existing event.*/ const val RESPONSE = "org.matrix.response" 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 b3afc6ad469..7dec4ab3de1 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 @@ -56,7 +56,7 @@ internal class DefaultRelationService @AssistedInject constructor( private val timelineEventMapper: TimelineEventMapper, @SessionDatabase private val monarchy: Monarchy, private val taskExecutor: TaskExecutor) : - RelationService { + RelationService { @AssistedFactory interface Factory { @@ -161,6 +161,9 @@ internal class DefaultRelationService @AssistedInject constructor( override fun replyInThread(eventToReplyInThread: TimelineEvent, replyInThreadText: CharSequence, autoMarkdown: Boolean): Cancelable? { val event = eventFactory.createThreadTextEvent(eventToReplyInThread, TextContent(replyInThreadText.toString())) + .also { + saveLocalEcho(it) + } return eventSenderProcessor.postEvent(event, cryptoSessionInfoProvider.isRoomEncrypted(roomId)) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/detail/RoomThreadDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/detail/RoomThreadDetailFragment.kt index 3332d7caa0c..532c67aaf8c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/detail/RoomThreadDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/detail/RoomThreadDetailFragment.kt @@ -47,7 +47,7 @@ class RoomThreadDetailFragment @Inject constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) initTextComposer() - views.testTextVeiwddasda.text = "${roomThreadDetailArgs.eventId} -- ${roomThreadDetailArgs.roomId}" +// views.testTextVeiwddasda.text = "${roomThreadDetailArgs.eventId} -- ${roomThreadDetailArgs.roomId}" } private fun initTextComposer(){ diff --git a/vector/src/main/res/layout/fragment_room_thread_detail.xml b/vector/src/main/res/layout/fragment_room_thread_detail.xml index 48e4896730d..cadc819d28c 100644 --- a/vector/src/main/res/layout/fragment_room_thread_detail.xml +++ b/vector/src/main/res/layout/fragment_room_thread_detail.xml @@ -6,12 +6,11 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - Date: Mon, 8 Nov 2021 20:46:37 +0200 Subject: [PATCH 005/581] Reply In Thread, create a new thread timeline --- .../sdk/api/session/events/model/Event.kt | 61 ++++++++------ .../room/model/relation/RelationService.kt | 4 +- .../model/relation/threads/ThreadContent.kt | 28 ------- .../model/relation/threads/ThreadRelatesTo.kt | 31 ------- .../relation/threads/ThreadTextContent.kt | 28 ------- .../database/RealmSessionStoreMigration.kt | 13 ++- .../internal/database/mapper/EventMapper.kt | 5 ++ .../internal/database/model/ChunkEntity.kt | 3 + .../internal/database/model/EventEntity.kt | 10 ++- .../database/query/ChunkEntityQueries.kt | 19 +++++ .../room/relation/DefaultRelationService.kt | 14 ++-- .../room/send/LocalEchoEventFactory.kt | 11 ++- .../internal/session/room/send/TextContent.kt | 14 ++-- .../room/timeline/TokenChunkEventPersistor.kt | 54 +++++++++++- .../sync/handler/room/RoomSyncHandler.kt | 84 ++++++++++++++++++- .../home/room/detail/RoomDetailFragment.kt | 35 ++++++-- .../home/room/detail/RoomDetailViewState.kt | 5 +- .../detail/composer/TextComposerAction.kt | 2 + .../detail/composer/TextComposerViewModel.kt | 21 +++-- .../detail/composer/TextComposerViewState.kt | 3 + .../timeline/factory/MessageItemFactory.kt | 2 +- .../helper/MessageItemAttributesFactory.kt | 6 +- .../detail/timeline/item/AbsMessageItem.kt | 5 +- .../detail/RoomThreadDetailActivity.kt | 13 ++- .../detail/RoomThreadDetailFragment.kt | 10 ++- .../res/layout/item_timeline_event_base.xml | 13 +++ 26 files changed, 340 insertions(+), 154 deletions(-) delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/threads/ThreadContent.kt delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/threads/ThreadRelatesTo.kt delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/threads/ThreadTextContent.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt index 169f90dbca0..896d6b0e7ba 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt @@ -96,6 +96,9 @@ data class Event( @Transient var sendStateDetails: String? = null + @Transient + var isRootThread: Boolean = false + fun sendStateError(): MatrixError? { return sendStateDetails?.let { val matrixErrorAdapter = MoshiProvider.providesMoshi().adapter(MatrixError::class.java) @@ -241,54 +244,54 @@ data class Event( fun Event.isTextMessage(): Boolean { return getClearType() == EventType.MESSAGE && when (getClearContent()?.get(MessageContent.MSG_TYPE_JSON_KEY)) { - MessageType.MSGTYPE_TEXT, - MessageType.MSGTYPE_EMOTE, - MessageType.MSGTYPE_NOTICE -> true - else -> false - } + MessageType.MSGTYPE_TEXT, + MessageType.MSGTYPE_EMOTE, + MessageType.MSGTYPE_NOTICE -> true + else -> false + } } fun Event.isImageMessage(): Boolean { return getClearType() == EventType.MESSAGE && when (getClearContent()?.get(MessageContent.MSG_TYPE_JSON_KEY)) { - MessageType.MSGTYPE_IMAGE -> true - else -> false - } + MessageType.MSGTYPE_IMAGE -> true + else -> false + } } fun Event.isVideoMessage(): Boolean { return getClearType() == EventType.MESSAGE && when (getClearContent()?.get(MessageContent.MSG_TYPE_JSON_KEY)) { - MessageType.MSGTYPE_VIDEO -> true - else -> false - } + MessageType.MSGTYPE_VIDEO -> true + else -> false + } } fun Event.isAudioMessage(): Boolean { return getClearType() == EventType.MESSAGE && when (getClearContent()?.get(MessageContent.MSG_TYPE_JSON_KEY)) { - MessageType.MSGTYPE_AUDIO -> true - else -> false - } + MessageType.MSGTYPE_AUDIO -> true + else -> false + } } fun Event.isFileMessage(): Boolean { return getClearType() == EventType.MESSAGE && when (getClearContent()?.get(MessageContent.MSG_TYPE_JSON_KEY)) { - MessageType.MSGTYPE_FILE -> true - else -> false - } + MessageType.MSGTYPE_FILE -> true + else -> false + } } fun Event.isAttachmentMessage(): Boolean { return getClearType() == EventType.MESSAGE && when (getClearContent()?.get(MessageContent.MSG_TYPE_JSON_KEY)) { - MessageType.MSGTYPE_IMAGE, - MessageType.MSGTYPE_AUDIO, - MessageType.MSGTYPE_VIDEO, - MessageType.MSGTYPE_FILE -> true - else -> false - } + MessageType.MSGTYPE_IMAGE, + MessageType.MSGTYPE_AUDIO, + MessageType.MSGTYPE_VIDEO, + MessageType.MSGTYPE_FILE -> true + else -> false + } } fun Event.getRelationContent(): RelationDefaultContent? { @@ -299,12 +302,22 @@ fun Event.getRelationContent(): RelationDefaultContent? { } } +/** + * Returns the relation content for a specific type or null otherwise + */ +fun Event.getRelationContentForType(type: String): RelationDefaultContent? = + getRelationContent()?.takeIf { it.type == type } + fun Event.isReply(): Boolean { return getRelationContent()?.inReplyTo?.eventId != null } +fun Event.isThread(): Boolean = getRelationContentForType(RelationType.THREAD)?.eventId != null + +fun Event.getRootThreadEventId(): String? = getRelationContentForType(RelationType.THREAD)?.eventId + fun Event.isEdition(): Boolean { - return getRelationContent()?.takeIf { it.type == RelationType.REPLACE }?.eventId != null + return getRelationContentForType(RelationType.REPLACE)?.eventId != null } fun Event.getPresenceContent(): PresenceContent? { 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 20e33fec8cd..226769ced47 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 @@ -131,11 +131,11 @@ interface RelationService { * Creates a thread reply for an existing timeline event * The replyInThreadText can be a Spannable and contains special spans (MatrixItemSpan) that will be translated * by the sdk into pills. - * @param eventToReplyInThread the event referenced by the thread reply + * @param rootThreadEventId the root thread eventId * @param replyInThreadText the reply text * @param autoMarkdown If true, the SDK will generate a formatted HTML message from the body text if markdown syntax is present */ - fun replyInThread(eventToReplyInThread: TimelineEvent, + fun replyInThread(rootThreadEventId: String, replyInThreadText: CharSequence, autoMarkdown: Boolean = false): Cancelable? } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/threads/ThreadContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/threads/ThreadContent.kt deleted file mode 100644 index 9d0fd9508a4..00000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/threads/ThreadContent.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 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.api.session.room.model.relation.threads - -interface ThreadContent { - - companion object { - const val MSG_TYPE_JSON_KEY = "msgtype" - } - - val msgType: String - val body: String - val relatesTo: ThreadRelatesTo? -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/threads/ThreadRelatesTo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/threads/ThreadRelatesTo.kt deleted file mode 100644 index 4a0d1e20545..00000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/threads/ThreadRelatesTo.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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 org.matrix.android.sdk.api.session.room.model.relation.threads - -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass -import org.matrix.android.sdk.api.session.events.model.RelationType -import org.matrix.android.sdk.api.session.room.model.relation.RelationContent -import org.matrix.android.sdk.api.session.room.model.relation.ReplyToContent - -@JsonClass(generateAdapter = true) -data class ThreadRelatesTo( - @Json(name = "rel_type") override val type: String? = RelationType.THREAD, - @Json(name = "event_id") override val eventId: String, - @Json(name = "m.in_reply_to") override val inReplyTo: ReplyToContent? = null, - @Json(name = "option") override val option: Int? = null -) : RelationContent diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/threads/ThreadTextContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/threads/ThreadTextContent.kt deleted file mode 100644 index 9244b0bf7f6..00000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/threads/ThreadTextContent.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 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.api.session.room.model.relation.threads - -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass -import org.matrix.android.sdk.api.session.room.model.message.MessageContent - -@JsonClass(generateAdapter = true) -data class ThreadTextContent( - @Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String, - @Json(name = "body") override val body: String, - @Json(name = "m.relates_to") override val relatesTo: ThreadRelatesTo? = null, -) : ThreadContent diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt index 05137f81052..96c32ea08fc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt @@ -25,6 +25,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent import org.matrix.android.sdk.api.session.room.model.VersioningState import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent import org.matrix.android.sdk.api.session.room.model.tag.RoomTag +import org.matrix.android.sdk.internal.database.model.ChunkEntityFields import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntityFields import org.matrix.android.sdk.internal.database.model.EditAggregatedSummaryEntityFields import org.matrix.android.sdk.internal.database.model.EditionOfEventFields @@ -49,7 +50,7 @@ import timber.log.Timber internal object RealmSessionStoreMigration : RealmMigration { - const val SESSION_STORE_SCHEMA_VERSION = 18L + const val SESSION_STORE_SCHEMA_VERSION = 19L override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { Timber.v("Migrating Realm Session from $oldVersion to $newVersion") @@ -72,6 +73,7 @@ internal object RealmSessionStoreMigration : RealmMigration { if (oldVersion <= 15) migrateTo16(realm) if (oldVersion <= 16) migrateTo17(realm) if (oldVersion <= 17) migrateTo18(realm) + if (oldVersion <= 18) migrateTo19(realm) } private fun migrateTo1(realm: DynamicRealm) { @@ -364,4 +366,13 @@ internal object RealmSessionStoreMigration : RealmMigration { realm.schema.get("RoomMemberSummaryEntity") ?.addRealmObjectField(RoomMemberSummaryEntityFields.USER_PRESENCE_ENTITY.`$`, userPresenceEntity) } + + private fun migrateTo19(realm: DynamicRealm) { + Timber.d("Step 18 -> 19") + realm.schema.get("EventEntity") + ?.addField(EventEntityFields.IS_THREAD, Boolean::class.java, FieldAttribute.INDEXED) + ?.addField(EventEntityFields.ROOT_THREAD_EVENT_ID, String::class.java) + realm.schema.get("ChunkEntity") + ?.addField(ChunkEntityFields.ROOT_THREAD_EVENT_ID, String::class.java, FieldAttribute.INDEXED) + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt index 613b38e3403..21a93ba9045 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt @@ -21,6 +21,8 @@ import org.matrix.android.sdk.api.session.crypto.MXCryptoError 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.UnsignedData +import org.matrix.android.sdk.api.session.events.model.getRootThreadEventId +import org.matrix.android.sdk.api.session.events.model.isThread import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult import org.matrix.android.sdk.internal.database.model.EventEntity @@ -39,6 +41,8 @@ internal object EventMapper { eventEntity.isUseless = IsUselessResolver.isUseless(event) eventEntity.stateKey = event.stateKey eventEntity.type = event.type ?: EventType.MISSING_TYPE + eventEntity.isThread = if(event.isRootThread) true else event.isThread() + eventEntity.rootThreadEventId = if(event.isRootThread) null else event.getRootThreadEventId() eventEntity.sender = event.senderId eventEntity.originServerTs = event.originServerTs eventEntity.redacts = event.redacts @@ -93,6 +97,7 @@ internal object EventMapper { MXCryptoError.ErrorType.valueOf(errorCode) } it.mCryptoErrorReason = eventEntity.decryptionErrorReason + it.isRootThread = eventEntity.isRootThread() } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt index 68533a3c199..2b763dd941e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt @@ -27,6 +27,7 @@ import org.matrix.android.sdk.internal.extensions.clearWith internal open class ChunkEntity(@Index var prevToken: String? = null, // Because of gaps we can have several chunks with nextToken == null @Index var nextToken: String? = null, + @Index var rootThreadEventId: String? = null, var stateEvents: RealmList = RealmList(), var timelineEvents: RealmList = RealmList(), var numberOfTimelineEvents: Long = 0, @@ -44,6 +45,8 @@ internal open class ChunkEntity(@Index var prevToken: String? = null, val room: RealmResults? = null companion object + + fun isThreadChunk() = rootThreadEventId != null } internal fun ChunkEntity.deleteOnCascade(deleteStateEvents: Boolean, canDeleteRoot: Boolean) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt index bcd30cb54bb..ad889dd3528 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt @@ -27,13 +27,15 @@ import org.matrix.android.sdk.internal.extensions.assertIsManaged internal open class EventEntity(@Index var eventId: String = "", @Index var roomId: String = "", @Index var type: String = "", + @Index var isThread: Boolean = false, + var rootThreadEventId: String? = null, var content: String? = null, var prevContent: String? = null, var isUseless: Boolean = false, @Index var stateKey: String? = null, var originServerTs: Long? = null, @Index var sender: String? = null, - // Can contain a serialized MatrixError + // Can contain a serialized MatrixError var sendStateDetails: String? = null, var age: Long? = 0, var unsignedData: String? = null, @@ -75,4 +77,10 @@ internal open class EventEntity(@Index var eventId: String = "", .findFirst() ?.canBeProcessed = true } + + /** + * Returns true if the current event is a thread root event + */ + fun isRootThread(): Boolean = isThread && rootThreadEventId == null + } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ChunkEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ChunkEntityQueries.kt index 156a8dd767c..6018305c39d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ChunkEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ChunkEntityQueries.kt @@ -33,9 +33,11 @@ internal fun ChunkEntity.Companion.find(realm: Realm, roomId: String, prevToken: val query = where(realm, roomId) if (prevToken != null) { query.equalTo(ChunkEntityFields.PREV_TOKEN, prevToken) + query.isNull(ChunkEntityFields.ROOT_THREAD_EVENT_ID) } if (nextToken != null) { query.equalTo(ChunkEntityFields.NEXT_TOKEN, nextToken) + query.isNull(ChunkEntityFields.ROOT_THREAD_EVENT_ID) } return query.findFirst() } @@ -43,12 +45,15 @@ internal fun ChunkEntity.Companion.find(realm: Realm, roomId: String, prevToken: internal fun ChunkEntity.Companion.findLastForwardChunkOfRoom(realm: Realm, roomId: String): ChunkEntity? { return where(realm, roomId) .equalTo(ChunkEntityFields.IS_LAST_FORWARD, true) + .isNull(ChunkEntityFields.ROOT_THREAD_EVENT_ID) .findFirst() } + internal fun ChunkEntity.Companion.findAllIncludingEvents(realm: Realm, eventIds: List): RealmResults { return realm.where() .`in`(ChunkEntityFields.TIMELINE_EVENTS.EVENT_ID, eventIds.toTypedArray()) + .isNull(ChunkEntityFields.ROOT_THREAD_EVENT_ID) .findAll() } @@ -56,6 +61,7 @@ internal fun ChunkEntity.Companion.findIncludingEvent(realm: Realm, eventId: Str return findAllIncludingEvents(realm, listOf(eventId)).firstOrNull() } + internal fun ChunkEntity.Companion.create( realm: Realm, prevToken: String?, @@ -66,3 +72,16 @@ internal fun ChunkEntity.Companion.create( this.nextToken = nextToken } } + +// Threads +internal fun ChunkEntity.Companion.findThreadChunkOfRoom(realm: Realm, roomId: String, rootThreadEventId: String): ChunkEntity? { + return where(realm, roomId) + .equalTo(ChunkEntityFields.ROOT_THREAD_EVENT_ID, rootThreadEventId) + .findFirst() +} + +internal fun ChunkEntity.Companion.findAllThreadChunkOfRoom(realm: Realm, roomId: String): RealmResults { + return where(realm, roomId) + .isNotNull(ChunkEntityFields.ROOT_THREAD_EVENT_ID) + .findAll() +} 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 7dec4ab3de1..833f056ceb8 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 @@ -159,11 +159,15 @@ internal class DefaultRelationService @AssistedInject constructor( } } - override fun replyInThread(eventToReplyInThread: TimelineEvent, replyInThreadText: CharSequence, autoMarkdown: Boolean): Cancelable? { - val event = eventFactory.createThreadTextEvent(eventToReplyInThread, TextContent(replyInThreadText.toString())) - .also { - saveLocalEcho(it) - } + override fun replyInThread(rootThreadEventId: String, replyInThreadText: CharSequence, autoMarkdown: Boolean): Cancelable { + val event = eventFactory.createThreadTextEvent( + rootThreadEventId = rootThreadEventId, + roomId = roomId, + text = replyInThreadText.toString(), + autoMarkdown = autoMarkdown) +// .also { +// saveLocalEcho(it) +// } return eventSenderProcessor.postEvent(event, cryptoSessionInfoProvider.isRoomEncrypted(roomId)) } 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 2e1a95feb5a..b69e8683388 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 @@ -51,8 +51,6 @@ import org.matrix.android.sdk.api.session.room.model.relation.ReactionContent import org.matrix.android.sdk.api.session.room.model.relation.ReactionInfo import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent import org.matrix.android.sdk.api.session.room.model.relation.ReplyToContent -import org.matrix.android.sdk.api.session.room.model.relation.threads.ThreadTextContent -import org.matrix.android.sdk.api.session.room.model.relation.threads.ThreadRelatesTo 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.isReply @@ -60,7 +58,6 @@ import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.content.ThumbnailExtractor import org.matrix.android.sdk.internal.session.permalinks.PermalinkFactory import org.matrix.android.sdk.internal.session.room.send.pills.TextPillsUtils -import timber.log.Timber import javax.inject.Inject /** @@ -346,11 +343,13 @@ internal class LocalEchoEventFactory @Inject constructor( /** * Creates a thread event related to the already existing event */ - fun createThreadTextEvent(eventToReplyInThread: TimelineEvent, textContent: TextContent): Event = + fun createThreadTextEvent(rootThreadEventId: String, roomId:String, text: String, autoMarkdown: Boolean): Event = createEvent( - eventToReplyInThread.roomId, + roomId, EventType.MESSAGE, - textContent.toThreadTextContent(eventToReplyInThread).toContent()) + createTextContent(text, autoMarkdown) + .toThreadTextContent(rootThreadEventId) + .toContent()) private fun dummyOriginServerTs(): Long { return System.currentTimeMillis() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/TextContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/TextContent.kt index c3f4f72834f..d3e0189f4b9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/TextContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/TextContent.kt @@ -16,13 +16,11 @@ package org.matrix.android.sdk.internal.session.room.send -import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.events.model.RelationType import org.matrix.android.sdk.api.session.room.model.message.MessageFormat 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.threads.ThreadTextContent -import org.matrix.android.sdk.api.session.room.model.relation.threads.ThreadRelatesTo -import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent import org.matrix.android.sdk.api.util.ContentUtils.extractUsefulTextFromHtmlReply import org.matrix.android.sdk.api.util.ContentUtils.extractUsefulTextFromReply @@ -45,11 +43,13 @@ fun TextContent.toMessageTextContent(msgType: String = MessageType.MSGTYPE_TEXT) ) } -fun TextContent.toThreadTextContent(eventToReplyInThread: TimelineEvent, msgType: String = MessageType.MSGTYPE_TEXT): ThreadTextContent { - return ThreadTextContent( +fun TextContent.toThreadTextContent(rootThreadEventId: String, msgType: String = MessageType.MSGTYPE_TEXT): MessageTextContent { + return MessageTextContent( msgType = msgType, + format = MessageFormat.FORMAT_MATRIX_HTML.takeIf { formattedText != null }, body = text, - relatesTo = ThreadRelatesTo(eventId = eventToReplyInThread.eventId) + relatesTo = RelationDefaultContent(RelationType.THREAD, rootThreadEventId), + formattedBody = formattedText ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt index dbcc37a918a..7b873166b47 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt @@ -18,7 +18,9 @@ package org.matrix.android.sdk.internal.session.room.timeline import com.zhuinden.monarchy.Monarchy import io.realm.Realm +import io.realm.kotlin.createObject import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.isThread import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.RoomMemberContent import org.matrix.android.sdk.api.session.room.send.SendState @@ -28,6 +30,8 @@ import org.matrix.android.sdk.internal.database.helper.addTimelineEvent import org.matrix.android.sdk.internal.database.helper.merge import org.matrix.android.sdk.internal.database.mapper.toEntity import org.matrix.android.sdk.internal.database.model.ChunkEntity +import org.matrix.android.sdk.internal.database.model.EventEntity +import org.matrix.android.sdk.internal.database.model.EventEntityFields import org.matrix.android.sdk.internal.database.model.EventInsertType import org.matrix.android.sdk.internal.database.model.RoomEntity import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity @@ -37,6 +41,7 @@ import org.matrix.android.sdk.internal.database.query.create import org.matrix.android.sdk.internal.database.query.find import org.matrix.android.sdk.internal.database.query.findAllIncludingEvents import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfRoom +import org.matrix.android.sdk.internal.database.query.findThreadChunkOfRoom import org.matrix.android.sdk.internal.database.query.getOrCreate import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase @@ -221,7 +226,15 @@ internal class TokenChunkEventPersistor @Inject constructor(@SessionDatabase pri roomMemberContentsByUser[event.stateKey] = contentToUse.toModel() } - currentChunk.addTimelineEvent(roomId, eventEntity, direction, roomMemberContentsByUser) + Timber.i("------> [TokenChunkEventPersistor] Add TimelineEvent to chunkEntity event[${event.eventId}] ${if (event.isThread()) "is Thread" else ""}") + + addTimelineEventToChunk( + realm = realm, + roomId = roomId, + eventEntity = eventEntity, + currentChunk = currentChunk, + direction = direction, + roomMemberContentsByUser = roomMemberContentsByUser) } // Find all the chunks which contain at least one event from the list of eventIds val chunks = ChunkEntity.findAllIncludingEvents(realm, eventIds) @@ -247,4 +260,43 @@ internal class TokenChunkEventPersistor @Inject constructor(@SessionDatabase pri RoomEntity.where(realm, roomId).findFirst()?.addIfNecessary(currentChunk) } } + + /** + * Adds a timeline event to the correct chunk. If there is a thread detected will be added + * to a specific chunk + */ + private fun addTimelineEventToChunk(realm: Realm, + roomId: String, + eventEntity: EventEntity, + currentChunk: ChunkEntity, + direction: PaginationDirection, + roomMemberContentsByUser: Map) { + val rootThreadEventId = eventEntity.rootThreadEventId + if (eventEntity.isThread && rootThreadEventId != null) { + val threadChunk = getOrCreateThreadChunk(realm, roomId, rootThreadEventId) + threadChunk.addTimelineEvent(roomId, eventEntity, direction, roomMemberContentsByUser) + markEventAsRootEvent(realm, rootThreadEventId) + if (threadChunk.isValid) + RoomEntity.where(realm, roomId).findFirst()?.addIfNecessary(threadChunk) + } else { + currentChunk.addTimelineEvent(roomId, eventEntity, direction, roomMemberContentsByUser) + } + } + + private fun markEventAsRootEvent(realm: Realm, rootThreadEventId: String) { + val rootThreadEvent = EventEntity + .where(realm, rootThreadEventId) + .equalTo(EventEntityFields.IS_THREAD, false).findFirst() ?: return + rootThreadEvent.isThread = true + } + + /** + * Returns the chunk for the current room if exists, otherwise it creates a new ChunkEntity + */ + private fun getOrCreateThreadChunk(realm: Realm, roomId: String, rootThreadEventId: String): ChunkEntity { + return ChunkEntity.findThreadChunkOfRoom(realm, roomId, rootThreadEventId) + ?: realm.createObject().apply { + this.rootThreadEventId = rootThreadEventId + } + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt index 8c4af81c99a..ea5aedeee92 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt @@ -21,31 +21,41 @@ import io.realm.kotlin.createObject import org.matrix.android.sdk.api.session.crypto.MXCryptoError 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.isThread import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.initsync.InitSyncStep import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomMemberContent +import org.matrix.android.sdk.api.session.room.model.message.MessageRelationContent import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.sync.model.InvitedRoomSync import org.matrix.android.sdk.api.session.sync.model.LazyRoomSyncEphemeral import org.matrix.android.sdk.api.session.sync.model.RoomSync import org.matrix.android.sdk.api.session.sync.model.RoomsSyncResponse +import org.matrix.android.sdk.api.util.JsonDict +import org.matrix.android.sdk.internal.crypto.CryptoSessionInfoProvider import org.matrix.android.sdk.internal.crypto.DefaultCryptoService import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult import org.matrix.android.sdk.internal.database.helper.addIfNecessary import org.matrix.android.sdk.internal.database.helper.addTimelineEvent +import org.matrix.android.sdk.internal.database.mapper.EventMapper import org.matrix.android.sdk.internal.database.mapper.asDomain import org.matrix.android.sdk.internal.database.mapper.toEntity import org.matrix.android.sdk.internal.database.model.ChunkEntity import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity +import org.matrix.android.sdk.internal.database.model.EventEntity +import org.matrix.android.sdk.internal.database.model.EventEntityFields import org.matrix.android.sdk.internal.database.model.EventInsertType import org.matrix.android.sdk.internal.database.model.RoomEntity import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity +import org.matrix.android.sdk.internal.database.model.TimelineEventEntity import org.matrix.android.sdk.internal.database.model.deleteOnCascade import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore import org.matrix.android.sdk.internal.database.query.find +import org.matrix.android.sdk.internal.database.query.findAllThreadChunkOfRoom import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfRoom +import org.matrix.android.sdk.internal.database.query.findThreadChunkOfRoom import org.matrix.android.sdk.internal.database.query.getOrCreate import org.matrix.android.sdk.internal.database.query.getOrNull import org.matrix.android.sdk.internal.database.query.where @@ -58,8 +68,11 @@ import org.matrix.android.sdk.internal.session.initsync.mapWithProgress import org.matrix.android.sdk.internal.session.initsync.reportSubtask import org.matrix.android.sdk.internal.session.room.membership.RoomChangeMembershipStateDataSource import org.matrix.android.sdk.internal.session.room.membership.RoomMemberEventHandler +import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory +import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryUpdater import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection +import org.matrix.android.sdk.internal.session.room.timeline.TimelineEventDecryptor import org.matrix.android.sdk.internal.session.room.timeline.TimelineInput import org.matrix.android.sdk.internal.session.room.typing.TypingEventContent import org.matrix.android.sdk.internal.session.sync.InitialSyncStrategy @@ -356,6 +369,21 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle val eventIds = ArrayList(eventList.size) val roomMemberContentsByUser = HashMap() +///////////////////// + // There is only one chunk per room + + val threadChunks = ChunkEntity.findAllThreadChunkOfRoom(realm, roomId) + + val tc = threadChunks.joinToString { chunk -> + var output = "\n----------------\n------> [${chunk.timelineEvents.size}] rootThreadEventId = ${chunk.rootThreadEventId}" + "\n" + output += chunk.timelineEvents + .joinToString("") { + "------> " + "eventId:[${it?.eventId}] payload:[${getValueFromPayload(it.root?.let { root -> EventMapper.map(root).mxDecryptionResult }?.payload, "body")}]\n" + } + output + } + Timber.i("------> Chunks (${threadChunks.size})$tc") +///////////////////// for (event in eventList) { if (event.eventId == null || event.senderId == null || event.type == null) { continue @@ -385,7 +413,16 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle rootStateEvent?.asDomain()?.getFixedRoomMemberContent() } - chunkEntity.addTimelineEvent(roomId, eventEntity, PaginationDirection.FORWARDS, roomMemberContentsByUser) + Timber.i("------> [RoomSyncHandler] Add TimelineEvent to chunkEntity event[${event.eventId}] ${if (event.isThread()) "is Thread" else ""}") + + addTimelineEventToChunk( + realm = realm, + roomId = roomId, + eventEntity = eventEntity, + chunkEntity = chunkEntity, + roomEntity = roomEntity, + roomMemberContentsByUser = roomMemberContentsByUser) + // Give info to crypto module cryptoService.onLiveEvent(roomEntity.roomId, event) @@ -412,9 +449,54 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle } // posting new events to timeline if any is registered timelineInput.onNewTimelineEvents(roomId = roomId, eventIds = eventIds) + return chunkEntity } + /** + * Adds a timeline event to the correct chunk. If there is a thread detected will be added + * to a specific chunk + */ + private fun addTimelineEventToChunk(realm: Realm, + roomId: String, + eventEntity: EventEntity, + chunkEntity: ChunkEntity, + roomEntity: RoomEntity, + roomMemberContentsByUser: Map) { + val rootThreadEventId = eventEntity.rootThreadEventId + if (eventEntity.isThread && rootThreadEventId != null) { + val threadChunk = getOrCreateThreadChunk(realm, roomId, rootThreadEventId) + threadChunk.addTimelineEvent(roomId, eventEntity, PaginationDirection.FORWARDS, roomMemberContentsByUser) + markEventAsRootEvent(realm, rootThreadEventId) + roomEntity.addIfNecessary(threadChunk) + } else { + chunkEntity.addTimelineEvent(roomId, eventEntity, PaginationDirection.FORWARDS, roomMemberContentsByUser) + } + } + + @Suppress("UNCHECKED_CAST") + private fun getValueFromPayload(payload: JsonDict?, key: String): String? { + val content = payload?.get("content") as? JsonDict + return content?.get(key) as? String + } + + /** + * Returns the chunk for the current room if exists, otherwise it creates a new ChunkEntity + */ + private fun getOrCreateThreadChunk(realm: Realm, roomId: String, rootThreadEventId: String): ChunkEntity { + return ChunkEntity.findThreadChunkOfRoom(realm, roomId, rootThreadEventId) + ?: realm.createObject().apply { + this.rootThreadEventId = rootThreadEventId + } + } + + private fun markEventAsRootEvent(realm: Realm, rootThreadEventId: String){ + val rootThreadEvent = EventEntity + .where(realm, rootThreadEventId) + .equalTo(EventEntityFields.IS_THREAD, false).findFirst() ?: return + rootThreadEvent.isThread = true + } + private fun decryptIfNeeded(event: Event, roomId: String) { try { // Event from sync does not have roomId, so add it to the event first diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index 9157ec3a2c7..bbccc78c9ca 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -49,6 +49,7 @@ import androidx.core.text.toSpannable import androidx.core.util.Pair import androidx.core.view.ViewCompat import androidx.core.view.forEach +import androidx.core.view.isGone import androidx.core.view.isInvisible import androidx.core.view.isVisible import androidx.fragment.app.setFragmentResultListener @@ -228,7 +229,8 @@ data class RoomDetailArgs( val roomId: String, val eventId: String? = null, val sharedData: SharedData? = null, - val openShareSpaceForId: String? = null + val openShareSpaceForId: String? = null, + val roomThreadDetailArgs: RoomThreadDetailArgs? = null ) : Parcelable class RoomDetailFragment @Inject constructor( @@ -352,7 +354,12 @@ class RoomDetailFragment @Inject constructor( ) keyboardStateUtils = KeyboardStateUtils(requireActivity()) lazyLoadedViews.bind(views) - setupToolbar(views.roomToolbar) + if (isThreadTimeLine()) { + views.roomToolbar.isGone = true + } else { + setupToolbar(views.roomToolbar) + } + setupThreadIfNeeded() setupRecyclerView() setupComposer() setupNotificationView() @@ -390,10 +397,10 @@ class RoomDetailFragment @Inject constructor( 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.quote, mode.text) - is SendMode.REPLY -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_reply, R.string.reply, mode.text) + 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.quote, mode.text) + is SendMode.REPLY -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_reply, R.string.reply, mode.text) } } @@ -902,6 +909,7 @@ class RoomDetailFragment @Inject constructor( override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { super.onCreateOptionsMenu(menu, inflater) + if (isThreadTimeLine()) return // We use a custom layout for this menu item, so we need to set a ClickListener menu.findItem(R.id.open_matrix_apps)?.let { menuItem -> menuItem.actionView.setOnClickListener { @@ -915,6 +923,12 @@ class RoomDetailFragment @Inject constructor( } override fun onPrepareOptionsMenu(menu: Menu) { + if (isThreadTimeLine()) { + menu.forEach { + it.isVisible = false + } + return + } menu.forEach { it.isVisible = roomDetailViewModel.isMenuItemVisible(it.itemId) } @@ -1180,6 +1194,12 @@ class RoomDetailFragment @Inject constructor( // PRIVATE METHODS ***************************************************************************** + private fun setupThreadIfNeeded(){ + getRootThreadEventId()?.let{ + textComposerViewModel.handle(TextComposerAction.EnterReplyInThreadTimeline(it)) + } + } + private fun setupRecyclerView() { timelineEventController.callback = this timelineEventController.timeline = roomDetailViewModel.timeline @@ -2203,4 +2223,7 @@ class RoomDetailFragment @Inject constructor( } } } + + fun isThreadTimeLine(): Boolean = roomDetailArgs.roomThreadDetailArgs != null + fun getRootThreadEventId(): String? = roomDetailArgs.roomThreadDetailArgs?.eventId } 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 042a415b47e..3266ae60e44 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 @@ -65,8 +65,9 @@ data class RoomDetailViewState( val isAllowedToManageWidgets: Boolean = false, val isAllowedToStartWebRTCCall: Boolean = true, val hasFailedSending: Boolean = false, - val jitsiState: JitsiState = JitsiState() -) : MavericksState { + val jitsiState: JitsiState = JitsiState(), + val rootThreadEventId: String? = null + ) : MavericksState { constructor(args: RoomDetailArgs) : this( roomId = args.roomId, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerAction.kt index 77254001875..48f6c849831 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerAction.kt @@ -28,4 +28,6 @@ sealed class TextComposerAction : VectorViewModelAction { data class UserIsTyping(val isTyping: Boolean) : TextComposerAction() data class OnTextChanged(val text: CharSequence) : TextComposerAction() data class OnVoiceRecordingStateChanged(val isRecording: Boolean) : TextComposerAction() + data class EnterReplyInThreadTimeline(val rootThreadEventId: String) : TextComposerAction() + } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt index 742d2848a1b..1541d5738b9 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt @@ -42,6 +42,7 @@ import org.commonmark.renderer.html.HtmlRenderer 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.EventType +import org.matrix.android.sdk.api.session.events.model.getRootThreadEventId 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.PowerLevelsContent @@ -88,6 +89,8 @@ class TextComposerViewModel @AssistedInject constructor( is TextComposerAction.UserIsTyping -> handleUserIsTyping(action) is TextComposerAction.OnTextChanged -> handleOnTextChanged(action) is TextComposerAction.OnVoiceRecordingStateChanged -> handleOnVoiceRecordingStateChanged(action) + is TextComposerAction.EnterReplyInThreadTimeline -> handleEnterReplyInThreadTimeline(action) + } } @@ -95,6 +98,10 @@ class TextComposerViewModel @AssistedInject constructor( copy(isVoiceRecording = action.isRecording) } + private fun handleEnterReplyInThreadTimeline(action: TextComposerAction.EnterReplyInThreadTimeline) = setState { + copy(rootThreadEventId = action.rootThreadEventId) + } + private fun handleOnTextChanged(action: TextComposerAction.OnTextChanged) { setState { // Makes sure currentComposerText is upToDate when accessing further setState @@ -151,11 +158,15 @@ class TextComposerViewModel @AssistedInject constructor( private fun handleSendMessage(action: TextComposerAction.SendMessage) { withState { state -> when (state.sendMode) { - is SendMode.REGULAR -> { + is SendMode.REGULAR -> { when (val slashCommandResult = CommandParser.parseSplashCommand(action.text)) { is ParsedCommand.ErrorNotACommand -> { // Send the text message to the room - room.sendTextMessage(action.text, autoMarkdown = action.autoMarkdown) + if (state.rootThreadEventId != null) + room.replyInThread(state.rootThreadEventId, action.text.toString(), action.autoMarkdown) + else + room.sendTextMessage(action.text, autoMarkdown = action.autoMarkdown) + _viewEvents.post(TextComposerViewEvents.MessageSent) popDraft() } @@ -386,7 +397,7 @@ class TextComposerViewModel @AssistedInject constructor( } }.exhaustive } - is SendMode.EDIT -> { + is SendMode.EDIT -> { // is original event a reply? val inReplyTo = state.sendMode.timelineEvent.getRelationContent()?.inReplyTo?.eventId if (inReplyTo != null) { @@ -409,7 +420,7 @@ class TextComposerViewModel @AssistedInject constructor( _viewEvents.post(TextComposerViewEvents.MessageSent) popDraft() } - is SendMode.QUOTE -> { + is SendMode.QUOTE -> { val messageContent = state.sendMode.timelineEvent.getLastMessageContent() val textMsg = messageContent?.body @@ -430,7 +441,7 @@ class TextComposerViewModel @AssistedInject constructor( _viewEvents.post(TextComposerViewEvents.MessageSent) popDraft() } - is SendMode.REPLY -> { + is SendMode.REPLY -> { state.sendMode.timelineEvent.let { room.replyToMessage(it, action.text.toString(), action.autoMarkdown) _viewEvents.post(TextComposerViewEvents.MessageSent) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewState.kt index 3110aa8dc34..36bdc4f5b2a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewState.kt @@ -46,6 +46,7 @@ data class TextComposerViewState( val canSendMessage: Boolean = true, val isVoiceRecording: Boolean = false, val isSendButtonVisible: Boolean = false, + val rootThreadEventId: String? = null, val sendMode: SendMode = SendMode.REGULAR("", false) ) : MavericksState { @@ -53,4 +54,6 @@ data class TextComposerViewState( get() = canSendMessage && !isVoiceRecording constructor(args: RoomDetailArgs) : this(roomId = args.roomId) + + fun isInThreadTimeline(): Boolean = rootThreadEventId != null } 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 98deaaf9c37..54cdb6db09f 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 @@ -149,7 +149,7 @@ class MessageItemFactory @Inject constructor( // This is an edit event, we should display it when debugging as a notice event return noticeItemFactory.create(params) } - val attributes = messageItemAttributesFactory.create(messageContent, informationData, callback) + val attributes = messageItemAttributesFactory.create(messageContent, informationData, callback, event.root.isRootThread) // val all = event.root.toContent() // val ev = all.toModel() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt index 679613d2628..80b36fa69f1 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt @@ -31,7 +31,8 @@ class MessageItemAttributesFactory @Inject constructor( fun create(messageContent: Any?, informationData: MessageInformationData, - callback: TimelineEventController.Callback?): AbsMessageItem.Attributes { + callback: TimelineEventController.Callback?, + isRootThread: Boolean = false): AbsMessageItem.Attributes { return AbsMessageItem.Attributes( avatarSize = avatarSizeProvider.avatarSize, informationData = informationData, @@ -49,7 +50,8 @@ class MessageItemAttributesFactory @Inject constructor( reactionPillCallback = callback, avatarCallback = callback, readReceiptsCallback = callback, - emojiTypeFace = emojiCompatFontProvider.typeface + emojiTypeFace = emojiCompatFontProvider.typeface, + isRootThread = isRootThread ) } } 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 b53495fdaf7..f6672a1d7ca 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 @@ -98,6 +98,7 @@ abstract class AbsMessageItem : AbsBaseMessageItem // Render send state indicator holder.sendStateImageView.render(attributes.informationData.sendStateDecoration) holder.eventSendingIndicator.isVisible = attributes.informationData.sendStateDecoration == SendStateDecoration.SENDING_MEDIA + holder.isThread.isVisible = attributes.isRootThread } override fun unbind(holder: H) { @@ -117,6 +118,7 @@ abstract class AbsMessageItem : AbsBaseMessageItem val timeView by bind(R.id.messageTimeView) val sendStateImageView by bind(R.id.messageSendStateImageView) val eventSendingIndicator by bind(R.id.eventSendingIndicator) + val isThread by bind(R.id.messageIsThread) } /** @@ -133,7 +135,8 @@ abstract class AbsMessageItem : AbsBaseMessageItem override val reactionPillCallback: TimelineEventController.ReactionPillCallback? = null, val avatarCallback: TimelineEventController.AvatarCallback? = null, override val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null, - val emojiTypeFace: Typeface? = null + val emojiTypeFace: Typeface? = null, + val isRootThread: Boolean = false ) : AbsBaseMessageItem.Attributes { // Have to override as it's used to diff epoxy items diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/detail/RoomThreadDetailActivity.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/detail/RoomThreadDetailActivity.kt index b8a58e178d7..c82fa353e4f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/detail/RoomThreadDetailActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/detail/RoomThreadDetailActivity.kt @@ -25,6 +25,8 @@ import im.vector.app.core.extensions.replaceFragment import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.databinding.ActivityRoomThreadDetailBinding import im.vector.app.features.home.AvatarRenderer +import im.vector.app.features.home.room.detail.RoomDetailArgs +import im.vector.app.features.home.room.detail.RoomDetailFragment import im.vector.app.features.home.room.threads.detail.arguments.RoomThreadDetailArgs import org.matrix.android.sdk.api.util.MatrixItem import javax.inject.Inject @@ -65,7 +67,16 @@ class RoomThreadDetailActivity : VectorBaseActivity $eventId isThread: ${EventMapper.map(r).isThread()}") +// } +// } +//// views.testTextVeiwddasda.text = "${roomThreadDetailArgs.eventId} -- ${roomThreadDetailArgs.roomId}" } private fun initTextComposer(){ diff --git a/vector/src/main/res/layout/item_timeline_event_base.xml b/vector/src/main/res/layout/item_timeline_event_base.xml index cb6f701bb4f..f9d43148132 100644 --- a/vector/src/main/res/layout/item_timeline_event_base.xml +++ b/vector/src/main/res/layout/item_timeline_event_base.xml @@ -200,4 +200,17 @@ + + \ No newline at end of file From 8c539426e63628317ab27a27799a7bcfb24ed9bc Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Mon, 15 Nov 2021 19:17:13 +0200 Subject: [PATCH 006/581] - Thread Summary along with optimization - Create new thread & reply to thread --- build.gradle | 5 + matrix-sdk-android/build.gradle | 2 + .../sdk/api/session/events/model/Event.kt | 18 ++- .../sdk/api/session/room/timeline/Timeline.kt | 2 +- .../sdk/api/session/threads/ThreadDetails.kt | 26 ++++ .../database/RealmSessionStoreMigration.kt | 10 +- .../database/helper/ThreadEventsHelper.kt | 88 ++++++++++++++ .../internal/database/mapper/EventMapper.kt | 23 +++- .../internal/database/model/ChunkEntity.kt | 2 - .../internal/database/model/EventEntity.kt | 17 +-- .../database/query/ChunkEntityQueries.kt | 19 +-- .../database/query/EventEntityQueries.kt | 5 + .../query/TimelineEventEntityQueries.kt | 4 + .../session/room/timeline/DefaultTimeline.kt | 22 +++- .../session/room/timeline/PaginationTask.kt | 1 + .../room/timeline/TokenChunkEventPersistor.kt | 112 +++++++++++------- .../sync/handler/room/RoomSyncHandler.kt | 96 +++------------ vector/build.gradle | 3 + .../home/room/detail/RoomDetailFragment.kt | 9 +- .../home/room/detail/RoomDetailViewModel.kt | 3 +- .../home/room/detail/RoomDetailViewState.kt | 3 +- .../detail/composer/TextComposerViewModel.kt | 6 - .../detail/composer/TextComposerViewState.kt | 4 +- .../timeline/TimelineEventController.kt | 15 ++- .../timeline/action/MessageActionState.kt | 9 +- .../action/MessageActionsBottomSheet.kt | 5 +- .../action/MessageActionsViewModel.kt | 22 ++-- .../action/TimelineEventFragmentArgs.kt | 3 +- .../factory/MergedHeaderItemFactory.kt | 2 +- .../timeline/factory/MessageItemFactory.kt | 2 +- .../timeline/factory/TimelineItemFactory.kt | 10 +- .../factory/TimelineItemFactoryParams.kt | 3 + .../helper/MessageItemAttributesFactory.kt | 5 +- .../helper/TimelineEventVisibilityHelper.kt | 23 ++-- .../detail/timeline/item/AbsMessageItem.kt | 29 ++++- .../detail/timeline/merged/MergedTimelines.kt | 2 +- .../main/res/drawable/ic_reply_in_thread.xml | 32 ++--- .../main/res/drawable/ic_thread_summary.xml | 11 ++ .../res/layout/item_timeline_event_base.xml | 18 +-- .../res/layout/view_thread_room_summary.xml | 74 ++++++++++++ 40 files changed, 481 insertions(+), 264 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadDetails.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt create mode 100644 vector/src/main/res/drawable/ic_thread_summary.xml create mode 100644 vector/src/main/res/layout/view_thread_room_summary.xml diff --git a/build.gradle b/build.gradle index 93f3e17f34c..7bde8422b37 100644 --- a/build.gradle +++ b/build.gradle @@ -147,6 +147,11 @@ project(":diff-match-patch") { } } +// Global configurations across all modules +ext { + isThreadingEnabled = true +} + //project(":matrix-sdk-android") { // sonarqube { // properties { diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 43ca243ec5b..176edb97f6c 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -38,6 +38,8 @@ android { resValue "string", "git_sdk_revision_unix_date", "\"${gitRevisionUnixDate()}\"" resValue "string", "git_sdk_revision_date", "\"${gitRevisionDate()}\"" + // Indicates whether or not threading support is enabled + buildConfigField "Boolean", "THREADING_ENABLED", "${isThreadingEnabled}" defaultConfig { consumerProguardFiles 'proguard-rules.pro' } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt index 896d6b0e7ba..ccf98f7754f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt @@ -26,6 +26,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageType import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent import org.matrix.android.sdk.api.session.room.send.SendState +import org.matrix.android.sdk.api.session.threads.ThreadDetails import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent @@ -97,7 +98,7 @@ data class Event( var sendStateDetails: String? = null @Transient - var isRootThread: Boolean = false + var threadDetails: ThreadDetails? = null fun sendStateError(): MatrixError? { return sendStateDetails?.let { @@ -124,6 +125,7 @@ data class Event( it.mCryptoErrorReason = mCryptoErrorReason it.sendState = sendState it.ageLocalTs = ageLocalTs + it.threadDetails = threadDetails } } @@ -186,6 +188,16 @@ data class Event( return contentMap?.let { JSONObject(adapter.toJson(it)).toString(4) } } + fun getDecryptedMessageText(): String { + return getValueFromPayload(mxDecryptionResult?.payload).orEmpty() + } + + @Suppress("UNCHECKED_CAST") + private fun getValueFromPayload(payload: JsonDict?, key: String = "body"): String? { + val content = payload?.get("content") as? JsonDict + return content?.get(key) as? String + } + /** * Tells if the event is redacted */ @@ -218,7 +230,7 @@ data class Event( if (mCryptoError != other.mCryptoError) return false if (mCryptoErrorReason != other.mCryptoErrorReason) return false if (sendState != other.sendState) return false - + if (threadDetails != other.threadDetails) return false return true } @@ -237,6 +249,8 @@ data class Event( result = 31 * result + (mCryptoError?.hashCode() ?: 0) result = 31 * result + (mCryptoErrorReason?.hashCode() ?: 0) result = 31 * result + sendState.hashCode() + result = 31 * result + threadDetails.hashCode() + return result } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt index 06c88db8316..4c07250e4c3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt @@ -43,7 +43,7 @@ interface Timeline { /** * This must be called before any other method after creating the timeline. It ensures the underlying database is open */ - fun start() + fun start(rootThreadEventId: String? = null) /** * This must be called when you don't need the timeline. It ensures the underlying database get closed. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadDetails.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadDetails.kt new file mode 100644 index 00000000000..04dbb18797e --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadDetails.kt @@ -0,0 +1,26 @@ +/* + * Copyright 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.api.session.threads + +import org.matrix.android.sdk.api.session.room.sender.SenderInfo + +data class ThreadDetails( + val isRootThread: Boolean = false, + val numberOfThreads: Int = 0, + val threadSummarySenderInfo: SenderInfo? = null, + val threadSummaryLatestTextMessage: String? = null +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt index 96c32ea08fc..111fc50e564 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt @@ -369,10 +369,12 @@ internal object RealmSessionStoreMigration : RealmMigration { private fun migrateTo19(realm: DynamicRealm) { Timber.d("Step 18 -> 19") + val eventEntity = realm.schema.get("TimelineEventEntity") ?: return + realm.schema.get("EventEntity") - ?.addField(EventEntityFields.IS_THREAD, Boolean::class.java, FieldAttribute.INDEXED) - ?.addField(EventEntityFields.ROOT_THREAD_EVENT_ID, String::class.java) - realm.schema.get("ChunkEntity") - ?.addField(ChunkEntityFields.ROOT_THREAD_EVENT_ID, String::class.java, FieldAttribute.INDEXED) + ?.addField(EventEntityFields.IS_ROOT_THREAD, Boolean::class.java, FieldAttribute.INDEXED) + ?.addField(EventEntityFields.ROOT_THREAD_EVENT_ID, String::class.java, FieldAttribute.INDEXED) + ?.addField(EventEntityFields.NUMBER_OF_THREADS, Int::class.java) + ?.addRealmObjectField(EventEntityFields.THREAD_SUMMARY_LATEST_MESSAGE.`$`, eventEntity) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt new file mode 100644 index 00000000000..597e08e3072 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt @@ -0,0 +1,88 @@ +/* + * Copyright 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.database.helper + +import io.realm.Realm +import io.realm.RealmResults +import io.realm.Sort +import org.matrix.android.sdk.BuildConfig +import org.matrix.android.sdk.internal.database.model.EventEntity +import org.matrix.android.sdk.internal.database.model.TimelineEventEntity +import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields +import org.matrix.android.sdk.internal.database.query.where +import org.matrix.android.sdk.internal.database.query.whereRoomId + +/** + * Finds the root thread event and update it with the latest message summary along with the number + * of threads included. If there is no root thread event no action is done + */ +internal fun Map.updateThreadSummaryIfNeeded() { + + if (!BuildConfig.THREADING_ENABLED) return + + for ((rootThreadEventId, eventEntity) in this) { + + eventEntity.findAllThreadsForRootEventId(eventEntity.realm, rootThreadEventId).let { + + if (it.isNullOrEmpty()) return@let + + val latestMessage = it.firstOrNull() + + // If this is a thread message, find its root event if exists + val rootThreadEvent = if (eventEntity.isThread()) eventEntity.findRootThreadEvent() else eventEntity + + rootThreadEvent?.markEventAsRoot( + threadsCounted = it.size, + latestMessageTimelineEventEntity = latestMessage + ) + } + } +} + +/** + * Finds the root event of the the current thread event message. + * Returns the EventEntity or null if the root event do not exist + */ +internal fun EventEntity.findRootThreadEvent(): EventEntity? = + rootThreadEventId?.let { + EventEntity + .where(realm, it) + .findFirst() + } + +/** + * Mark or update the current event a root thread event + */ +internal fun EventEntity.markEventAsRoot(threadsCounted: Int, + latestMessageTimelineEventEntity: TimelineEventEntity?) { + isRootThread = true + numberOfThreads = threadsCounted + threadSummaryLatestMessage = latestMessageTimelineEventEntity +} + +/** + * Find all TimelineEventEntity that are threads bind to the Event with rootThreadEventId + * @param rootThreadEventId The root eventId that will try to find bind threads + */ +internal fun EventEntity.findAllThreadsForRootEventId(realm: Realm, rootThreadEventId: String): RealmResults = + TimelineEventEntity + .whereRoomId(realm, roomId = roomId) + .equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, rootThreadEventId) + .sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING).findAll() + + + diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt index 21a93ba9045..de4be16493b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt @@ -24,6 +24,9 @@ import org.matrix.android.sdk.api.session.events.model.UnsignedData import org.matrix.android.sdk.api.session.events.model.getRootThreadEventId import org.matrix.android.sdk.api.session.events.model.isThread import org.matrix.android.sdk.api.session.room.send.SendState +import org.matrix.android.sdk.api.session.room.sender.SenderInfo +import org.matrix.android.sdk.api.session.threads.ThreadDetails +import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult import org.matrix.android.sdk.internal.database.model.EventEntity import org.matrix.android.sdk.internal.di.MoshiProvider @@ -41,8 +44,6 @@ internal object EventMapper { eventEntity.isUseless = IsUselessResolver.isUseless(event) eventEntity.stateKey = event.stateKey eventEntity.type = event.type ?: EventType.MISSING_TYPE - eventEntity.isThread = if(event.isRootThread) true else event.isThread() - eventEntity.rootThreadEventId = if(event.isRootThread) null else event.getRootThreadEventId() eventEntity.sender = event.senderId eventEntity.originServerTs = event.originServerTs eventEntity.redacts = event.redacts @@ -55,6 +56,9 @@ internal object EventMapper { } eventEntity.decryptionErrorReason = event.mCryptoErrorReason eventEntity.decryptionErrorCode = event.mCryptoError?.name + eventEntity.isRootThread = event.threadDetails?.isRootThread ?: false + eventEntity.rootThreadEventId = event.getRootThreadEventId() + eventEntity.numberOfThreads = event.threadDetails?.numberOfThreads ?: 0 return eventEntity } @@ -97,7 +101,20 @@ internal object EventMapper { MXCryptoError.ErrorType.valueOf(errorCode) } it.mCryptoErrorReason = eventEntity.decryptionErrorReason - it.isRootThread = eventEntity.isRootThread() + + it.threadDetails = ThreadDetails( + isRootThread = eventEntity.isRootThread, + numberOfThreads = eventEntity.numberOfThreads, + threadSummarySenderInfo = eventEntity.threadSummaryLatestMessage?.let { timelineEventEntity -> + SenderInfo( + userId = timelineEventEntity.root?.sender ?: "", + displayName = timelineEventEntity.senderName, + isUniqueDisplayName = timelineEventEntity.isUniqueDisplayName, + avatarUrl = timelineEventEntity.senderAvatar + ) + }, + threadSummaryLatestTextMessage = eventEntity.threadSummaryLatestMessage?.root?.asDomain()?.getDecryptedMessageText().orEmpty() + ) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt index 2b763dd941e..0b9a1ee8cce 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt @@ -27,7 +27,6 @@ import org.matrix.android.sdk.internal.extensions.clearWith internal open class ChunkEntity(@Index var prevToken: String? = null, // Because of gaps we can have several chunks with nextToken == null @Index var nextToken: String? = null, - @Index var rootThreadEventId: String? = null, var stateEvents: RealmList = RealmList(), var timelineEvents: RealmList = RealmList(), var numberOfTimelineEvents: Long = 0, @@ -46,7 +45,6 @@ internal open class ChunkEntity(@Index var prevToken: String? = null, companion object - fun isThreadChunk() = rootThreadEventId != null } internal fun ChunkEntity.deleteOnCascade(deleteStateEvents: Boolean, canDeleteRoot: Boolean) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt index ad889dd3528..1898d63af85 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt @@ -27,15 +27,13 @@ import org.matrix.android.sdk.internal.extensions.assertIsManaged internal open class EventEntity(@Index var eventId: String = "", @Index var roomId: String = "", @Index var type: String = "", - @Index var isThread: Boolean = false, - var rootThreadEventId: String? = null, var content: String? = null, var prevContent: String? = null, var isUseless: Boolean = false, @Index var stateKey: String? = null, var originServerTs: Long? = null, @Index var sender: String? = null, - // Can contain a serialized MatrixError + // Can contain a serialized MatrixError var sendStateDetails: String? = null, var age: Long? = 0, var unsignedData: String? = null, @@ -43,7 +41,13 @@ internal open class EventEntity(@Index var eventId: String = "", var decryptionResultJson: String? = null, var decryptionErrorCode: String? = null, var decryptionErrorReason: String? = null, - var ageLocalTs: Long? = null + var ageLocalTs: Long? = null, + // Thread related, no need to create a new Entity for performance + @Index var isRootThread: Boolean = false, + @Index var rootThreadEventId: String? = null, + var numberOfThreads: Int = 0, + var threadSummaryLatestMessage: TimelineEventEntity? = null + ) : RealmObject() { private var sendStateStr: String = SendState.UNKNOWN.name @@ -78,9 +82,6 @@ internal open class EventEntity(@Index var eventId: String = "", ?.canBeProcessed = true } - /** - * Returns true if the current event is a thread root event - */ - fun isRootThread(): Boolean = isThread && rootThreadEventId == null + fun isThread(): Boolean = rootThreadEventId != null } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ChunkEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ChunkEntityQueries.kt index 6018305c39d..2261d9786ac 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ChunkEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ChunkEntityQueries.kt @@ -33,11 +33,9 @@ internal fun ChunkEntity.Companion.find(realm: Realm, roomId: String, prevToken: val query = where(realm, roomId) if (prevToken != null) { query.equalTo(ChunkEntityFields.PREV_TOKEN, prevToken) - query.isNull(ChunkEntityFields.ROOT_THREAD_EVENT_ID) } if (nextToken != null) { query.equalTo(ChunkEntityFields.NEXT_TOKEN, nextToken) - query.isNull(ChunkEntityFields.ROOT_THREAD_EVENT_ID) } return query.findFirst() } @@ -45,15 +43,15 @@ internal fun ChunkEntity.Companion.find(realm: Realm, roomId: String, prevToken: internal fun ChunkEntity.Companion.findLastForwardChunkOfRoom(realm: Realm, roomId: String): ChunkEntity? { return where(realm, roomId) .equalTo(ChunkEntityFields.IS_LAST_FORWARD, true) - .isNull(ChunkEntityFields.ROOT_THREAD_EVENT_ID) .findFirst() } + + internal fun ChunkEntity.Companion.findAllIncludingEvents(realm: Realm, eventIds: List): RealmResults { return realm.where() .`in`(ChunkEntityFields.TIMELINE_EVENTS.EVENT_ID, eventIds.toTypedArray()) - .isNull(ChunkEntityFields.ROOT_THREAD_EVENT_ID) .findAll() } @@ -72,16 +70,3 @@ internal fun ChunkEntity.Companion.create( this.nextToken = nextToken } } - -// Threads -internal fun ChunkEntity.Companion.findThreadChunkOfRoom(realm: Realm, roomId: String, rootThreadEventId: String): ChunkEntity? { - return where(realm, roomId) - .equalTo(ChunkEntityFields.ROOT_THREAD_EVENT_ID, rootThreadEventId) - .findFirst() -} - -internal fun ChunkEntity.Companion.findAllThreadChunkOfRoom(realm: Realm, roomId: String): RealmResults { - return where(realm, roomId) - .isNotNull(ChunkEntityFields.ROOT_THREAD_EVENT_ID) - .findAll() -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt index 240b2a06911..e27130442dd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt @@ -85,3 +85,8 @@ internal fun RealmList.find(eventId: String): EventEntity? { internal fun RealmList.fastContains(eventId: String): Boolean { return this.find(eventId) != null } + +internal fun EventEntity.Companion.whereRootThreadEventId(realm: Realm, rootThreadEventId: String): RealmQuery { + return realm.where() + .equalTo(EventEntityFields.ROOT_THREAD_EVENT_ID, rootThreadEventId) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt index aa1ce41bb7c..9ce59904b4d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt @@ -25,9 +25,11 @@ import io.realm.kotlin.where import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.timeline.TimelineEventFilters import org.matrix.android.sdk.internal.database.model.ChunkEntity +import org.matrix.android.sdk.internal.database.model.ChunkEntityFields import org.matrix.android.sdk.internal.database.model.RoomEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields +import timber.log.Timber internal fun TimelineEventEntity.Companion.where(realm: Realm, roomId: String, eventId: String): RealmQuery { return realm.where() @@ -59,6 +61,7 @@ internal fun TimelineEventEntity.Companion.latestEvent(realm: Realm, filters: TimelineEventFilters = TimelineEventFilters()): TimelineEventEntity? { val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: return null val sendingTimelineEvents = roomEntity.sendingTimelineEvents.where().filterEvents(filters) + val liveEvents = ChunkEntity.findLastForwardChunkOfRoom(realm, roomId)?.timelineEvents?.where()?.filterEvents(filters) val query = if (includesSending && sendingTimelineEvents.findAll().isNotEmpty()) { sendingTimelineEvents @@ -100,6 +103,7 @@ internal fun RealmQuery.filterEvents(filters: TimelineEvent if (filters.filterRedacted) { not().like(TimelineEventEntityFields.ROOT.UNSIGNED_DATA, TimelineEventFilter.Unsigned.REDACTED) } + return this } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt index 0c917448cc4..a8a72d8a527 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt @@ -101,7 +101,7 @@ internal class DefaultTimeline( private val builtEventsIdMap = Collections.synchronizedMap(HashMap()) private val backwardsState = AtomicReference(TimelineState()) private val forwardsState = AtomicReference(TimelineState()) - + private var isFromThreadTimeline = false override val timelineID = UUID.randomUUID().toString() override val isLive @@ -143,8 +143,9 @@ internal class DefaultTimeline( } } - override fun start() { + override fun start(rootThreadEventId: String?) { if (isStarted.compareAndSet(false, true)) { + isFromThreadTimeline = rootThreadEventId != null Timber.v("Start timeline for roomId: $roomId and eventId: $initialEventId") timelineInput.listeners.add(this) BACKGROUND_HANDLER.post { @@ -163,7 +164,13 @@ internal class DefaultTimeline( postSnapshot() } - timelineEvents = buildEventQuery(realm).sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING).findAll() + timelineEvents = rootThreadEventId?.let { + TimelineEventEntity + .whereRoomId(realm, roomId = roomId) + .equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, it) + .sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING).findAll() + } ?: buildEventQuery(realm).sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING).findAll() + timelineEvents.addChangeListener(eventsChangeListener) handleInitialLoad() loadRoomMembersTask @@ -313,16 +320,18 @@ internal class DefaultTimeline( val firstCacheEvent = results.firstOrNull() val chunkEntity = getLiveChunk() + updateState(Timeline.Direction.FORWARDS) { it.copy( - hasMoreInCache = !builtEventsIdMap.containsKey(firstCacheEvent?.eventId), - hasReachedEnd = chunkEntity?.isLastForward ?: false + hasMoreInCache = !builtEventsIdMap.containsKey(firstCacheEvent?.eventId), // what is in DB + hasReachedEnd = if (isFromThreadTimeline) true else chunkEntity?.isLastForward ?: false // if you neeed fetch more ) } updateState(Timeline.Direction.BACKWARDS) { + it.copy( hasMoreInCache = !builtEventsIdMap.containsKey(lastCacheEvent?.eventId), - hasReachedEnd = chunkEntity?.isLastBackward ?: false || lastCacheEvent?.root?.type == EventType.STATE_ROOM_CREATE + hasReachedEnd = if (isFromThreadTimeline) true else chunkEntity?.isLastBackward ?: false || lastCacheEvent?.root?.type == EventType.STATE_ROOM_CREATE ) } } @@ -472,6 +481,7 @@ internal class DefaultTimeline( * This has to be called on TimelineThread as it accesses realm live results */ private fun executePaginationTask(direction: Timeline.Direction, limit: Int) { + val currentChunk = getLiveChunk() val token = if (direction == Timeline.Direction.BACKWARDS) currentChunk?.prevToken else currentChunk?.nextToken if (token == null) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/PaginationTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/PaginationTask.kt index 8aeccb66c89..cb23061edad 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/PaginationTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/PaginationTask.kt @@ -21,6 +21,7 @@ import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.filter.FilterRepository import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.task.Task +import timber.log.Timber import javax.inject.Inject internal interface PaginationTask : Task { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt index 7b873166b47..ba34e88ff7b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt @@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.session.room.timeline import com.zhuinden.monarchy.Monarchy import io.realm.Realm import io.realm.kotlin.createObject +import org.matrix.android.sdk.BuildConfig import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.isThread import org.matrix.android.sdk.api.session.events.model.toModel @@ -28,10 +29,10 @@ import org.matrix.android.sdk.internal.database.helper.addIfNecessary import org.matrix.android.sdk.internal.database.helper.addStateEvent import org.matrix.android.sdk.internal.database.helper.addTimelineEvent import org.matrix.android.sdk.internal.database.helper.merge +import org.matrix.android.sdk.internal.database.helper.updateThreadSummaryIfNeeded import org.matrix.android.sdk.internal.database.mapper.toEntity import org.matrix.android.sdk.internal.database.model.ChunkEntity import org.matrix.android.sdk.internal.database.model.EventEntity -import org.matrix.android.sdk.internal.database.model.EventEntityFields import org.matrix.android.sdk.internal.database.model.EventInsertType import org.matrix.android.sdk.internal.database.model.RoomEntity import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity @@ -41,9 +42,9 @@ import org.matrix.android.sdk.internal.database.query.create import org.matrix.android.sdk.internal.database.query.find import org.matrix.android.sdk.internal.database.query.findAllIncludingEvents import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfRoom -import org.matrix.android.sdk.internal.database.query.findThreadChunkOfRoom import org.matrix.android.sdk.internal.database.query.getOrCreate import org.matrix.android.sdk.internal.database.query.where +import org.matrix.android.sdk.internal.database.query.whereRootThreadEventId import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryEventsHelper import org.matrix.android.sdk.internal.util.awaitTransaction @@ -160,6 +161,7 @@ internal class TokenChunkEventPersistor @Inject constructor(@SessionDatabase pri handlePagination(realm, roomId, direction, receivedChunk, currentChunk) } } + return if (receivedChunk.events.isEmpty()) { if (receivedChunk.hasMore()) { Result.SHOULD_FETCH_MORE @@ -210,6 +212,8 @@ internal class TokenChunkEventPersistor @Inject constructor(@SessionDatabase pri } } val eventIds = ArrayList(eventList.size) + + val optimizedThreadSummaryMap = hashMapOf() eventList.forEach { event -> if (event.eventId == null || event.senderId == null) { return@forEach @@ -226,16 +230,18 @@ internal class TokenChunkEventPersistor @Inject constructor(@SessionDatabase pri roomMemberContentsByUser[event.stateKey] = contentToUse.toModel() } - Timber.i("------> [TokenChunkEventPersistor] Add TimelineEvent to chunkEntity event[${event.eventId}] ${if (event.isThread()) "is Thread" else ""}") + currentChunk.addTimelineEvent(roomId, eventEntity, direction, roomMemberContentsByUser) + + eventEntity.rootThreadEventId?.let { + // This is a thread event + optimizedThreadSummaryMap[it] = eventEntity + } ?: run { + // This is a normal event or a root thread one + optimizedThreadSummaryMap[eventEntity.eventId] = eventEntity + } - addTimelineEventToChunk( - realm = realm, - roomId = roomId, - eventEntity = eventEntity, - currentChunk = currentChunk, - direction = direction, - roomMemberContentsByUser = roomMemberContentsByUser) } + // Find all the chunks which contain at least one event from the list of eventIds val chunks = ChunkEntity.findAllIncludingEvents(realm, eventIds) Timber.d("Found ${chunks.size} chunks containing at least one of the eventIds") @@ -254,49 +260,63 @@ internal class TokenChunkEventPersistor @Inject constructor(@SessionDatabase pri val shouldUpdateSummary = roomSummaryEntity.latestPreviewableEvent == null || (chunksToDelete.isNotEmpty() && currentChunk.isLastForward && direction == PaginationDirection.FORWARDS) if (shouldUpdateSummary) { + // TODO maybe add support to view latest thread message roomSummaryEntity.latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId) } if (currentChunk.isValid) { RoomEntity.where(realm, roomId).findFirst()?.addIfNecessary(currentChunk) } - } - /** - * Adds a timeline event to the correct chunk. If there is a thread detected will be added - * to a specific chunk - */ - private fun addTimelineEventToChunk(realm: Realm, - roomId: String, - eventEntity: EventEntity, - currentChunk: ChunkEntity, - direction: PaginationDirection, - roomMemberContentsByUser: Map) { - val rootThreadEventId = eventEntity.rootThreadEventId - if (eventEntity.isThread && rootThreadEventId != null) { - val threadChunk = getOrCreateThreadChunk(realm, roomId, rootThreadEventId) - threadChunk.addTimelineEvent(roomId, eventEntity, direction, roomMemberContentsByUser) - markEventAsRootEvent(realm, rootThreadEventId) - if (threadChunk.isValid) - RoomEntity.where(realm, roomId).findFirst()?.addIfNecessary(threadChunk) - } else { - currentChunk.addTimelineEvent(roomId, eventEntity, direction, roomMemberContentsByUser) - } - } + optimizedThreadSummaryMap.updateThreadSummaryIfNeeded() - private fun markEventAsRootEvent(realm: Realm, rootThreadEventId: String) { - val rootThreadEvent = EventEntity - .where(realm, rootThreadEventId) - .equalTo(EventEntityFields.IS_THREAD, false).findFirst() ?: return - rootThreadEvent.isThread = true } - /** - * Returns the chunk for the current room if exists, otherwise it creates a new ChunkEntity - */ - private fun getOrCreateThreadChunk(realm: Realm, roomId: String, rootThreadEventId: String): ChunkEntity { - return ChunkEntity.findThreadChunkOfRoom(realm, roomId, rootThreadEventId) - ?: realm.createObject().apply { - this.rootThreadEventId = rootThreadEventId - } - } +// /** +// * Mark or update the thread root event accordingly. If the Threading is disabled +// * no action is done +// */ +// private fun updateRootThreadEventIfNeeded(realm: Realm, eventEntity: EventEntity) { +// +// if (!BuildConfig.THREADING_ENABLED) return +// +// val rootThreadEventId = eventEntity.rootThreadEventId +// +// if (eventEntity.isThread && rootThreadEventId != null) { +// markEventAsRootEvent(realm, rootThreadEventId) +// } else { +// markAsRootEventIfNeeded(realm, eventEntity.eventId) +// } +// } + +// /** +// * Finds the event with rootThreadEventId and marks it as a root thread +// */ +// private fun markEventAsRootEvent(realm: Realm, rootThreadEventId: String) { +// val rootThreadEvent = EventEntity +// .where(realm, rootThreadEventId) +// .equalTo(EventEntityFields.IS_THREAD, false).findFirst() ?: return +// rootThreadEvent.isThread = true +// } +// +// /** +// * Also check if there is at least one thread message for that rootThreadEventId, +// * that means it is a root thread so it should be updated accordingly +// */ +// private fun markAsRootEventIfNeeded(realm: Realm, candidateIdRootThread: String) { +// EventEntity +// .whereRootThreadEventId(realm, candidateIdRootThread) +// .findFirst() ?: return +// +// markEventAsRootEvent(realm, candidateIdRootThread) +// } + +// /** +// * Returns the chunk for the current room if exists, otherwise it creates a new ChunkEntity +// */ +// private fun getOrCreateThreadChunk(realm: Realm, roomId: String, rootThreadEventId: String): ChunkEntity { +// return ChunkEntity.findThreadChunkOfRoom(realm, roomId, rootThreadEventId) +// ?: realm.createObject().apply { +// this.rootThreadEventId = rootThreadEventId +// } +// } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt index ea5aedeee92..8d64c7fc969 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt @@ -21,41 +21,33 @@ import io.realm.kotlin.createObject import org.matrix.android.sdk.api.session.crypto.MXCryptoError 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.isThread import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.initsync.InitSyncStep import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomMemberContent -import org.matrix.android.sdk.api.session.room.model.message.MessageRelationContent import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.sync.model.InvitedRoomSync import org.matrix.android.sdk.api.session.sync.model.LazyRoomSyncEphemeral import org.matrix.android.sdk.api.session.sync.model.RoomSync import org.matrix.android.sdk.api.session.sync.model.RoomsSyncResponse -import org.matrix.android.sdk.api.util.JsonDict -import org.matrix.android.sdk.internal.crypto.CryptoSessionInfoProvider import org.matrix.android.sdk.internal.crypto.DefaultCryptoService import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult import org.matrix.android.sdk.internal.database.helper.addIfNecessary import org.matrix.android.sdk.internal.database.helper.addTimelineEvent -import org.matrix.android.sdk.internal.database.mapper.EventMapper +import org.matrix.android.sdk.internal.database.helper.updateThreadSummaryIfNeeded import org.matrix.android.sdk.internal.database.mapper.asDomain import org.matrix.android.sdk.internal.database.mapper.toEntity import org.matrix.android.sdk.internal.database.model.ChunkEntity import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity import org.matrix.android.sdk.internal.database.model.EventEntity -import org.matrix.android.sdk.internal.database.model.EventEntityFields import org.matrix.android.sdk.internal.database.model.EventInsertType import org.matrix.android.sdk.internal.database.model.RoomEntity import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity -import org.matrix.android.sdk.internal.database.model.TimelineEventEntity import org.matrix.android.sdk.internal.database.model.deleteOnCascade import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore import org.matrix.android.sdk.internal.database.query.find -import org.matrix.android.sdk.internal.database.query.findAllThreadChunkOfRoom import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfRoom -import org.matrix.android.sdk.internal.database.query.findThreadChunkOfRoom import org.matrix.android.sdk.internal.database.query.getOrCreate import org.matrix.android.sdk.internal.database.query.getOrNull import org.matrix.android.sdk.internal.database.query.where @@ -68,11 +60,8 @@ import org.matrix.android.sdk.internal.session.initsync.mapWithProgress import org.matrix.android.sdk.internal.session.initsync.reportSubtask import org.matrix.android.sdk.internal.session.room.membership.RoomChangeMembershipStateDataSource import org.matrix.android.sdk.internal.session.room.membership.RoomMemberEventHandler -import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory -import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryUpdater import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection -import org.matrix.android.sdk.internal.session.room.timeline.TimelineEventDecryptor import org.matrix.android.sdk.internal.session.room.timeline.TimelineInput import org.matrix.android.sdk.internal.session.room.typing.TypingEventContent import org.matrix.android.sdk.internal.session.sync.InitialSyncStrategy @@ -357,11 +346,14 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle syncLocalTimestampMillis: Long, aggregator: SyncResponsePostTreatmentAggregator): ChunkEntity { val lastChunk = ChunkEntity.findLastForwardChunkOfRoom(realm, roomEntity.roomId) + val chunkEntity = if (!isLimited && lastChunk != null) { + // There are no more events to fetch lastChunk } else { realm.createObject().apply { this.prevToken = prevToken } } + // Only one chunk has isLastForward set to true lastChunk?.isLastForward = false chunkEntity.isLastForward = true @@ -369,21 +361,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle val eventIds = ArrayList(eventList.size) val roomMemberContentsByUser = HashMap() -///////////////////// - // There is only one chunk per room - - val threadChunks = ChunkEntity.findAllThreadChunkOfRoom(realm, roomId) - - val tc = threadChunks.joinToString { chunk -> - var output = "\n----------------\n------> [${chunk.timelineEvents.size}] rootThreadEventId = ${chunk.rootThreadEventId}" + "\n" - output += chunk.timelineEvents - .joinToString("") { - "------> " + "eventId:[${it?.eventId}] payload:[${getValueFromPayload(it.root?.let { root -> EventMapper.map(root).mxDecryptionResult }?.payload, "body")}]\n" - } - output - } - Timber.i("------> Chunks (${threadChunks.size})$tc") -///////////////////// + val optimizedThreadSummaryMap = hashMapOf() for (event in eventList) { if (event.eventId == null || event.senderId == null || event.type == null) { continue @@ -413,15 +391,14 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle rootStateEvent?.asDomain()?.getFixedRoomMemberContent() } - Timber.i("------> [RoomSyncHandler] Add TimelineEvent to chunkEntity event[${event.eventId}] ${if (event.isThread()) "is Thread" else ""}") - - addTimelineEventToChunk( - realm = realm, - roomId = roomId, - eventEntity = eventEntity, - chunkEntity = chunkEntity, - roomEntity = roomEntity, - roomMemberContentsByUser = roomMemberContentsByUser) + chunkEntity.addTimelineEvent(roomId, eventEntity, PaginationDirection.FORWARDS, roomMemberContentsByUser) + eventEntity.rootThreadEventId?.let { + // This is a thread event + optimizedThreadSummaryMap[it] = eventEntity + } ?: run { + // This is a normal event or a root thread one + optimizedThreadSummaryMap[eventEntity.eventId] = eventEntity + } // Give info to crypto module cryptoService.onLiveEvent(roomEntity.roomId, event) @@ -447,56 +424,15 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle } } } + + optimizedThreadSummaryMap.updateThreadSummaryIfNeeded() + // posting new events to timeline if any is registered timelineInput.onNewTimelineEvents(roomId = roomId, eventIds = eventIds) return chunkEntity } - /** - * Adds a timeline event to the correct chunk. If there is a thread detected will be added - * to a specific chunk - */ - private fun addTimelineEventToChunk(realm: Realm, - roomId: String, - eventEntity: EventEntity, - chunkEntity: ChunkEntity, - roomEntity: RoomEntity, - roomMemberContentsByUser: Map) { - val rootThreadEventId = eventEntity.rootThreadEventId - if (eventEntity.isThread && rootThreadEventId != null) { - val threadChunk = getOrCreateThreadChunk(realm, roomId, rootThreadEventId) - threadChunk.addTimelineEvent(roomId, eventEntity, PaginationDirection.FORWARDS, roomMemberContentsByUser) - markEventAsRootEvent(realm, rootThreadEventId) - roomEntity.addIfNecessary(threadChunk) - } else { - chunkEntity.addTimelineEvent(roomId, eventEntity, PaginationDirection.FORWARDS, roomMemberContentsByUser) - } - } - - @Suppress("UNCHECKED_CAST") - private fun getValueFromPayload(payload: JsonDict?, key: String): String? { - val content = payload?.get("content") as? JsonDict - return content?.get(key) as? String - } - - /** - * Returns the chunk for the current room if exists, otherwise it creates a new ChunkEntity - */ - private fun getOrCreateThreadChunk(realm: Realm, roomId: String, rootThreadEventId: String): ChunkEntity { - return ChunkEntity.findThreadChunkOfRoom(realm, roomId, rootThreadEventId) - ?: realm.createObject().apply { - this.rootThreadEventId = rootThreadEventId - } - } - - private fun markEventAsRootEvent(realm: Realm, rootThreadEventId: String){ - val rootThreadEvent = EventEntity - .where(realm, rootThreadEventId) - .equalTo(EventEntityFields.IS_THREAD, false).findFirst() ?: return - rootThreadEvent.isThread = true - } - private fun decryptIfNeeded(event: Event, roomId: String) { try { // Event from sync does not have roomId, so add it to the event first diff --git a/vector/build.gradle b/vector/build.gradle index d06779d61c2..7fae950d04f 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -159,6 +159,9 @@ android { // This *must* only be set in trusted environments. buildConfigField "Boolean", "handleCallAssertedIdentityEvents", "false" + // Indicates whether or not threading support is enabled + buildConfigField "Boolean", "THREADING_ENABLED", "${isThreadingEnabled}" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" // Keep abiFilter for the universalApk diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index bbccc78c9ca..ecc96e4be89 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -359,7 +359,6 @@ class RoomDetailFragment @Inject constructor( } else { setupToolbar(views.roomToolbar) } - setupThreadIfNeeded() setupRecyclerView() setupComposer() setupNotificationView() @@ -1194,12 +1193,6 @@ class RoomDetailFragment @Inject constructor( // PRIVATE METHODS ***************************************************************************** - private fun setupThreadIfNeeded(){ - getRootThreadEventId()?.let{ - textComposerViewModel.handle(TextComposerAction.EnterReplyInThreadTimeline(it)) - } - } - private fun setupRecyclerView() { timelineEventController.callback = this timelineEventController.timeline = roomDetailViewModel.timeline @@ -1762,7 +1755,7 @@ class RoomDetailFragment @Inject constructor( this.view?.hideKeyboard() MessageActionsBottomSheet - .newInstance(roomId, informationData) + .newInstance(roomId, informationData, isThreadTimeLine()) .show(requireActivity().supportFragmentManager, "MESSAGE_CONTEXTUAL_ACTIONS") return true diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index b9622ae9ae4..28b5e88a820 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -160,7 +160,7 @@ class RoomDetailViewModel @AssistedInject constructor( } init { - timeline.start() + timeline.start(initialState.rootThreadEventId) timeline.addListener(this) observeRoomSummary() observeMembershipChanges() @@ -1094,6 +1094,7 @@ class RoomDetailViewModel @AssistedInject constructor( } override fun onTimelineUpdated(snapshot: List) { + timelineEvents.tryEmit(snapshot) // PreviewUrl 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 3266ae60e44..1848f1b28e9 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 @@ -73,7 +73,8 @@ data class RoomDetailViewState( roomId = args.roomId, eventId = args.eventId, // Also highlight the target event, if any - highlightedEventId = args.eventId + highlightedEventId = args.eventId, + rootThreadEventId = args.roomThreadDetailArgs?.eventId ) fun isWebRTCCallOptionAvailable() = (asyncRoomSummary.invoke()?.joinedMembersCount ?: 0) <= 2 diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt index 1541d5738b9..158bc85cb1c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt @@ -89,8 +89,6 @@ class TextComposerViewModel @AssistedInject constructor( is TextComposerAction.UserIsTyping -> handleUserIsTyping(action) is TextComposerAction.OnTextChanged -> handleOnTextChanged(action) is TextComposerAction.OnVoiceRecordingStateChanged -> handleOnVoiceRecordingStateChanged(action) - is TextComposerAction.EnterReplyInThreadTimeline -> handleEnterReplyInThreadTimeline(action) - } } @@ -98,10 +96,6 @@ class TextComposerViewModel @AssistedInject constructor( copy(isVoiceRecording = action.isRecording) } - private fun handleEnterReplyInThreadTimeline(action: TextComposerAction.EnterReplyInThreadTimeline) = setState { - copy(rootThreadEventId = action.rootThreadEventId) - } - private fun handleOnTextChanged(action: TextComposerAction.OnTextChanged) { setState { // Makes sure currentComposerText is upToDate when accessing further setState diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewState.kt index 36bdc4f5b2a..f4dd5adebeb 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewState.kt @@ -53,7 +53,9 @@ data class TextComposerViewState( val isComposerVisible: Boolean get() = canSendMessage && !isVoiceRecording - constructor(args: RoomDetailArgs) : this(roomId = args.roomId) + constructor(args: RoomDetailArgs) : this( + roomId = args.roomId, + rootThreadEventId = args.roomThreadDetailArgs?.eventId) fun isInThreadTimeline(): Boolean = rootThreadEventId != null } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt index d320f0b6e0c..d08259d7392 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt @@ -93,14 +93,16 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec val unreadState: UnreadState = UnreadState.Unknown, val highlightedEventId: String? = null, val jitsiState: JitsiState = JitsiState(), - val roomSummary: RoomSummary? = null + val roomSummary: RoomSummary? = null, + val rootThreadEventId: String? = null ) { constructor(state: RoomDetailViewState) : this( unreadState = state.unreadState, highlightedEventId = state.highlightedEventId, jitsiState = state.jitsiState, - roomSummary = state.asyncRoomSummary() + roomSummary = state.asyncRoomSummary(), + rootThreadEventId = state.rootThreadEventId ) } @@ -191,7 +193,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec // it's sent by the same user so we are sure we have up to date information. val invalidatedSenderId: String? = currentSnapshot.getOrNull(position)?.senderInfo?.userId val prevDisplayableEventIndex = currentSnapshot.subList(0, position).indexOfLast { - timelineEventVisibilityHelper.shouldShowEvent(it, partialState.highlightedEventId) + timelineEventVisibilityHelper.shouldShowEvent(it, partialState.highlightedEventId, partialState.rootThreadEventId ) } if (prevDisplayableEventIndex != -1 && currentSnapshot[prevDisplayableEventIndex].senderInfo.userId == invalidatedSenderId) { modelCache[prevDisplayableEventIndex] = null @@ -319,6 +321,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec } private fun submitSnapshot(newSnapshot: List) { + // Update is triggered on any DB change backgroundHandler.post { inSubmitList = true val diffCallback = TimelineEventDiffUtilCallback(currentSnapshot, newSnapshot) @@ -367,7 +370,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec val nextEvent = currentSnapshot.nextOrNull(position) val prevEvent = currentSnapshot.prevOrNull(position) val nextDisplayableEvent = currentSnapshot.subList(position + 1, currentSnapshot.size).firstOrNull { - timelineEventVisibilityHelper.shouldShowEvent(it, partialState.highlightedEventId) + timelineEventVisibilityHelper.shouldShowEvent(it, partialState.highlightedEventId, partialState.rootThreadEventId) } // Should be build if not cached or if model should be refreshed if (modelCache[position] == null || modelCache[position]?.isCacheable == false) { @@ -449,7 +452,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec return null } // If the event is not shown, we go to the next one - if (!timelineEventVisibilityHelper.shouldShowEvent(event, partialState.highlightedEventId)) { + if (!timelineEventVisibilityHelper.shouldShowEvent(event, partialState.highlightedEventId, partialState.rootThreadEventId)) { continue } // If the event is sent by us, we update the holder with the eventId and stop the search @@ -471,7 +474,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec val currentReadReceipts = ArrayList(event.readReceipts).filter { it.user.userId != session.myUserId } - if (timelineEventVisibilityHelper.shouldShowEvent(event, partialState.highlightedEventId)) { + if (timelineEventVisibilityHelper.shouldShowEvent(event, partialState.highlightedEventId, partialState.rootThreadEventId)) { lastShownEventId = event.eventId } if (lastShownEventId == null) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionState.kt index c1c145040ed..0cf7e60eae5 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionState.kt @@ -49,10 +49,15 @@ data class MessageActionState( // For actions val actions: List = emptyList(), val expendedReportContentMenu: Boolean = false, - val actionPermissions: ActionPermissions = ActionPermissions() + val actionPermissions: ActionPermissions = ActionPermissions(), + val isFromThreadTimeline: Boolean = false ) : MavericksState { - constructor(args: TimelineEventFragmentArgs) : this(roomId = args.roomId, eventId = args.eventId, informationData = args.informationData) + constructor(args: TimelineEventFragmentArgs) : this( + roomId = args.roomId, + eventId = args.eventId, + informationData = args.informationData, + isFromThreadTimeline = args.isFromThreadTimeline) fun senderName(): String = informationData.memberName?.toString() ?: "" diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt index 6de8864f103..3aad4f1e7ee 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt @@ -97,13 +97,14 @@ class MessageActionsBottomSheet : } companion object { - fun newInstance(roomId: String, informationData: MessageInformationData): MessageActionsBottomSheet { + fun newInstance(roomId: String, informationData: MessageInformationData, isFromThreadTimeline: Boolean): MessageActionsBottomSheet { return MessageActionsBottomSheet().apply { setArguments( TimelineEventFragmentArgs( informationData.eventId, roomId, - informationData + informationData, + isFromThreadTimeline ) ) } 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 bdd51770588..6762ed1479f 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 @@ -22,6 +22,7 @@ import dagger.Lazy import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import im.vector.app.BuildConfig import im.vector.app.R import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.extensions.canReact @@ -326,7 +327,6 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted add(EventSharedAction.Reply(eventId)) } - // *** Testing Threads **** if (canReplyInThread(timelineEvent, messageContent, actionPermissions)) { add(EventSharedAction.ReplyInThread(eventId)) } @@ -417,18 +417,22 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted } } - private fun canReplyInThread(event: TimelineEvent, messageContent: MessageContent?, actionPermissions: ActionPermissions): Boolean { + private fun canReplyInThread(event: TimelineEvent, + messageContent: MessageContent?, + actionPermissions: ActionPermissions): Boolean { // Only event of type EventType.MESSAGE are supported for the moment + if (!BuildConfig.THREADING_ENABLED) return false + if (initialState.isFromThreadTimeline) return false if (event.root.getClearType() != EventType.MESSAGE) return false if (!actionPermissions.canSendMessage) return false return when (messageContent?.msgType) { - MessageType.MSGTYPE_TEXT, - MessageType.MSGTYPE_NOTICE, - MessageType.MSGTYPE_EMOTE, - MessageType.MSGTYPE_IMAGE, - MessageType.MSGTYPE_VIDEO, - MessageType.MSGTYPE_AUDIO, - MessageType.MSGTYPE_FILE -> true + MessageType.MSGTYPE_TEXT -> true +// MessageType.MSGTYPE_NOTICE, +// MessageType.MSGTYPE_EMOTE, +// MessageType.MSGTYPE_IMAGE, +// MessageType.MSGTYPE_VIDEO, +// MessageType.MSGTYPE_AUDIO, +// MessageType.MSGTYPE_FILE -> true else -> false } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/TimelineEventFragmentArgs.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/TimelineEventFragmentArgs.kt index 1bb1a876bdb..2bd3c54d525 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/TimelineEventFragmentArgs.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/TimelineEventFragmentArgs.kt @@ -24,5 +24,6 @@ import kotlinx.parcelize.Parcelize data class TimelineEventFragmentArgs( val eventId: String, val roomId: String, - val informationData: MessageInformationData + val informationData: MessageInformationData, + val isFromThreadTimeline: Boolean = false ) : Parcelable diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt index e378969b4a7..fa699f0c786 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt @@ -83,7 +83,7 @@ class MergedHeaderItemFactory @Inject constructor(private val activeSessionHolde eventIdToHighlight: String?, requestModelBuild: () -> Unit, callback: TimelineEventController.Callback?): MergedMembershipEventsItem_? { - val mergedEvents = timelineEventVisibilityHelper.prevSameTypeEvents(items, currentPosition, 2, eventIdToHighlight) + val mergedEvents = timelineEventVisibilityHelper.prevSameTypeEvents(items, currentPosition, 2, eventIdToHighlight, partialState.rootThreadEventId) return if (mergedEvents.isEmpty()) { null } else { 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 54cdb6db09f..0ee28404df4 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 @@ -149,7 +149,7 @@ class MessageItemFactory @Inject constructor( // This is an edit event, we should display it when debugging as a notice event return noticeItemFactory.create(params) } - val attributes = messageItemAttributesFactory.create(messageContent, informationData, callback, event.root.isRootThread) + val attributes = messageItemAttributesFactory.create(messageContent, informationData, callback, event.root.threadDetails) // val all = event.root.toContent() // val ev = all.toModel() 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 c21fe935bbf..96786e33770 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 @@ -42,8 +42,8 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me fun create(params: TimelineItemFactoryParams): VectorEpoxyModel<*> { val event = params.event val computedModel = try { - if (!timelineEventVisibilityHelper.shouldShowEvent(event, params.highlightedEventId)) { - return buildEmptyItem(event, params.prevEvent, params.highlightedEventId) + if (!timelineEventVisibilityHelper.shouldShowEvent(event, params.highlightedEventId, params.rootThreadEventId)) { + return buildEmptyItem(event, params.prevEvent, params.highlightedEventId, params.rootThreadEventId) } when (event.root.getClearType()) { // Message itemsX @@ -109,11 +109,11 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me Timber.e(throwable, "failed to create message item") defaultItemFactory.create(params, throwable) } - return computedModel ?: buildEmptyItem(event, params.prevEvent, params.highlightedEventId) + return computedModel ?: buildEmptyItem(event, params.prevEvent, params.highlightedEventId, params.rootThreadEventId) } - private fun buildEmptyItem(timelineEvent: TimelineEvent, prevEvent: TimelineEvent?, highlightedEventId: String?): TimelineEmptyItem { - val isNotBlank = prevEvent == null || timelineEventVisibilityHelper.shouldShowEvent(prevEvent, highlightedEventId) + private fun buildEmptyItem(timelineEvent: TimelineEvent, prevEvent: TimelineEvent?, highlightedEventId: String?, rootThreadEventId: String?): TimelineEmptyItem { + val isNotBlank = prevEvent == null || timelineEventVisibilityHelper.shouldShowEvent(prevEvent, highlightedEventId, rootThreadEventId) return TimelineEmptyItem_() .id(timelineEvent.localId) .eventId(timelineEvent.eventId) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactoryParams.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactoryParams.kt index cdfedb29250..94e94911c05 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactoryParams.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactoryParams.kt @@ -34,5 +34,8 @@ data class TimelineItemFactoryParams( val highlightedEventId: String? get() = partialState.highlightedEventId + val rootThreadEventId: String? + get() = partialState.rootThreadEventId + val isHighlighted = highlightedEventId == event.eventId } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt index 80b36fa69f1..11061cbc9ac 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt @@ -21,6 +21,7 @@ import im.vector.app.features.home.room.detail.timeline.MessageColorProvider import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData +import org.matrix.android.sdk.api.session.threads.ThreadDetails import javax.inject.Inject class MessageItemAttributesFactory @Inject constructor( @@ -32,7 +33,7 @@ class MessageItemAttributesFactory @Inject constructor( fun create(messageContent: Any?, informationData: MessageInformationData, callback: TimelineEventController.Callback?, - isRootThread: Boolean = false): AbsMessageItem.Attributes { + threadDetails: ThreadDetails? = null): AbsMessageItem.Attributes { return AbsMessageItem.Attributes( avatarSize = avatarSizeProvider.avatarSize, informationData = informationData, @@ -51,7 +52,7 @@ class MessageItemAttributesFactory @Inject constructor( avatarCallback = callback, readReceiptsCallback = callback, emojiTypeFace = emojiCompatFontProvider.typeface, - isRootThread = isRootThread + threadDetails = threadDetails ) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt index 580d7d18cf1..c56e9d1336f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt @@ -18,9 +18,12 @@ package im.vector.app.features.home.room.detail.timeline.helper import im.vector.app.core.extensions.localDateTime import im.vector.app.core.resources.UserPreferencesProvider +import org.matrix.android.sdk.BuildConfig import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.RelationType import org.matrix.android.sdk.api.session.events.model.getRelationContent +import org.matrix.android.sdk.api.session.events.model.getRootThreadEventId +import org.matrix.android.sdk.api.session.events.model.isThread import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomMemberContent @@ -37,7 +40,7 @@ class TimelineEventVisibilityHelper @Inject constructor(private val userPreferen * * @return a list of timeline events which have sequentially the same type following the next direction. */ - fun nextSameTypeEvents(timelineEvents: List, index: Int, minSize: Int, eventIdToHighlight: String?): List { + private fun nextSameTypeEvents(timelineEvents: List, index: Int, minSize: Int, eventIdToHighlight: String?, rootThreadEventId: String?): List { if (index >= timelineEvents.size - 1) { return emptyList() } @@ -59,7 +62,7 @@ class TimelineEventVisibilityHelper @Inject constructor(private val userPreferen } else { nextSameDayEvents.subList(0, indexOfFirstDifferentEventType) } - val filteredSameTypeEvents = sameTypeEvents.filter { shouldShowEvent(it, eventIdToHighlight) } + val filteredSameTypeEvents = sameTypeEvents.filter { shouldShowEvent(it, eventIdToHighlight, rootThreadEventId) } if (filteredSameTypeEvents.size < minSize) { return emptyList() } @@ -74,12 +77,12 @@ class TimelineEventVisibilityHelper @Inject constructor(private val userPreferen * * @return a list of timeline events which have sequentially the same type following the prev direction. */ - fun prevSameTypeEvents(timelineEvents: List, index: Int, minSize: Int, eventIdToHighlight: String?): List { + fun prevSameTypeEvents(timelineEvents: List, index: Int, minSize: Int, eventIdToHighlight: String?, rootThreadEventId: String?): List { val prevSub = timelineEvents.subList(0, index + 1) return prevSub .reversed() .let { - nextSameTypeEvents(it, 0, minSize, eventIdToHighlight) + nextSameTypeEvents(it, 0, minSize, eventIdToHighlight, rootThreadEventId) } } @@ -88,7 +91,7 @@ class TimelineEventVisibilityHelper @Inject constructor(private val userPreferen * @param highlightedEventId can be checked to force visibility to true * @return true if the event should be shown in the timeline. */ - fun shouldShowEvent(timelineEvent: TimelineEvent, highlightedEventId: String?): Boolean { + fun shouldShowEvent(timelineEvent: TimelineEvent, highlightedEventId: String?, rootThreadEventId: String?): Boolean { // If show hidden events is true we should always display something if (userPreferencesProvider.shouldShowHiddenEvents()) { return true @@ -100,15 +103,16 @@ class TimelineEventVisibilityHelper @Inject constructor(private val userPreferen if (!timelineEvent.isDisplayable()) { return false } + // Check for special case where we should hide the event, like redacted, relation, memberships... according to user preferences. - return !timelineEvent.shouldBeHidden() + return !timelineEvent.shouldBeHidden(rootThreadEventId) } private fun TimelineEvent.isDisplayable(): Boolean { return TimelineDisplayableEvents.DISPLAYABLE_TYPES.contains(root.getClearType()) } - private fun TimelineEvent.shouldBeHidden(): Boolean { + private fun TimelineEvent.shouldBeHidden(rootThreadEventId: String?): Boolean { if (root.isRedacted() && !userPreferencesProvider.shouldShowRedactedMessages()) { return true } @@ -120,6 +124,11 @@ class TimelineEventVisibilityHelper @Inject constructor(private val userPreferen if ((diff.isJoin || diff.isPart) && !userPreferencesProvider.shouldShowJoinLeaves()) return true if ((diff.isAvatarChange || diff.isDisplaynameChange) && !userPreferencesProvider.shouldShowAvatarDisplayNameChanges()) return true } + + if(BuildConfig.THREADING_ENABLED && rootThreadEventId == null && root.isThread() && root.getRootThreadEventId() != null){ + return true + } + return false } 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 f6672a1d7ca..0649755c2a3 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 @@ -18,10 +18,12 @@ package im.vector.app.features.home.room.detail.timeline.item import android.graphics.Typeface import android.view.View +import android.view.ViewStub import android.widget.ImageView import android.widget.ProgressBar import android.widget.TextView import androidx.annotation.IdRes +import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.view.isInvisible import androidx.core.view.isVisible import com.airbnb.epoxy.EpoxyAttribute @@ -32,6 +34,9 @@ import im.vector.app.core.ui.views.SendStateImageView import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.timeline.MessageColorProvider import im.vector.app.features.home.room.detail.timeline.TimelineEventController +import org.matrix.android.sdk.api.session.threads.ThreadDetails +import org.matrix.android.sdk.api.util.MatrixItem +import timber.log.Timber /** * Base timeline item that adds an optional information bar with the sender avatar, name, time, send state @@ -98,9 +103,20 @@ abstract class AbsMessageItem : AbsBaseMessageItem // Render send state indicator holder.sendStateImageView.render(attributes.informationData.sendStateDecoration) holder.eventSendingIndicator.isVisible = attributes.informationData.sendStateDecoration == SendStateDecoration.SENDING_MEDIA - holder.isThread.isVisible = attributes.isRootThread + + // Threads + attributes.threadDetails?.let { threadDetails -> + threadDetails.isRootThread + holder.threadSummaryConstraintLayout.isVisible = threadDetails.isRootThread + holder.threadSummaryCounterTextView.text = threadDetails.numberOfThreads.toString() + holder.threadSummaryInfoTextView.text = threadDetails.threadSummaryLatestTextMessage + threadDetails.threadSummarySenderInfo?.let { senderInfo -> + attributes.avatarRenderer.render(MatrixItem.UserItem(senderInfo.userId, senderInfo.displayName, senderInfo.avatarUrl), holder.threadSummaryAvatarImageView) + } + } } + override fun unbind(holder: H) { attributes.avatarRenderer.clear(holder.avatarImageView) holder.avatarImageView.setOnClickListener(null) @@ -118,7 +134,11 @@ abstract class AbsMessageItem : AbsBaseMessageItem val timeView by bind(R.id.messageTimeView) val sendStateImageView by bind(R.id.messageSendStateImageView) val eventSendingIndicator by bind(R.id.eventSendingIndicator) - val isThread by bind(R.id.messageIsThread) + val threadSummaryConstraintLayout by bind(R.id.messageThreadSummaryConstraintLayout) + val threadSummaryCounterTextView by bind(R.id.messageThreadSummaryCounterTextView) + val threadSummaryImageView by bind(R.id.messageThreadSummaryImageView) + val threadSummaryAvatarImageView by bind(R.id.messageThreadSummaryAvatarImageView) + val threadSummaryInfoTextView by bind(R.id.messageThreadSummaryInfoTextView) } /** @@ -136,7 +156,7 @@ abstract class AbsMessageItem : AbsBaseMessageItem val avatarCallback: TimelineEventController.AvatarCallback? = null, override val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null, val emojiTypeFace: Typeface? = null, - val isRootThread: Boolean = false + val threadDetails: ThreadDetails? = null ) : AbsBaseMessageItem.Attributes { // Have to override as it's used to diff epoxy items @@ -148,6 +168,7 @@ abstract class AbsMessageItem : AbsBaseMessageItem if (avatarSize != other.avatarSize) return false if (informationData != other.informationData) return false + if (threadDetails != other.threadDetails) return false return true } @@ -155,6 +176,8 @@ abstract class AbsMessageItem : AbsBaseMessageItem override fun hashCode(): Int { var result = avatarSize result = 31 * result + informationData.hashCode() + result = 31 * result + threadDetails.hashCode() + return result } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/merged/MergedTimelines.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/merged/MergedTimelines.kt index 0d5dbc5a8e3..1a90951f07b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/merged/MergedTimelines.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/merged/MergedTimelines.kt @@ -110,7 +110,7 @@ class MergedTimelines( secondaryTimeline.removeAllListeners() } - override fun start() { + override fun start(rootThreadEventId: String?) { mainTimeline.start() secondaryTimeline.start() } diff --git a/vector/src/main/res/drawable/ic_reply_in_thread.xml b/vector/src/main/res/drawable/ic_reply_in_thread.xml index 955dc27f45f..3b9b595bd3d 100644 --- a/vector/src/main/res/drawable/ic_reply_in_thread.xml +++ b/vector/src/main/res/drawable/ic_reply_in_thread.xml @@ -1,24 +1,8 @@ - - - - - - - - - - - \ No newline at end of file + + + + + diff --git a/vector/src/main/res/drawable/ic_thread_summary.xml b/vector/src/main/res/drawable/ic_thread_summary.xml new file mode 100644 index 00000000000..5e27ad0a0a9 --- /dev/null +++ b/vector/src/main/res/drawable/ic_thread_summary.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/vector/src/main/res/layout/item_timeline_event_base.xml b/vector/src/main/res/layout/item_timeline_event_base.xml index f9d43148132..a1e1827d526 100644 --- a/vector/src/main/res/layout/item_timeline_event_base.xml +++ b/vector/src/main/res/layout/item_timeline_event_base.xml @@ -33,8 +33,8 @@ android:layout_marginStart="8dp" android:layout_marginTop="4dp" android:layout_marginEnd="4dp" - android:layout_toStartOf="@+id/messageTimeView" - android:layout_toEndOf="@+id/messageStartGuideline" + android:layout_toStartOf="@id/messageTimeView" + android:layout_toEndOf="@id/messageStartGuideline" android:ellipsize="end" android:maxLines="1" android:textColor="?vctr_content_primary" @@ -200,17 +200,7 @@ - + \ No newline at end of file diff --git a/vector/src/main/res/layout/view_thread_room_summary.xml b/vector/src/main/res/layout/view_thread_room_summary.xml new file mode 100644 index 00000000000..31bdd5ce062 --- /dev/null +++ b/vector/src/main/res/layout/view_thread_room_summary.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + From 4160688f83b37479940fdad985fe69d71e8ab6d6 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Tue, 16 Nov 2021 14:59:30 +0200 Subject: [PATCH 007/581] Supporting command in threads --- .../room/model/relation/RelationService.kt | 7 +- .../room/relation/DefaultRelationService.kt | 7 +- .../room/send/LocalEchoEventFactory.kt | 22 ++- .../command/AutocompleteCommandPresenter.kt | 25 ++- .../im/vector/app/features/command/Command.kt | 72 ++++---- .../app/features/command/CommandParser.kt | 17 +- .../app/features/command/ParsedCommand.kt | 2 + .../home/room/detail/AutoCompleter.kt | 9 +- .../home/room/detail/RoomDetailFragment.kt | 28 ++-- .../detail/composer/TextComposerViewEvents.kt | 1 + .../detail/composer/TextComposerViewModel.kt | 158 +++++++++++------- vector/src/main/res/values/strings.xml | 1 + 12 files changed, 225 insertions(+), 124 deletions(-) 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 226769ced47..be6d1d9aa35 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 @@ -18,6 +18,7 @@ package org.matrix.android.sdk.api.session.room.model.relation import androidx.lifecycle.LiveData import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary +import org.matrix.android.sdk.api.session.room.model.message.MessageType 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.Optional @@ -136,6 +137,8 @@ interface RelationService { * @param autoMarkdown If true, the SDK will generate a formatted HTML message from the body text if markdown syntax is present */ fun replyInThread(rootThreadEventId: String, - replyInThreadText: CharSequence, - autoMarkdown: Boolean = false): Cancelable? + replyInThreadText: CharSequence, + msgType: String = MessageType.MSGTYPE_TEXT, + autoMarkdown: Boolean = false, + formattedText: String? = null): Cancelable? } 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 833f056ceb8..2184e83ac55 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 @@ -159,12 +159,15 @@ internal class DefaultRelationService @AssistedInject constructor( } } - override fun replyInThread(rootThreadEventId: String, replyInThreadText: CharSequence, autoMarkdown: Boolean): Cancelable { + override fun replyInThread(rootThreadEventId: String, replyInThreadText: CharSequence, msgType: String, autoMarkdown: Boolean, formattedText: String?): Cancelable { val event = eventFactory.createThreadTextEvent( rootThreadEventId = rootThreadEventId, roomId = roomId, text = replyInThreadText.toString(), - autoMarkdown = autoMarkdown) + msgType = msgType, + autoMarkdown = autoMarkdown, + formattedText = formattedText + ) // .also { // saveLocalEcho(it) // } 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 b69e8683388..7d99dc67bfe 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 @@ -343,13 +343,21 @@ internal class LocalEchoEventFactory @Inject constructor( /** * Creates a thread event related to the already existing event */ - fun createThreadTextEvent(rootThreadEventId: String, roomId:String, text: String, autoMarkdown: Boolean): Event = - createEvent( - roomId, - EventType.MESSAGE, - createTextContent(text, autoMarkdown) - .toThreadTextContent(rootThreadEventId) - .toContent()) + fun createThreadTextEvent( + rootThreadEventId: String, + roomId: String, + text: String, + msgType: String, + autoMarkdown: Boolean, + formattedText: String?): Event { + + val content = formattedText?.let { TextContent(text, it) } ?: createTextContent(text, autoMarkdown) + return createEvent( + roomId, + EventType.MESSAGE, + content.toThreadTextContent(rootThreadEventId, msgType) + .toContent()) + } private fun dummyOriginServerTs(): Long { return System.currentTimeMillis() diff --git a/vector/src/main/java/im/vector/app/features/autocomplete/command/AutocompleteCommandPresenter.kt b/vector/src/main/java/im/vector/app/features/autocomplete/command/AutocompleteCommandPresenter.kt index 5ad31aeaa63..7846ebab372 100644 --- a/vector/src/main/java/im/vector/app/features/autocomplete/command/AutocompleteCommandPresenter.kt +++ b/vector/src/main/java/im/vector/app/features/autocomplete/command/AutocompleteCommandPresenter.kt @@ -18,17 +18,30 @@ package im.vector.app.features.autocomplete.command import android.content.Context import androidx.recyclerview.widget.RecyclerView +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import im.vector.app.BuildConfig import im.vector.app.features.autocomplete.AutocompleteClickListener import im.vector.app.features.autocomplete.RecyclerViewPresenter import im.vector.app.features.command.Command +import im.vector.app.features.home.room.detail.AutoCompleter import im.vector.app.features.settings.VectorPreferences +import timber.log.Timber import javax.inject.Inject -class AutocompleteCommandPresenter @Inject constructor(context: Context, - private val controller: AutocompleteCommandController, - private val vectorPreferences: VectorPreferences) : +class AutocompleteCommandPresenter @AssistedInject constructor( + @Assisted val isInThreadTimeline: Boolean, + context: Context, + private val controller: AutocompleteCommandController, + private val vectorPreferences: VectorPreferences) : RecyclerViewPresenter(context), AutocompleteClickListener { + @AssistedFactory + interface Factory { + fun create(isFromThreadTimeline: Boolean): AutocompleteCommandPresenter + } + init { controller.listener = this } @@ -46,6 +59,12 @@ class AutocompleteCommandPresenter @Inject constructor(context: Context, .filter { !it.isDevCommand || vectorPreferences.developerMode() } + .filter { + if (BuildConfig.THREADING_ENABLED && isInThreadTimeline) { + it.isThreadCommand + } else + true + } .filter { if (query.isNullOrEmpty()) { true diff --git a/vector/src/main/java/im/vector/app/features/command/Command.kt b/vector/src/main/java/im/vector/app/features/command/Command.kt index 33ccd08d222..2c0d6e83877 100644 --- a/vector/src/main/java/im/vector/app/features/command/Command.kt +++ b/vector/src/main/java/im/vector/app/features/command/Command.kt @@ -24,42 +24,42 @@ import im.vector.app.R * the user can write theses messages to perform some actions * the list will be displayed in this order */ -enum class Command(val command: String, val parameters: String, @StringRes val description: Int, val isDevCommand: Boolean) { - EMOTE("/me", "", R.string.command_description_emote, false), - BAN_USER("/ban", " [reason]", R.string.command_description_ban_user, false), - UNBAN_USER("/unban", " [reason]", R.string.command_description_unban_user, false), - IGNORE_USER("/ignore", " [reason]", R.string.command_description_ignore_user, false), - UNIGNORE_USER("/unignore", "", R.string.command_description_unignore_user, false), - SET_USER_POWER_LEVEL("/op", " []", R.string.command_description_op_user, false), - RESET_USER_POWER_LEVEL("/deop", "", R.string.command_description_deop_user, false), - ROOM_NAME("/roomname", "", R.string.command_description_room_name, false), - INVITE("/invite", " [reason]", R.string.command_description_invite_user, false), - JOIN_ROOM("/join", " [reason]", R.string.command_description_join_room, false), - PART("/part", " [reason]", R.string.command_description_part_room, false), - TOPIC("/topic", "", R.string.command_description_topic, false), - KICK_USER("/kick", " [reason]", R.string.command_description_kick_user, false), - CHANGE_DISPLAY_NAME("/nick", "", R.string.command_description_nick, false), - CHANGE_DISPLAY_NAME_FOR_ROOM("/myroomnick", "", R.string.command_description_nick_for_room, false), - ROOM_AVATAR("/roomavatar", "", R.string.command_description_room_avatar, true /* Since user has to know the mxc url */), - CHANGE_AVATAR_FOR_ROOM("/myroomavatar", "", R.string.command_description_avatar_for_room, true /* Since user has to know the mxc url */), - MARKDOWN("/markdown", "", R.string.command_description_markdown, false), - RAINBOW("/rainbow", "", R.string.command_description_rainbow, false), - RAINBOW_EMOTE("/rainbowme", "", R.string.command_description_rainbow_emote, false), - CLEAR_SCALAR_TOKEN("/clear_scalar_token", "", R.string.command_description_clear_scalar_token, false), - SPOILER("/spoiler", "", R.string.command_description_spoiler, false), - POLL("/poll", "Question | Option 1 | Option 2 ...", R.string.command_description_poll, false), - SHRUG("/shrug", "", R.string.command_description_shrug, false), - LENNY("/lenny", "", R.string.command_description_lenny, false), - PLAIN("/plain", "", R.string.command_description_plain, false), - WHOIS("/whois", "", R.string.command_description_whois, false), - DISCARD_SESSION("/discardsession", "", R.string.command_description_discard_session, false), - CONFETTI("/confetti", "", R.string.command_confetti, false), - SNOWFALL("/snowfall", "", R.string.command_snow, false), - CREATE_SPACE("/createspace", " *", R.string.command_description_create_space, true), - ADD_TO_SPACE("/addToSpace", "spaceId", R.string.command_description_add_to_space, true), - JOIN_SPACE("/joinSpace", "spaceId", R.string.command_description_join_space, true), - LEAVE_ROOM("/leave", "", R.string.command_description_leave_room, true), - UPGRADE_ROOM("/upgraderoom", "newVersion", R.string.command_description_upgrade_room, true); +enum class Command(val command: String, val parameters: String, @StringRes val description: Int, val isDevCommand: Boolean, val isThreadCommand: Boolean) { + EMOTE("/me", "", R.string.command_description_emote, false, true), + BAN_USER("/ban", " [reason]", R.string.command_description_ban_user, false, false), + UNBAN_USER("/unban", " [reason]", R.string.command_description_unban_user, false, false), + IGNORE_USER("/ignore", " [reason]", R.string.command_description_ignore_user, false, true), + UNIGNORE_USER("/unignore", "", R.string.command_description_unignore_user, false, true), + SET_USER_POWER_LEVEL("/op", " []", R.string.command_description_op_user, false, false), + RESET_USER_POWER_LEVEL("/deop", "", R.string.command_description_deop_user, false, false), + ROOM_NAME("/roomname", "", R.string.command_description_room_name, false, false), + INVITE("/invite", " [reason]", R.string.command_description_invite_user, false, false), + JOIN_ROOM("/join", " [reason]", R.string.command_description_join_room, false, false), + PART("/part", " [reason]", R.string.command_description_part_room, false, false), + TOPIC("/topic", "", R.string.command_description_topic, false, false), + KICK_USER("/kick", " [reason]", R.string.command_description_kick_user, false, false), + CHANGE_DISPLAY_NAME("/nick", "", R.string.command_description_nick, false, false), + CHANGE_DISPLAY_NAME_FOR_ROOM("/myroomnick", "", R.string.command_description_nick_for_room, false, false), + ROOM_AVATAR("/roomavatar", "", R.string.command_description_room_avatar, true /* Since user has to know the mxc url */, false), + CHANGE_AVATAR_FOR_ROOM("/myroomavatar", "", R.string.command_description_avatar_for_room, true /* Since user has to know the mxc url */, false), + MARKDOWN("/markdown", "", R.string.command_description_markdown, false, false), + RAINBOW("/rainbow", "", R.string.command_description_rainbow, false, true), + RAINBOW_EMOTE("/rainbowme", "", R.string.command_description_rainbow_emote, false, true), + CLEAR_SCALAR_TOKEN("/clear_scalar_token", "", R.string.command_description_clear_scalar_token, false, false), + SPOILER("/spoiler", "", R.string.command_description_spoiler, false, true), + POLL("/poll", "Question | Option 1 | Option 2 ...", R.string.command_description_poll, false, false), + SHRUG("/shrug", "", R.string.command_description_shrug, false, true), + LENNY("/lenny", "", R.string.command_description_lenny, false, true), + PLAIN("/plain", "", R.string.command_description_plain, false, true), + WHOIS("/whois", "", R.string.command_description_whois, false, true), + DISCARD_SESSION("/discardsession", "", R.string.command_description_discard_session, false, false), + CONFETTI("/confetti", "", R.string.command_confetti, false, false), + SNOWFALL("/snowfall", "", R.string.command_snow, false, false), + CREATE_SPACE("/createspace", " *", R.string.command_description_create_space, true, false), + ADD_TO_SPACE("/addToSpace", "spaceId", R.string.command_description_add_to_space, true, false), + JOIN_SPACE("/joinSpace", "spaceId", R.string.command_description_join_space, true, false), + LEAVE_ROOM("/leave", "", R.string.command_description_leave_room, true, false), + UPGRADE_ROOM("/upgraderoom", "newVersion", R.string.command_description_upgrade_room, true, false); val length get() = command.length + 1 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 e570033d35a..47dbce13768 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 @@ -16,6 +16,7 @@ package im.vector.app.features.command +import im.vector.app.BuildConfig import im.vector.app.core.extensions.isEmail import im.vector.app.core.extensions.isMsisdn import im.vector.app.features.home.room.detail.ChatEffect @@ -32,7 +33,7 @@ object CommandParser { * @param textMessage the text message * @return a parsed slash command (ok or error) */ - fun parseSplashCommand(textMessage: CharSequence): ParsedCommand { + fun parseSplashCommand(textMessage: CharSequence, isInThreadTimeline: Boolean): ParsedCommand { // check if it has the Slash marker if (!textMessage.startsWith("/")) { return ParsedCommand.ErrorNotACommand @@ -61,6 +62,20 @@ object CommandParser { return ParsedCommand.ErrorEmptySlashCommand } + // If the command is not supported by threads return error + + if(BuildConfig.THREADING_ENABLED && isInThreadTimeline){ + val slashCommand = messageParts.first() + val notSupportedCommandsInThreads = Command.values().filter { + !it.isThreadCommand + }.map { + it.command + } + if(notSupportedCommandsInThreads.contains(slashCommand)){ + return ParsedCommand.ErrorCommandNotSupportedInThreads(slashCommand) + } + } + return when (val slashCommand = messageParts.first()) { Command.PLAIN.command -> { val text = textMessage.substring(Command.PLAIN.command.length).trim() 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 bafb9153e6a..a439b3eb469 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 @@ -28,6 +28,8 @@ sealed class ParsedCommand { object ErrorEmptySlashCommand : ParsedCommand() + class ErrorCommandNotSupportedInThreads(val slashCommand: String) : ParsedCommand() + // Unknown/Unsupported slash command class ErrorUnknownSlashCommand(val slashCommand: String) : ParsedCommand() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/AutoCompleter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/AutoCompleter.kt index 1d6530218df..053b267a5dc 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/AutoCompleter.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/AutoCompleter.kt @@ -49,9 +49,10 @@ import org.matrix.android.sdk.api.util.toRoomAliasMatrixItem class AutoCompleter @AssistedInject constructor( @Assisted val roomId: String, + @Assisted val isInThreadTimeline: Boolean, private val avatarRenderer: AvatarRenderer, private val commandAutocompletePolicy: CommandAutocompletePolicy, - private val autocompleteCommandPresenter: AutocompleteCommandPresenter, + AutocompleteCommandPresenterFactory: AutocompleteCommandPresenter.Factory, private val autocompleteMemberPresenterFactory: AutocompleteMemberPresenter.Factory, private val autocompleteRoomPresenter: AutocompleteRoomPresenter, private val autocompleteGroupPresenter: AutocompleteGroupPresenter, @@ -62,7 +63,11 @@ class AutoCompleter @AssistedInject constructor( @AssistedFactory interface Factory { - fun create(roomId: String): AutoCompleter + fun create(roomId: String, isInThreadTimeline: Boolean): AutoCompleter + } + + private val autocompleteCommandPresenter: AutocompleteCommandPresenter by lazy { + AutocompleteCommandPresenterFactory.create(isInThreadTimeline) } private var editText: EditText? = null diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index ecc96e4be89..6dfe29cec6f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -291,8 +291,9 @@ class RoomDetailFragment @Inject constructor( } private val autoCompleter: AutoCompleter by lazy { - autoCompleterFactory.create(roomDetailArgs.roomId) + autoCompleterFactory.create(roomDetailArgs.roomId, isThreadTimeLine()) } + private val roomDetailViewModel: RoomDetailViewModel by fragmentViewModel() private val textComposerViewModel: TextComposerViewModel by fragmentViewModel() private val debouncer = Debouncer(createUIHandler()) @@ -396,10 +397,10 @@ class RoomDetailFragment @Inject constructor( 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.quote, mode.text) - is SendMode.REPLY -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_reply, R.string.reply, mode.text) + 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.quote, mode.text) + is SendMode.REPLY -> renderSpecialMode(mode.timelineEvent, R.drawable.ic_reply, R.string.reply, mode.text) } } @@ -1466,24 +1467,27 @@ class RoomDetailFragment @Inject constructor( private fun renderSendMessageResult(sendMessageResult: TextComposerViewEvents.SendMessageResult) { when (sendMessageResult) { - is TextComposerViewEvents.SlashCommandHandled -> { + is TextComposerViewEvents.SlashCommandHandled -> { sendMessageResult.messageRes?.let { showSnackWithMessage(getString(it)) } } - is TextComposerViewEvents.SlashCommandError -> { + is TextComposerViewEvents.SlashCommandError -> { displayCommandError(getString(R.string.command_problem_with_parameters, sendMessageResult.command.command)) } - is TextComposerViewEvents.SlashCommandUnknown -> { + is TextComposerViewEvents.SlashCommandUnknown -> { displayCommandError(getString(R.string.unrecognized_command, sendMessageResult.command)) } - is TextComposerViewEvents.SlashCommandResultOk -> { + is TextComposerViewEvents.SlashCommandResultOk -> { views.composerLayout.setTextIfDifferent("") } - is TextComposerViewEvents.SlashCommandResultError -> { + is TextComposerViewEvents.SlashCommandResultError -> { displayCommandError(errorFormatter.toHumanReadable(sendMessageResult.throwable)) } - is TextComposerViewEvents.SlashCommandNotImplemented -> { + is TextComposerViewEvents.SlashCommandNotImplemented -> { displayCommandError(getString(R.string.not_implemented)) } + is TextComposerViewEvents.SlashCommandNotSupportedInThreads -> { + displayCommandError(getString(R.string.command_not_supported_in_threads, sendMessageResult.command)) + } } // .exhaustive lockSendButton = false @@ -2217,6 +2221,6 @@ class RoomDetailFragment @Inject constructor( } } - fun isThreadTimeLine(): Boolean = roomDetailArgs.roomThreadDetailArgs != null + private fun isThreadTimeLine(): Boolean = roomDetailArgs.roomThreadDetailArgs != null fun getRootThreadEventId(): String? = roomDetailArgs.roomThreadDetailArgs?.eventId } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewEvents.kt index 691ed4d93e0..e40e2b0b833 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewEvents.kt @@ -32,6 +32,7 @@ sealed class TextComposerViewEvents : VectorViewEvents { data class JoinRoomCommandSuccess(val roomId: String) : SendMessageResult() class SlashCommandError(val command: Command) : SendMessageResult() class SlashCommandUnknown(val command: String) : SendMessageResult() + class SlashCommandNotSupportedInThreads(val command: String) : SendMessageResult() data class SlashCommandHandled(@StringRes val messageRes: Int? = null) : SendMessageResult() object SlashCommandResultOk : SendMessageResult() class SlashCommandResultError(val throwable: Throwable) : SendMessageResult() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt index 158bc85cb1c..1fa1bfde351 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt @@ -152,160 +152,198 @@ class TextComposerViewModel @AssistedInject constructor( private fun handleSendMessage(action: TextComposerAction.SendMessage) { withState { state -> when (state.sendMode) { - is SendMode.REGULAR -> { - when (val slashCommandResult = CommandParser.parseSplashCommand(action.text)) { - is ParsedCommand.ErrorNotACommand -> { + is SendMode.REGULAR -> { + when (val slashCommandResult = CommandParser.parseSplashCommand(action.text, state.isInThreadTimeline())) { + is ParsedCommand.ErrorNotACommand -> { // Send the text message to the room if (state.rootThreadEventId != null) - room.replyInThread(state.rootThreadEventId, action.text.toString(), action.autoMarkdown) + room.replyInThread( + rootThreadEventId = state.rootThreadEventId, + replyInThreadText = action.text.toString(), + autoMarkdown = action.autoMarkdown) else room.sendTextMessage(action.text, autoMarkdown = action.autoMarkdown) _viewEvents.post(TextComposerViewEvents.MessageSent) popDraft() } - is ParsedCommand.ErrorSyntax -> { + is ParsedCommand.ErrorSyntax -> { _viewEvents.post(TextComposerViewEvents.SlashCommandError(slashCommandResult.command)) } - is ParsedCommand.ErrorEmptySlashCommand -> { + is ParsedCommand.ErrorEmptySlashCommand -> { _viewEvents.post(TextComposerViewEvents.SlashCommandUnknown("/")) } - is ParsedCommand.ErrorUnknownSlashCommand -> { + is ParsedCommand.ErrorUnknownSlashCommand -> { _viewEvents.post(TextComposerViewEvents.SlashCommandUnknown(slashCommandResult.slashCommand)) } - is ParsedCommand.SendPlainText -> { + is ParsedCommand.ErrorCommandNotSupportedInThreads -> { + _viewEvents.post(TextComposerViewEvents.SlashCommandNotSupportedInThreads(slashCommandResult.slashCommand)) + } + is ParsedCommand.SendPlainText -> { // Send the text message to the room, without markdown - room.sendTextMessage(slashCommandResult.message, autoMarkdown = false) + if (state.rootThreadEventId != null) + room.replyInThread( + rootThreadEventId = state.rootThreadEventId, + replyInThreadText = action.text.toString(), + autoMarkdown = false) + else + room.sendTextMessage(slashCommandResult.message, autoMarkdown = false) _viewEvents.post(TextComposerViewEvents.MessageSent) popDraft() } - is ParsedCommand.ChangeRoomName -> { + is ParsedCommand.ChangeRoomName -> { handleChangeRoomNameSlashCommand(slashCommandResult) popDraft() } - is ParsedCommand.Invite -> { + is ParsedCommand.Invite -> { handleInviteSlashCommand(slashCommandResult) popDraft() } - is ParsedCommand.Invite3Pid -> { + is ParsedCommand.Invite3Pid -> { handleInvite3pidSlashCommand(slashCommandResult) popDraft() } - is ParsedCommand.SetUserPowerLevel -> { + is ParsedCommand.SetUserPowerLevel -> { handleSetUserPowerLevel(slashCommandResult) popDraft() } - is ParsedCommand.ClearScalarToken -> { + is ParsedCommand.ClearScalarToken -> { // TODO _viewEvents.post(TextComposerViewEvents.SlashCommandNotImplemented) } - is ParsedCommand.SetMarkdown -> { + is ParsedCommand.SetMarkdown -> { vectorPreferences.setMarkdownEnabled(slashCommandResult.enable) _viewEvents.post(TextComposerViewEvents.SlashCommandHandled( if (slashCommandResult.enable) R.string.markdown_has_been_enabled else R.string.markdown_has_been_disabled)) popDraft() } - is ParsedCommand.BanUser -> { + is ParsedCommand.BanUser -> { handleBanSlashCommand(slashCommandResult) popDraft() } - is ParsedCommand.UnbanUser -> { + is ParsedCommand.UnbanUser -> { handleUnbanSlashCommand(slashCommandResult) popDraft() } - is ParsedCommand.IgnoreUser -> { + is ParsedCommand.IgnoreUser -> { handleIgnoreSlashCommand(slashCommandResult) popDraft() } - is ParsedCommand.UnignoreUser -> { + is ParsedCommand.UnignoreUser -> { handleUnignoreSlashCommand(slashCommandResult) popDraft() } - is ParsedCommand.KickUser -> { + is ParsedCommand.KickUser -> { handleKickSlashCommand(slashCommandResult) popDraft() } - is ParsedCommand.JoinRoom -> { + is ParsedCommand.JoinRoom -> { handleJoinToAnotherRoomSlashCommand(slashCommandResult) popDraft() } - is ParsedCommand.PartRoom -> { + is ParsedCommand.PartRoom -> { // TODO _viewEvents.post(TextComposerViewEvents.SlashCommandNotImplemented) } - is ParsedCommand.SendEmote -> { - room.sendTextMessage(slashCommandResult.message, msgType = MessageType.MSGTYPE_EMOTE, autoMarkdown = action.autoMarkdown) + is ParsedCommand.SendEmote -> { + state.rootThreadEventId?.let { + room.replyInThread( + rootThreadEventId = state.rootThreadEventId, + replyInThreadText = slashCommandResult.message, + msgType = MessageType.MSGTYPE_EMOTE, + autoMarkdown = action.autoMarkdown) + } ?: room.sendTextMessage(slashCommandResult.message, msgType = MessageType.MSGTYPE_EMOTE, autoMarkdown = action.autoMarkdown) _viewEvents.post(TextComposerViewEvents.SlashCommandHandled()) popDraft() } - is ParsedCommand.SendRainbow -> { - slashCommandResult.message.toString().let { - room.sendFormattedTextMessage(it, rainbowGenerator.generate(it)) - } + is ParsedCommand.SendRainbow -> { + + val message = slashCommandResult.message.toString() + state.rootThreadEventId?.let { + room.replyInThread( + rootThreadEventId = state.rootThreadEventId, + replyInThreadText = slashCommandResult.message, + formattedText = rainbowGenerator.generate(message)) + } ?: room.sendFormattedTextMessage(message, rainbowGenerator.generate(message)) _viewEvents.post(TextComposerViewEvents.SlashCommandHandled()) popDraft() } - is ParsedCommand.SendRainbowEmote -> { - slashCommandResult.message.toString().let { - room.sendFormattedTextMessage(it, rainbowGenerator.generate(it), MessageType.MSGTYPE_EMOTE) - } + is ParsedCommand.SendRainbowEmote -> { + val message = slashCommandResult.message.toString() + state.rootThreadEventId?.let { + room.replyInThread( + rootThreadEventId = state.rootThreadEventId, + replyInThreadText = slashCommandResult.message, + msgType = MessageType.MSGTYPE_EMOTE, + formattedText = rainbowGenerator.generate(message)) + } ?: room.sendFormattedTextMessage(message, rainbowGenerator.generate(message),MessageType.MSGTYPE_EMOTE) + _viewEvents.post(TextComposerViewEvents.SlashCommandHandled()) popDraft() } - is ParsedCommand.SendSpoiler -> { - room.sendFormattedTextMessage( - "[${stringProvider.getString(R.string.spoiler)}](${slashCommandResult.message})", - "${slashCommandResult.message}" + is ParsedCommand.SendSpoiler -> { + + val text = "[${stringProvider.getString(R.string.spoiler)}](${slashCommandResult.message})" + val formattedText = "${slashCommandResult.message}" + state.rootThreadEventId?.let { + room.replyInThread( + rootThreadEventId = state.rootThreadEventId, + replyInThreadText = text, + formattedText = formattedText) + } ?: room.sendFormattedTextMessage( + text, + formattedText ) _viewEvents.post(TextComposerViewEvents.SlashCommandHandled()) popDraft() } - is ParsedCommand.SendShrug -> { - sendPrefixedMessage("¯\\_(ツ)_/¯", slashCommandResult.message) + is ParsedCommand.SendShrug -> { + + sendPrefixedMessage("¯\\_(ツ)_/¯", slashCommandResult.message, state.rootThreadEventId) _viewEvents.post(TextComposerViewEvents.SlashCommandHandled()) popDraft() } - is ParsedCommand.SendLenny -> { - sendPrefixedMessage("( ͡° ͜ʖ ͡°)", slashCommandResult.message) + is ParsedCommand.SendLenny -> { + sendPrefixedMessage("( ͡° ͜ʖ ͡°)", slashCommandResult.message, state.rootThreadEventId) _viewEvents.post(TextComposerViewEvents.SlashCommandHandled()) popDraft() } - is ParsedCommand.SendChatEffect -> { + is ParsedCommand.SendChatEffect -> { sendChatEffect(slashCommandResult) _viewEvents.post(TextComposerViewEvents.SlashCommandHandled()) popDraft() } - is ParsedCommand.SendPoll -> { + is ParsedCommand.SendPoll -> { room.sendPoll(slashCommandResult.question, slashCommandResult.options.mapIndexed { index, s -> OptionItem(s, "$index. $s") }) _viewEvents.post(TextComposerViewEvents.SlashCommandHandled()) popDraft() } - is ParsedCommand.ChangeTopic -> { + is ParsedCommand.ChangeTopic -> { handleChangeTopicSlashCommand(slashCommandResult) popDraft() } - is ParsedCommand.ChangeDisplayName -> { + is ParsedCommand.ChangeDisplayName -> { handleChangeDisplayNameSlashCommand(slashCommandResult) popDraft() } - is ParsedCommand.ChangeDisplayNameForRoom -> { + is ParsedCommand.ChangeDisplayNameForRoom -> { handleChangeDisplayNameForRoomSlashCommand(slashCommandResult) popDraft() } - is ParsedCommand.ChangeRoomAvatar -> { + is ParsedCommand.ChangeRoomAvatar -> { handleChangeRoomAvatarSlashCommand(slashCommandResult) popDraft() } - is ParsedCommand.ChangeAvatarForRoom -> { + is ParsedCommand.ChangeAvatarForRoom -> { handleChangeAvatarForRoomSlashCommand(slashCommandResult) popDraft() } - is ParsedCommand.ShowUser -> { + is ParsedCommand.ShowUser -> { _viewEvents.post(TextComposerViewEvents.SlashCommandHandled()) handleWhoisSlashCommand(slashCommandResult) popDraft() } - is ParsedCommand.DiscardSession -> { + is ParsedCommand.DiscardSession -> { if (room.isEncrypted()) { session.cryptoService().discardOutboundSession(room.roomId) _viewEvents.post(TextComposerViewEvents.SlashCommandHandled()) @@ -318,7 +356,7 @@ class TextComposerViewModel @AssistedInject constructor( ) } } - is ParsedCommand.CreateSpace -> { + is ParsedCommand.CreateSpace -> { viewModelScope.launch(Dispatchers.IO) { try { val params = CreateSpaceParams().apply { @@ -340,7 +378,7 @@ class TextComposerViewModel @AssistedInject constructor( _viewEvents.post(TextComposerViewEvents.SlashCommandHandled()) popDraft() } - is ParsedCommand.AddToSpace -> { + is ParsedCommand.AddToSpace -> { viewModelScope.launch(Dispatchers.IO) { try { session.spaceService().getSpace(slashCommandResult.spaceId) @@ -357,7 +395,7 @@ class TextComposerViewModel @AssistedInject constructor( _viewEvents.post(TextComposerViewEvents.SlashCommandHandled()) popDraft() } - is ParsedCommand.JoinSpace -> { + is ParsedCommand.JoinSpace -> { viewModelScope.launch(Dispatchers.IO) { try { session.spaceService().joinSpace(slashCommandResult.spaceIdOrAlias) @@ -368,7 +406,7 @@ class TextComposerViewModel @AssistedInject constructor( _viewEvents.post(TextComposerViewEvents.SlashCommandHandled()) popDraft() } - is ParsedCommand.LeaveRoom -> { + is ParsedCommand.LeaveRoom -> { viewModelScope.launch(Dispatchers.IO) { try { session.getRoom(slashCommandResult.roomId)?.leave(null) @@ -379,7 +417,7 @@ class TextComposerViewModel @AssistedInject constructor( _viewEvents.post(TextComposerViewEvents.SlashCommandHandled()) popDraft() } - is ParsedCommand.UpgradeRoom -> { + is ParsedCommand.UpgradeRoom -> { _viewEvents.post( TextComposerViewEvents.ShowRoomUpgradeDialog( slashCommandResult.newVersion, @@ -391,7 +429,7 @@ class TextComposerViewModel @AssistedInject constructor( } }.exhaustive } - is SendMode.EDIT -> { + is SendMode.EDIT -> { // is original event a reply? val inReplyTo = state.sendMode.timelineEvent.getRelationContent()?.inReplyTo?.eventId if (inReplyTo != null) { @@ -414,7 +452,7 @@ class TextComposerViewModel @AssistedInject constructor( _viewEvents.post(TextComposerViewEvents.MessageSent) popDraft() } - is SendMode.QUOTE -> { + is SendMode.QUOTE -> { val messageContent = state.sendMode.timelineEvent.getLastMessageContent() val textMsg = messageContent?.body @@ -435,7 +473,7 @@ class TextComposerViewModel @AssistedInject constructor( _viewEvents.post(TextComposerViewEvents.MessageSent) popDraft() } - is SendMode.REPLY -> { + is SendMode.REPLY -> { state.sendMode.timelineEvent.let { room.replyToMessage(it, action.text.toString(), action.autoMarkdown) _viewEvents.post(TextComposerViewEvents.MessageSent) @@ -657,7 +695,7 @@ class TextComposerViewModel @AssistedInject constructor( _viewEvents.post(TextComposerViewEvents.OpenRoomMemberProfile(whois.userId)) } - private fun sendPrefixedMessage(prefix: String, message: CharSequence) { + private fun sendPrefixedMessage(prefix: String, message: CharSequence, rootThreadEventId: String?) { val sequence = buildString { append(prefix) if (message.isNotEmpty()) { @@ -665,7 +703,9 @@ class TextComposerViewModel @AssistedInject constructor( append(message) } } - room.sendTextMessage(sequence) + rootThreadEventId?.let { + room.replyInThread(it, sequence) + }?: room.sendTextMessage(sequence) } /** diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 591cc152b90..b7816927336 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1831,6 +1831,7 @@ Command error Unrecognized command: %s The command \"%s\" needs more parameters, or some parameters are incorrect. + The command \"%s\" is recognized but not supported in threads. Displays action Bans user with given id Unbans user with given id From 3d9350091ef44e6284a911c380f43679fd29265f Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Wed, 17 Nov 2021 13:09:27 +0200 Subject: [PATCH 008/581] Add Replies support from within a thread --- .../room/model/relation/RelationService.kt | 6 +++- .../room/relation/DefaultRelationService.kt | 27 +++++++++++---- .../session/room/relation/EventEditor.kt | 6 +++- .../room/send/LocalEchoEventFactory.kt | 23 ++++++++++--- .../detail/composer/TextComposerAction.kt | 2 -- .../detail/composer/TextComposerViewModel.kt | 34 ++++++++++++++----- 6 files changed, 75 insertions(+), 23 deletions(-) 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 be6d1d9aa35..a5ecfaf6e44 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 @@ -134,11 +134,15 @@ interface RelationService { * by the sdk into pills. * @param rootThreadEventId the root thread eventId * @param replyInThreadText the reply text + * @param msgType the message type: MessageType.MSGTYPE_TEXT (default) or MessageType.MSGTYPE_EMOTE + * @param formattedText The formatted body using MessageType#FORMAT_MATRIX_HTML * @param autoMarkdown If true, the SDK will generate a formatted HTML message from the body text if markdown syntax is present + * @param eventReplied the event referenced by the reply within a thread */ fun replyInThread(rootThreadEventId: String, replyInThreadText: CharSequence, msgType: String = MessageType.MSGTYPE_TEXT, autoMarkdown: Boolean = false, - formattedText: String? = null): Cancelable? + formattedText: String? = null, + eventReplied: TimelineEvent? = null): Cancelable? } 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 2184e83ac55..23862ae9638 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 @@ -38,7 +38,6 @@ import org.matrix.android.sdk.internal.database.model.TimelineEventEntity import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory -import org.matrix.android.sdk.internal.session.room.send.TextContent import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.configureWith @@ -133,7 +132,11 @@ internal class DefaultRelationService @AssistedInject constructor( } override fun replyToMessage(eventReplied: TimelineEvent, replyText: CharSequence, autoMarkdown: Boolean): Cancelable? { - val event = eventFactory.createReplyTextEvent(roomId, eventReplied, replyText, autoMarkdown) + val event = eventFactory.createReplyTextEvent( + roomId = roomId, + eventReplied = eventReplied, + replyText = replyText, + autoMarkdown = autoMarkdown) ?.also { saveLocalEcho(it) } ?: return null @@ -159,15 +162,27 @@ internal class DefaultRelationService @AssistedInject constructor( } } - override fun replyInThread(rootThreadEventId: String, replyInThreadText: CharSequence, msgType: String, autoMarkdown: Boolean, formattedText: String?): Cancelable { - val event = eventFactory.createThreadTextEvent( + override fun replyInThread( + rootThreadEventId: String, + replyInThreadText: CharSequence, + msgType: String, + autoMarkdown: Boolean, + formattedText: String?, + eventReplied: TimelineEvent?): Cancelable { + val event = eventReplied?.let { + eventFactory.createReplyTextEvent( + roomId = roomId, + eventReplied = eventReplied, + replyText = replyInThreadText, + autoMarkdown = autoMarkdown, + rootThreadEventId = rootThreadEventId) + } ?: eventFactory.createThreadTextEvent( rootThreadEventId = rootThreadEventId, roomId = roomId, text = replyInThreadText.toString(), msgType = msgType, autoMarkdown = autoMarkdown, - formattedText = formattedText - ) + formattedText = formattedText) // .also { // saveLocalEcho(it) // } 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 a666d40fc31..7e3d7dfde87 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 @@ -67,7 +67,11 @@ internal class EventEditor @Inject constructor(private val eventSenderProcessor: val roomId = replyToEdit.roomId if (replyToEdit.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.createReplyTextEvent(roomId, originalTimelineEvent, newBodyText, false)?.copy( + val editedEvent = eventFactory.createReplyTextEvent( + roomId = roomId, + eventReplied = originalTimelineEvent, + replyText = newBodyText, + autoMarkdown = false)?.copy( eventId = replyToEdit.eventId ) ?: return NoOpCancellable updateFailedEchoWithEvent(roomId, replyToEdit.eventId, editedEvent) 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 7d99dc67bfe..5741d0f5ba9 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 @@ -341,7 +341,7 @@ internal class LocalEchoEventFactory @Inject constructor( } /** - * Creates a thread event related to the already existing event + * Creates a thread event related to the already existing root event */ fun createThreadTextEvent( rootThreadEventId: String, @@ -363,10 +363,14 @@ internal class LocalEchoEventFactory @Inject constructor( return System.currentTimeMillis() } + /** + * Creates a reply to a regular timeline Event or a thread Event if needed + */ fun createReplyTextEvent(roomId: String, eventReplied: TimelineEvent, replyText: CharSequence, - autoMarkdown: Boolean): Event? { + autoMarkdown: Boolean, + rootThreadEventId: String? = null): Event? { // Fallbacks and event representation // TODO Add error/warning logs when any of this is null val permalink = permalinkFactory.createPermalink(eventReplied.root, false) ?: return null @@ -393,11 +397,22 @@ internal class LocalEchoEventFactory @Inject constructor( format = MessageFormat.FORMAT_MATRIX_HTML, body = replyFallback, formattedBody = replyFormatted, - relatesTo = RelationDefaultContent(null, null, ReplyToContent(eventId)) - ) + relatesTo = generateReplyRelationContent(eventId = eventId, rootThreadEventId = rootThreadEventId)) return createMessageEvent(roomId, content) } + /** + * Generates the appropriate relatesTo object for a reply event. + * It can either be a regular reply or a reply within a thread + */ + private fun generateReplyRelationContent(eventId: String, rootThreadEventId: String? = null): RelationDefaultContent = + rootThreadEventId?.let { + RelationDefaultContent( + type = RelationType.THREAD, + eventId = it, + inReplyTo = ReplyToContent(eventId)) + } ?: RelationDefaultContent(null, null, ReplyToContent(eventId)) + private fun buildReplyFallback(body: TextContent, originalSenderId: String?, newBodyText: String): String { return buildString { append("> <") diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerAction.kt index 48f6c849831..77254001875 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerAction.kt @@ -28,6 +28,4 @@ sealed class TextComposerAction : VectorViewModelAction { data class UserIsTyping(val isTyping: Boolean) : TextComposerAction() data class OnTextChanged(val text: CharSequence) : TextComposerAction() data class OnVoiceRecordingStateChanged(val isRecording: Boolean) : TextComposerAction() - data class EnterReplyInThreadTimeline(val rootThreadEventId: String) : TextComposerAction() - } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt index 1fa1bfde351..bcc26247a2f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt @@ -276,7 +276,7 @@ class TextComposerViewModel @AssistedInject constructor( replyInThreadText = slashCommandResult.message, msgType = MessageType.MSGTYPE_EMOTE, formattedText = rainbowGenerator.generate(message)) - } ?: room.sendFormattedTextMessage(message, rainbowGenerator.generate(message),MessageType.MSGTYPE_EMOTE) + } ?: room.sendFormattedTextMessage(message, rainbowGenerator.generate(message), MessageType.MSGTYPE_EMOTE) _viewEvents.post(TextComposerViewEvents.SlashCommandHandled()) popDraft() @@ -465,20 +465,36 @@ class TextComposerViewModel @AssistedInject constructor( val document = parser.parse(finalText) val renderer = HtmlRenderer.builder().build() val htmlText = renderer.render(document) + if (finalText == htmlText) { - room.sendTextMessage(finalText) + state.rootThreadEventId?.let { + room.replyInThread( + rootThreadEventId = it, + replyInThreadText = finalText) + } ?: room.sendTextMessage(finalText) } else { - room.sendFormattedTextMessage(finalText, htmlText) + state.rootThreadEventId?.let { + room.replyInThread( + rootThreadEventId = it, + replyInThreadText = finalText, + formattedText = htmlText) + } ?: room.sendFormattedTextMessage(finalText, htmlText) } _viewEvents.post(TextComposerViewEvents.MessageSent) popDraft() } is SendMode.REPLY -> { - state.sendMode.timelineEvent.let { - room.replyToMessage(it, action.text.toString(), action.autoMarkdown) - _viewEvents.post(TextComposerViewEvents.MessageSent) - popDraft() - } + val timelineEvent = state.sendMode.timelineEvent + state.rootThreadEventId?.let { rootThreadEventId -> + room.replyInThread( + rootThreadEventId = rootThreadEventId, + replyInThreadText = action.text.toString(), + autoMarkdown = action.autoMarkdown, + eventReplied = timelineEvent) + } ?: room.replyToMessage(timelineEvent, action.text.toString(), action.autoMarkdown) + + _viewEvents.post(TextComposerViewEvents.MessageSent) + popDraft() } }.exhaustive } @@ -705,7 +721,7 @@ class TextComposerViewModel @AssistedInject constructor( } rootThreadEventId?.let { room.replyInThread(it, sequence) - }?: room.sendTextMessage(sequence) + } ?: room.sendTextMessage(sequence) } /** From 3de0f7bf373d736b63b9784f76e01d414b8b3551 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Thu, 18 Nov 2021 15:48:17 +0200 Subject: [PATCH 009/581] Add sending file to thread support ** Important while this feature depends on local echo, should be added local echo support in threads to work 100% --- .../java/org/matrix/android/sdk/rx/RxRoom.kt | 5 ++- .../sdk/api/session/room/send/SendService.kt | 8 +++-- .../session/room/send/DefaultSendService.kt | 24 +++++++++++--- .../room/send/LocalEchoEventFactory.kt | 33 +++++++++++-------- .../home/room/detail/RoomDetailViewModel.kt | 19 ++++++++--- 5 files changed, 64 insertions(+), 25 deletions(-) diff --git a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxRoom.kt b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxRoom.kt index b3495c4493c..36f59c00584 100644 --- a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxRoom.kt +++ b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxRoom.kt @@ -149,7 +149,10 @@ class RxRoom(private val room: Room) { } fun sendMedia(attachment: ContentAttachmentData, compressBeforeSending: Boolean, roomIds: Set): Completable = rxCompletable { - room.sendMedia(attachment, compressBeforeSending, roomIds) + room.sendMedia( + attachment = attachment, + compressBeforeSending = compressBeforeSending, + roomIds = roomIds) } } 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 6ae42de90cd..a1a5d629589 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 @@ -63,11 +63,13 @@ interface SendService { * @param compressBeforeSending set to true to compress images before sending them * @param roomIds set of roomIds to where the media will be sent. The current roomId will be add to this set if not present. * It can be useful to send media to multiple room. It's safe to include the current roomId in this set + * @param rootThreadEventId when this param is not null, the Media will be sent in this specific thread * @return a [Cancelable] */ fun sendMedia(attachment: ContentAttachmentData, compressBeforeSending: Boolean, - roomIds: Set): Cancelable + roomIds: Set, + rootThreadEventId: String? = null): Cancelable /** * Method to send a list of media asynchronously. @@ -75,11 +77,13 @@ interface SendService { * @param compressBeforeSending set to true to compress images before sending them * @param roomIds set of roomIds to where the media will be sent. The current roomId will be add to this set if not present. * It can be useful to send media to multiple room. It's safe to include the current roomId in this set + * @param rootThreadEventId when this param is not null, all the Media will be sent in this specific thread * @return a [Cancelable] */ fun sendMedias(attachments: List, compressBeforeSending: Boolean, - roomIds: Set): Cancelable + roomIds: Set, + rootThreadEventId: String? = null): Cancelable /** * Send a poll to the room. 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 177c98541c8..860940167b1 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 @@ -236,22 +236,38 @@ internal class DefaultSendService @AssistedInject constructor( override fun sendMedias(attachments: List, compressBeforeSending: Boolean, - roomIds: Set): Cancelable { + roomIds: Set, + rootThreadEventId: String? + ): Cancelable { return attachments.mapTo(CancelableBag()) { - sendMedia(it, compressBeforeSending, roomIds) + sendMedia( + attachment = it, + compressBeforeSending = compressBeforeSending, + roomIds = roomIds, + rootThreadEventId = rootThreadEventId) } } override fun sendMedia(attachment: ContentAttachmentData, compressBeforeSending: Boolean, - roomIds: Set): Cancelable { + roomIds: Set, + rootThreadEventId: String? + ): Cancelable { + + // Ensure that the event will not be send in a thread if we are a different flow. + // Like sending files to multiple rooms + val rootThreadId = if (roomIds.isNotEmpty()) null else rootThreadEventId + // Create an event with the media file path // Ensure current roomId is included in the set val allRoomIds = (roomIds + roomId).toList() // Create local echo for each room val allLocalEchoes = allRoomIds.map { - localEchoEventFactory.createMediaEvent(it, attachment).also { event -> + localEchoEventFactory.createMediaEvent( + roomId = it, + attachment = attachment, + rootThreadEventId = rootThreadId).also { event -> createLocalEcho(event) } } 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 5741d0f5ba9..005f3779431 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 @@ -197,12 +197,15 @@ internal class LocalEchoEventFactory @Inject constructor( )) } - fun createMediaEvent(roomId: String, attachment: ContentAttachmentData): Event { + fun createMediaEvent(roomId: String, + attachment: ContentAttachmentData, + rootThreadEventId: String? + ): Event { return when (attachment.type) { - ContentAttachmentData.Type.IMAGE -> createImageEvent(roomId, attachment) - ContentAttachmentData.Type.VIDEO -> createVideoEvent(roomId, attachment) - ContentAttachmentData.Type.AUDIO -> createAudioEvent(roomId, attachment) - ContentAttachmentData.Type.FILE -> createFileEvent(roomId, attachment) + ContentAttachmentData.Type.IMAGE -> createImageEvent(roomId, attachment, rootThreadEventId) + ContentAttachmentData.Type.VIDEO -> createVideoEvent(roomId, attachment, rootThreadEventId) + ContentAttachmentData.Type.AUDIO -> createAudioEvent(roomId, attachment, rootThreadEventId) + ContentAttachmentData.Type.FILE -> createFileEvent(roomId, attachment, rootThreadEventId) } } @@ -225,7 +228,7 @@ internal class LocalEchoEventFactory @Inject constructor( unsignedData = UnsignedData(age = null, transactionId = localId)) } - private fun createImageEvent(roomId: String, attachment: ContentAttachmentData): Event { + private fun createImageEvent(roomId: String, attachment: ContentAttachmentData, rootThreadEventId: String?): Event { var width = attachment.width var height = attachment.height @@ -249,12 +252,13 @@ internal class LocalEchoEventFactory @Inject constructor( height = height?.toInt() ?: 0, size = attachment.size ), - url = attachment.queryUri.toString() + url = attachment.queryUri.toString(), + relatesTo = rootThreadEventId?.let { RelationDefaultContent(RelationType.THREAD, it) } ) return createMessageEvent(roomId, content) } - private fun createVideoEvent(roomId: String, attachment: ContentAttachmentData): Event { + private fun createVideoEvent(roomId: String, attachment: ContentAttachmentData, rootThreadEventId: String?): Event { val mediaDataRetriever = MediaMetadataRetriever() mediaDataRetriever.setDataSource(context, attachment.queryUri) @@ -285,12 +289,13 @@ internal class LocalEchoEventFactory @Inject constructor( thumbnailUrl = attachment.queryUri.toString(), thumbnailInfo = thumbnailInfo ), - url = attachment.queryUri.toString() + url = attachment.queryUri.toString(), + relatesTo = rootThreadEventId?.let { RelationDefaultContent(RelationType.THREAD, it) } ) return createMessageEvent(roomId, content) } - private fun createAudioEvent(roomId: String, attachment: ContentAttachmentData): Event { + private fun createAudioEvent(roomId: String, attachment: ContentAttachmentData, rootThreadEventId: String?): Event { val isVoiceMessage = attachment.waveform != null val content = MessageAudioContent( msgType = MessageType.MSGTYPE_AUDIO, @@ -305,12 +310,13 @@ internal class LocalEchoEventFactory @Inject constructor( duration = attachment.duration?.toInt(), waveform = waveformSanitizer.sanitize(attachment.waveform) ), - voiceMessageIndicator = if (!isVoiceMessage) null else emptyMap() + voiceMessageIndicator = if (!isVoiceMessage) null else emptyMap(), + relatesTo = rootThreadEventId?.let { RelationDefaultContent(RelationType.THREAD, it) } ) return createMessageEvent(roomId, content) } - private fun createFileEvent(roomId: String, attachment: ContentAttachmentData): Event { + private fun createFileEvent(roomId: String, attachment: ContentAttachmentData, rootThreadEventId: String?): Event { val content = MessageFileContent( msgType = MessageType.MSGTYPE_FILE, body = attachment.name ?: "file", @@ -318,7 +324,8 @@ internal class LocalEchoEventFactory @Inject constructor( mimeType = attachment.getSafeMimeType()?.takeIf { it.isNotBlank() }, size = attachment.size ), - url = attachment.queryUri.toString() + url = attachment.queryUri.toString(), + relatesTo = rootThreadEventId?.let { RelationDefaultContent(RelationType.THREAD, it) } ) return createMessageEvent(roomId, content) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index 28b5e88a820..ca7ed4b6ec3 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -531,9 +531,9 @@ class RoomDetailViewModel @AssistedInject constructor( val isAllowed = action.userJustAccepted || if (widget.type == WidgetType.Jitsi) { widget.senderInfo?.userId == session.myUserId || session.integrationManagerService().isNativeWidgetDomainAllowed( - action.widget.type.preferred, - domain - ) + action.widget.type.preferred, + domain + ) } else false if (isAllowed) { @@ -626,7 +626,11 @@ class RoomDetailViewModel @AssistedInject constructor( } else { voiceMessageHelper.stopRecording()?.let { audioType -> if (audioType.duration > 1000) { - room.sendMedia(audioType.toContentAttachmentData(), false, emptySet()) + room.sendMedia( + attachment = audioType.toContentAttachmentData(), + compressBeforeSending = false, + roomIds = emptySet(), + rootThreadEventId = initialState.rootThreadEventId) } else { voiceMessageHelper.deleteRecording() } @@ -705,7 +709,12 @@ class RoomDetailViewModel @AssistedInject constructor( } private fun handleSendMedia(action: RoomDetailAction.SendMedia) { - room.sendMedias(action.attachments, action.compressBeforeSending, emptySet()) + room.sendMedias( + action.attachments, + action.compressBeforeSending, + emptySet(), + initialState.rootThreadEventId + ) } private fun handleEventVisible(action: RoomDetailAction.TimelineEventTurnsVisible) { From 586b3d8caad38bf1ccc14b54dd3129c60d5d4a66 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Mon, 22 Nov 2021 14:02:17 +0200 Subject: [PATCH 010/581] - Add specific toolbar for threads - Renamed RoomDetailFragment to TimelineFragment while it should be reused from threads - View root thread message in room functionality --- .../debug/res/layout/fragment_thread_list.xml | 14 ++ vector/src/main/AndroidManifest.xml | 3 +- .../im/vector/app/core/di/FragmentModule.kt | 12 +- .../im/vector/app/core/di/ScreenComponent.kt | 6 +- .../app/features/call/VectorCallActivity.kt | 4 +- .../IncomingVerificationRequestHandler.kt | 4 +- .../home/room/detail/RoomDetailActivity.kt | 17 +- .../home/room/detail/RoomDetailViewModel.kt | 34 ++- .../home/room/detail/RoomDetailViewState.kt | 8 +- ...mDetailFragment.kt => TimelineFragment.kt} | 230 +++++++++++------- .../room/detail/arguments/TimelineArgs.kt | 31 +++ .../detail/composer/TextComposerViewModel.kt | 5 +- .../detail/composer/TextComposerViewState.kt | 6 +- .../timeline/TimelineEventController.kt | 2 +- .../helper/MessageItemAttributesFactory.kt | 2 +- .../home/room/threads/RoomThreadsActivity.kt | 66 ----- .../home/room/threads/ThreadsActivity.kt | 132 ++++++++++ .../room/threads/arguments/ThreadListArgs.kt | 25 ++ .../ThreadTimelineArgs.kt} | 6 +- .../detail/RoomThreadDetailActivity.kt | 95 -------- ...etailFragment.kt => ThreadListFragment.kt} | 19 +- .../features/navigation/DefaultNavigator.kt | 18 +- .../app/features/navigation/Navigator.kt | 4 + .../notifications/NotificationUtils.kt | 6 +- .../res/drawable/ic_thread_link_menu_item.xml | 12 + .../main/res/drawable/ic_thread_menu_item.xml | 10 + .../drawable/ic_thread_share_menu_item.xml | 19 ++ .../ic_thread_view_in_room_menu_item.xml | 30 +++ .../layout/activity_room_thread_detail.xml | 88 ------- .../main/res/layout/activity_room_threads.xml | 88 ------- .../src/main/res/layout/activity_threads.xml | 24 ++ .../main/res/layout/fragment_room_detail.xml | 100 +------- .../layout/fragment_room_thread_detail.xml | 31 --- .../view_room_detail_thread_toolbar.xml | 51 ++++ .../res/layout/view_room_detail_toolbar.xml | 99 ++++++++ .../main/res/menu/menu_thread_timeline.xml | 36 +++ vector/src/main/res/menu/menu_timeline.xml | 47 +++- vector/src/main/res/values/strings.xml | 7 + 38 files changed, 766 insertions(+), 625 deletions(-) create mode 100644 vector/src/debug/res/layout/fragment_thread_list.xml rename vector/src/main/java/im/vector/app/features/home/room/detail/{RoomDetailFragment.kt => TimelineFragment.kt} (93%) create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/arguments/TimelineArgs.kt delete mode 100644 vector/src/main/java/im/vector/app/features/home/room/threads/RoomThreadsActivity.kt create mode 100644 vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt create mode 100644 vector/src/main/java/im/vector/app/features/home/room/threads/arguments/ThreadListArgs.kt rename vector/src/main/java/im/vector/app/features/home/room/threads/{detail/arguments/RoomThreadDetailArgs.kt => arguments/ThreadTimelineArgs.kt} (85%) delete mode 100644 vector/src/main/java/im/vector/app/features/home/room/threads/detail/RoomThreadDetailActivity.kt rename vector/src/main/java/im/vector/app/features/home/room/threads/detail/{RoomThreadDetailFragment.kt => ThreadListFragment.kt} (74%) create mode 100644 vector/src/main/res/drawable/ic_thread_link_menu_item.xml create mode 100644 vector/src/main/res/drawable/ic_thread_menu_item.xml create mode 100644 vector/src/main/res/drawable/ic_thread_share_menu_item.xml create mode 100644 vector/src/main/res/drawable/ic_thread_view_in_room_menu_item.xml delete mode 100644 vector/src/main/res/layout/activity_room_thread_detail.xml delete mode 100644 vector/src/main/res/layout/activity_room_threads.xml create mode 100644 vector/src/main/res/layout/activity_threads.xml delete mode 100644 vector/src/main/res/layout/fragment_room_thread_detail.xml create mode 100644 vector/src/main/res/layout/view_room_detail_thread_toolbar.xml create mode 100644 vector/src/main/res/layout/view_room_detail_toolbar.xml create mode 100644 vector/src/main/res/menu/menu_thread_timeline.xml diff --git a/vector/src/debug/res/layout/fragment_thread_list.xml b/vector/src/debug/res/layout/fragment_thread_list.xml new file mode 100644 index 00000000000..cf3a79e7760 --- /dev/null +++ b/vector/src/debug/res/layout/fragment_thread_list.xml @@ -0,0 +1,14 @@ + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index 2b7b445ad5b..f0e68e84465 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -179,8 +179,7 @@ - - + (), CallContro private fun returnToChat() { val roomId = withState(callViewModel) { it.roomId } - val args = RoomDetailArgs(roomId) + val args = TimelineArgs(roomId) val intent = RoomDetailActivity.newIntent(this, args).apply { flags = FLAG_ACTIVITY_CLEAR_TOP } diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/IncomingVerificationRequestHandler.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/IncomingVerificationRequestHandler.kt index 6c009d37866..ea0fb3f0ca8 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/IncomingVerificationRequestHandler.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/IncomingVerificationRequestHandler.kt @@ -21,7 +21,7 @@ import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.features.displayname.getBestName import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.RoomDetailActivity -import im.vector.app.features.home.room.detail.RoomDetailArgs +import im.vector.app.features.home.room.detail.arguments.TimelineArgs import im.vector.app.features.popup.PopupAlertManager import im.vector.app.features.popup.VerificationVectorAlert import org.matrix.android.sdk.api.session.Session @@ -142,7 +142,7 @@ class IncomingVerificationRequestHandler @Inject constructor( R.drawable.ic_shield_black, shouldBeDisplayedIn = { activity -> if (activity is RoomDetailActivity) { - activity.intent?.extras?.getParcelable(RoomDetailActivity.EXTRA_ROOM_DETAIL_ARGS)?.let { + activity.intent?.extras?.getParcelable(RoomDetailActivity.EXTRA_ROOM_DETAIL_ARGS)?.let { it.roomId != pr.roomId } ?: true } else true diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt index 76c3816ce67..d9a10d8745e 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt @@ -34,6 +34,7 @@ import im.vector.app.core.platform.ToolbarConfigurable import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.databinding.ActivityRoomDetailBinding import im.vector.app.features.home.room.breadcrumbs.BreadcrumbsFragment +import im.vector.app.features.home.room.detail.arguments.TimelineArgs import im.vector.app.features.matrixto.MatrixToBottomSheet import im.vector.app.features.navigation.Navigator import im.vector.app.features.room.RequireActiveMembershipAction @@ -102,16 +103,16 @@ class RoomDetailActivity : super.onCreate(savedInstanceState) supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, false) waitingView = views.waitingView.waitingView - val roomDetailArgs: RoomDetailArgs? = if (intent?.action == ACTION_ROOM_DETAILS_FROM_SHORTCUT) { - RoomDetailArgs(roomId = intent?.extras?.getString(EXTRA_ROOM_ID)!!) + val timelineArgs: TimelineArgs? = if (intent?.action == ACTION_ROOM_DETAILS_FROM_SHORTCUT) { + TimelineArgs(roomId = intent?.extras?.getString(EXTRA_ROOM_ID)!!) } else { intent?.extras?.getParcelable(EXTRA_ROOM_DETAIL_ARGS) } - if (roomDetailArgs == null) return - currentRoomId = roomDetailArgs.roomId + if (timelineArgs == null) return + currentRoomId = timelineArgs.roomId if (isFirstCreation()) { - replaceFragment(R.id.roomDetailContainer, RoomDetailFragment::class.java, roomDetailArgs) + replaceFragment(R.id.roomDetailContainer, TimelineFragment::class.java, timelineArgs) replaceFragment(R.id.roomDetailDrawerContainer, BreadcrumbsFragment::class.java) } @@ -147,7 +148,7 @@ class RoomDetailActivity : if (currentRoomId != switchToRoom.roomId) { currentRoomId = switchToRoom.roomId requireActiveMembershipViewModel.handle(RequireActiveMembershipAction.ChangeRoom(switchToRoom.roomId)) - replaceFragment(R.id.roomDetailContainer, RoomDetailFragment::class.java, RoomDetailArgs(switchToRoom.roomId)) + replaceFragment(R.id.roomDetailContainer, TimelineFragment::class.java, TimelineArgs(switchToRoom.roomId)) } } @@ -191,9 +192,9 @@ class RoomDetailActivity : const val EXTRA_ROOM_ID = "EXTRA_ROOM_ID" const val ACTION_ROOM_DETAILS_FROM_SHORTCUT = "ROOM_DETAILS_FROM_SHORTCUT" - fun newIntent(context: Context, roomDetailArgs: RoomDetailArgs): Intent { + fun newIntent(context: Context, timelineArgs: TimelineArgs): Intent { return Intent(context, RoomDetailActivity::class.java).apply { - putExtra(EXTRA_ROOM_DETAIL_ARGS, roomDetailArgs) + putExtra(EXTRA_ROOM_DETAIL_ARGS, timelineArgs) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index ca7ed4b6ec3..5854d35fb6a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -153,7 +153,7 @@ class RoomDetailViewModel @AssistedInject constructor( @JvmStatic override fun create(viewModelContext: ViewModelContext, state: RoomDetailViewState): RoomDetailViewModel? { - val fragment: RoomDetailFragment = (viewModelContext as FragmentViewModelContext).fragment() + val fragment: TimelineFragment = (viewModelContext as FragmentViewModelContext).fragment() return fragment.roomDetailViewModelFactory.create(state) } @@ -668,20 +668,30 @@ class RoomDetailViewModel @AssistedInject constructor( private fun isIntegrationEnabled() = session.integrationManagerService().isIntegrationEnabled() fun isMenuItemVisible(@IdRes itemId: Int): Boolean = com.airbnb.mvrx.withState(this) { state -> + if (state.asyncRoomSummary()?.membership != Membership.JOIN) { return@withState false } - when (itemId) { - R.id.timeline_setting -> true - R.id.invite -> state.canInvite - R.id.open_matrix_apps -> true - R.id.voice_call -> state.isWebRTCCallOptionAvailable() - R.id.video_call -> state.isWebRTCCallOptionAvailable() || state.jitsiState.confId == null || state.jitsiState.hasJoined - // Show Join conference button only if there is an active conf id not joined. Otherwise fallback to default video disabled. ^ - R.id.join_conference -> !state.isWebRTCCallOptionAvailable() && state.jitsiState.confId != null && !state.jitsiState.hasJoined - R.id.search -> true - R.id.dev_tools -> vectorPreferences.developerMode() - else -> false + + if (initialState.isThreadTimeline()) { + when (itemId) { + R.id.menu_thread_timeline_more -> true + else -> false + } + } else { + when (itemId) { + R.id.timeline_setting -> true + R.id.invite -> state.canInvite + R.id.open_matrix_apps -> true + R.id.voice_call -> state.isWebRTCCallOptionAvailable() + R.id.video_call -> state.isWebRTCCallOptionAvailable() || state.jitsiState.confId == null || state.jitsiState.hasJoined + // Show Join conference button only if there is an active conf id not joined. Otherwise fallback to default video disabled. ^ + R.id.join_conference -> !state.isWebRTCCallOptionAvailable() && state.jitsiState.confId != null && !state.jitsiState.hasJoined + R.id.search -> true + R.id.threads -> true + R.id.dev_tools -> vectorPreferences.developerMode() + else -> false + } } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt index 1848f1b28e9..fa772ca0732 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 @@ -19,6 +19,7 @@ package im.vector.app.features.home.room.detail 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 org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.initsync.SyncStatusService @@ -69,12 +70,12 @@ data class RoomDetailViewState( val rootThreadEventId: String? = null ) : MavericksState { - constructor(args: RoomDetailArgs) : this( + constructor(args: TimelineArgs) : this( roomId = args.roomId, eventId = args.eventId, // Also highlight the target event, if any highlightedEventId = args.eventId, - rootThreadEventId = args.roomThreadDetailArgs?.eventId + rootThreadEventId = args.threadTimelineArgs?.rootThreadEventId ) fun isWebRTCCallOptionAvailable() = (asyncRoomSummary.invoke()?.joinedMembersCount ?: 0) <= 2 @@ -84,4 +85,7 @@ data class RoomDetailViewState( fun hasActiveJitsiWidget() = activeRoomWidgets()?.any { it.type == WidgetType.Jitsi && it.isActive }.orFalse() fun isDm() = asyncRoomSummary()?.isDirect == true + + fun isThreadTimeline() = rootThreadEventId != null + } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt similarity index 93% rename from vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt rename to vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt index 6dfe29cec6f..9afd5a2fc93 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt @@ -25,7 +25,6 @@ import android.graphics.Typeface import android.net.Uri import android.os.Build import android.os.Bundle -import android.os.Parcelable import android.text.Spannable import android.text.format.DateUtils import android.view.HapticFeedbackConstants @@ -49,7 +48,6 @@ import androidx.core.text.toSpannable import androidx.core.util.Pair import androidx.core.view.ViewCompat import androidx.core.view.forEach -import androidx.core.view.isGone import androidx.core.view.isInvisible import androidx.core.view.isVisible import androidx.fragment.app.setFragmentResultListener @@ -134,6 +132,7 @@ import im.vector.app.features.command.Command 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.SendMode import im.vector.app.features.home.room.detail.composer.TextComposerAction import im.vector.app.features.home.room.detail.composer.TextComposerView @@ -162,8 +161,7 @@ import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever import im.vector.app.features.home.room.detail.upgrade.MigrateRoomBottomSheet import im.vector.app.features.home.room.detail.views.RoomDetailLazyLoadedViews import im.vector.app.features.home.room.detail.widget.RoomWidgetsBottomSheet -import im.vector.app.features.home.room.threads.detail.arguments.RoomThreadDetailArgs -import im.vector.app.features.home.room.threads.detail.RoomThreadDetailActivity +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 @@ -188,7 +186,6 @@ 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.launch -import kotlinx.parcelize.Parcelize import nl.dionsegijn.konfetti.models.Shape import nl.dionsegijn.konfetti.models.Size import org.billcarsonfr.jsonviewer.JSonViewerDialog @@ -224,16 +221,7 @@ import java.util.UUID import java.util.concurrent.TimeUnit import javax.inject.Inject -@Parcelize -data class RoomDetailArgs( - val roomId: String, - val eventId: String? = null, - val sharedData: SharedData? = null, - val openShareSpaceForId: String? = null, - val roomThreadDetailArgs: RoomThreadDetailArgs? = null -) : Parcelable - -class RoomDetailFragment @Inject constructor( +class TimelineFragment @Inject constructor( private val session: Session, private val avatarRenderer: AvatarRenderer, private val timelineEventController: TimelineEventController, @@ -282,16 +270,16 @@ class RoomDetailFragment @Inject constructor( private val galleryOrCameraDialogHelper = GalleryOrCameraDialogHelper(this, colorProvider) - private val roomDetailArgs: RoomDetailArgs by args() + private val timelineArgs: TimelineArgs by args() private val glideRequests by lazy { GlideApp.with(this) } private val pillsPostProcessor by lazy { - pillsPostProcessorFactory.create(roomDetailArgs.roomId) + pillsPostProcessorFactory.create(timelineArgs.roomId) } private val autoCompleter: AutoCompleter by lazy { - autoCompleterFactory.create(roomDetailArgs.roomId, isThreadTimeLine()) + autoCompleterFactory.create(timelineArgs.roomId, isThreadTimeLine()) } private val roomDetailViewModel: RoomDetailViewModel by fragmentViewModel() @@ -308,6 +296,8 @@ class RoomDetailFragment @Inject constructor( override fun getMenuRes() = R.menu.menu_timeline private lateinit var sharedActionViewModel: MessageSharedActionViewModel + private lateinit var sharedActivityActionViewModel: RoomDetailSharedActionViewModel + private lateinit var knownCallsViewModel: SharedKnownCallsViewModel private lateinit var layoutManager: LinearLayoutManager @@ -341,10 +331,11 @@ class RoomDetailFragment @Inject constructor( lifecycle.addObserver(ConferenceEventObserver(vectorBaseActivity, this::onBroadcastJitsiEvent)) super.onViewCreated(view, savedInstanceState) sharedActionViewModel = activityViewModelProvider.get(MessageSharedActionViewModel::class.java) + sharedActivityActionViewModel = activityViewModelProvider.get(RoomDetailSharedActionViewModel::class.java) knownCallsViewModel = activityViewModelProvider.get(SharedKnownCallsViewModel::class.java) attachmentsHelper = AttachmentsHelper(requireContext(), this).register() callActionsHandler = StartCallActionsHandler( - roomId = roomDetailArgs.roomId, + roomId = timelineArgs.roomId, fragment = this, vectorPreferences = vectorPreferences, roomDetailViewModel = roomDetailViewModel, @@ -355,11 +346,7 @@ class RoomDetailFragment @Inject constructor( ) keyboardStateUtils = KeyboardStateUtils(requireActivity()) lazyLoadedViews.bind(views) - if (isThreadTimeLine()) { - views.roomToolbar.isGone = true - } else { - setupToolbar(views.roomToolbar) - } + setupToolbar(views.roomToolbar) setupRecyclerView() setupComposer() setupNotificationView() @@ -370,8 +357,8 @@ class RoomDetailFragment @Inject constructor( setupRemoveJitsiWidgetView() setupVoiceMessageView() - views.roomToolbarContentView.debouncedClicks { - navigator.openRoomProfile(requireActivity(), roomDetailArgs.roomId) + views.includeRoomToolbar.roomToolbarContentView.debouncedClicks { + navigator.openRoomProfile(requireActivity(), timelineArgs.roomId) } sharedActionViewModel @@ -456,7 +443,7 @@ class RoomDetailFragment @Inject constructor( RoomDetailViewEvents.HideWaitingView -> vectorBaseActivity.hideWaitingView() is RoomDetailViewEvents.RequestNativeWidgetPermission -> requestNativeWidgetPermission(it) is RoomDetailViewEvents.OpenRoom -> handleOpenRoom(it) - RoomDetailViewEvents.OpenInvitePeople -> navigator.openInviteUsersToRoom(requireContext(), roomDetailArgs.roomId) + RoomDetailViewEvents.OpenInvitePeople -> navigator.openInviteUsersToRoom(requireContext(), timelineArgs.roomId) RoomDetailViewEvents.OpenSetRoomAvatarDialog -> galleryOrCameraDialogHelper.show() RoomDetailViewEvents.OpenRoomSettings -> handleOpenRoomSettings() is RoomDetailViewEvents.ShowRoomAvatarFullScreen -> it.matrixItem?.let { item -> @@ -532,7 +519,7 @@ class RoomDetailFragment @Inject constructor( private fun handleShowRoomUpgradeDialog(roomDetailViewEvents: TextComposerViewEvents.ShowRoomUpgradeDialog) { val tag = MigrateRoomBottomSheet::javaClass.name - MigrateRoomBottomSheet.newInstance(roomDetailArgs.roomId, roomDetailViewEvents.newVersion) + MigrateRoomBottomSheet.newInstance(timelineArgs.roomId, roomDetailViewEvents.newVersion) .show(parentFragmentManager, tag) } @@ -578,7 +565,7 @@ class RoomDetailFragment @Inject constructor( private fun handleOpenRoomSettings() { navigator.openRoomProfile( requireContext(), - roomDetailArgs.roomId, + timelineArgs.roomId, RoomProfileActivity.EXTRA_DIRECT_ACCESS_ROOM_SETTINGS ) } @@ -600,7 +587,7 @@ class RoomDetailFragment @Inject constructor( WidgetArgs( baseUrl = it.domain, kind = WidgetKind.ROOM, - roomId = roomDetailArgs.roomId, + roomId = timelineArgs.roomId, widgetId = it.widget.widgetId ) ).apply { @@ -626,7 +613,7 @@ class RoomDetailFragment @Inject constructor( navigator.openIntegrationManager( context = requireContext(), activityResultLauncher = integrationManagerActivityResultLauncher, - roomId = roomDetailArgs.roomId, + roomId = timelineArgs.roomId, integId = null, screen = screen ) @@ -717,11 +704,11 @@ class RoomDetailFragment @Inject constructor( } private fun joinJitsiRoom(jitsiWidget: Widget, enableVideo: Boolean) { - navigator.openRoomWidget(requireContext(), roomDetailArgs.roomId, jitsiWidget, mapOf(JitsiCallViewModel.ENABLE_VIDEO_OPTION to enableVideo)) + navigator.openRoomWidget(requireContext(), timelineArgs.roomId, jitsiWidget, mapOf(JitsiCallViewModel.ENABLE_VIDEO_OPTION to enableVideo)) } private fun openStickerPicker(event: RoomDetailViewEvents.OpenStickerPicker) { - navigator.openStickerPicker(requireContext(), stickerActivityResultLauncher, roomDetailArgs.roomId, event.widget) + navigator.openStickerPicker(requireContext(), stickerActivityResultLauncher, timelineArgs.roomId, event.widget) } private fun startOpenFileIntent(action: RoomDetailViewEvents.OpenFile) { @@ -795,7 +782,7 @@ class RoomDetailFragment @Inject constructor( } private fun handleShareData() { - when (val sharedData = roomDetailArgs.sharedData) { + when (val sharedData = timelineArgs.sharedData) { is SharedData.Text -> { textComposerViewModel.handle(TextComposerAction.EnterRegularMode(sharedData.text, fromSharing = true)) } @@ -808,7 +795,7 @@ class RoomDetailFragment @Inject constructor( } private fun handleSpaceShare() { - roomDetailArgs.openShareSpaceForId?.let { spaceId -> + timelineArgs.openShareSpaceForId?.let { spaceId -> ShareSpaceBottomSheet.show(childFragmentManager, spaceId, true) view?.post { handleChatEffect(ChatEffect.CONFETTI) @@ -909,7 +896,6 @@ class RoomDetailFragment @Inject constructor( override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { super.onCreateOptionsMenu(menu, inflater) - if (isThreadTimeLine()) return // We use a custom layout for this menu item, so we need to set a ClickListener menu.findItem(R.id.open_matrix_apps)?.let { menuItem -> menuItem.actionView.setOnClickListener { @@ -923,15 +909,11 @@ class RoomDetailFragment @Inject constructor( } override fun onPrepareOptionsMenu(menu: Menu) { - if (isThreadTimeLine()) { - menu.forEach { - it.isVisible = false - } - return - } + menu.forEach { it.isVisible = roomDetailViewModel.isMenuItemVisible(it.itemId) } + withState(roomDetailViewModel) { state -> // Set the visual state of the call buttons (voice/video) to enabled/disabled according to user permissions val hasCallInRoom = callManager.getCallsByRoomId(state.roomId).isNotEmpty() || state.jitsiState.hasJoined @@ -968,41 +950,72 @@ class RoomDetailFragment @Inject constructor( override fun onOptionsItemSelected(item: MenuItem): Boolean { return when (item.itemId) { - R.id.invite -> { - navigator.openInviteUsersToRoom(requireActivity(), roomDetailArgs.roomId) + R.id.invite -> { + navigator.openInviteUsersToRoom(requireActivity(), timelineArgs.roomId) true } - R.id.timeline_setting -> { - navigator.openRoomProfile(requireActivity(), roomDetailArgs.roomId) + R.id.timeline_setting -> { + navigator.openRoomProfile(requireActivity(), timelineArgs.roomId) true } - R.id.open_matrix_apps -> { + R.id.open_matrix_apps -> { roomDetailViewModel.handle(RoomDetailAction.ManageIntegrations) true } - R.id.voice_call -> { + R.id.voice_call -> { callActionsHandler.onVoiceCallClicked() true } - R.id.video_call -> { + R.id.video_call -> { callActionsHandler.onVideoCallClicked() true } - R.id.search -> { + R.id.threads -> { + requireActivity().toast("View All Threads") + true + } + R.id.search -> { handleSearchAction() true } - R.id.dev_tools -> { - navigator.openDevTools(requireContext(), roomDetailArgs.roomId) + R.id.dev_tools -> { + navigator.openDevTools(requireContext(), timelineArgs.roomId) + true + } + R.id.menu_thread_timeline_copy_link -> { + requireActivity().toast("menu_thread_timeline_copy_link") + true + } + R.id.menu_thread_timeline_view_in_room -> { + handleViewInRoomAction() true } - else -> super.onOptionsItemSelected(item) + R.id.menu_thread_timeline_share -> { + requireActivity().toast("menu_thread_timeline_share") + true + } + else -> super.onOptionsItemSelected(item) + } + } + + /** + * View and highlight the original root thread message in the main timeline + */ + private fun handleViewInRoomAction(){ + getRootThreadEventId()?.let { + val newRoom = timelineArgs.copy(threadTimelineArgs = null,eventId = it) + context?.let{ con -> + val int = RoomDetailActivity.newIntent(con, newRoom) + int.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK + con.startActivity(int) + + } } } private fun handleSearchAction() { - if (session.getRoom(roomDetailArgs.roomId)?.isEncrypted() == false) { - navigator.openSearch(requireContext(), roomDetailArgs.roomId) + if (session.getRoom(timelineArgs.roomId)?.isEncrypted() == false) { + navigator.openSearch(requireContext(), timelineArgs.roomId) } else { showDialogWithMessage(getString(R.string.search_is_not_supported_in_e2e_room)) } @@ -1080,7 +1093,7 @@ class RoomDetailFragment @Inject constructor( override fun onResume() { super.onResume() - notificationDrawerManager.setCurrentRoom(roomDetailArgs.roomId) + notificationDrawerManager.setCurrentRoom(timelineArgs.roomId) roomDetailPendingActionStore.data?.let { handlePendingAction(it) } roomDetailPendingActionStore.data = null @@ -1322,7 +1335,7 @@ class RoomDetailFragment @Inject constructor( views.composerLayout.callback = object : TextComposerView.Callback { override fun onAddAttachment() { if (!::attachmentTypeSelector.isInitialized) { - attachmentTypeSelector = AttachmentTypeSelectorView(vectorBaseActivity, vectorBaseActivity.layoutInflater, this@RoomDetailFragment) + attachmentTypeSelector = AttachmentTypeSelectorView(vectorBaseActivity, vectorBaseActivity.layoutInflater, this@TimelineFragment) } attachmentTypeSelector.show(views.composerLayout.views.attachmentButton, keyboardStateUtils.isKeyboardShowing) } @@ -1420,7 +1433,7 @@ class RoomDetailFragment @Inject constructor( } else if (summary?.membership == Membership.INVITE && inviter != null) { views.hideComposerViews() lazyLoadedViews.inviteView(true)?.apply { - callback = this@RoomDetailFragment + callback = this@TimelineFragment isVisible = true render(inviter, VectorInviteView.Mode.LARGE, mainState.changeMembershipState) setOnClickListener { } @@ -1437,23 +1450,35 @@ class RoomDetailFragment @Inject constructor( } private fun renderToolbar(roomSummary: RoomSummary?, typingMessage: String?) { - if (roomSummary == null) { - views.roomToolbarContentView.isClickable = false + if (!isThreadTimeLine()) { + views.includeRoomToolbar.roomToolbarContentView.isVisible = true + views.includeThreadToolbar.roomToolbarThreadConstraintLayout.isVisible = false + if (roomSummary == null) { + views.includeRoomToolbar.roomToolbarContentView.isClickable = false + } else { + views.includeRoomToolbar.roomToolbarContentView.isClickable = roomSummary.membership == Membership.JOIN + views.includeRoomToolbar.roomToolbarTitleView.text = roomSummary.displayName + avatarRenderer.render(roomSummary.toMatrixItem(), views.includeRoomToolbar.roomToolbarAvatarImageView) + renderSubTitle(typingMessage, roomSummary.topic) + views.includeRoomToolbar.roomToolbarDecorationImageView.render(roomSummary.roomEncryptionTrustLevel) + views.includeRoomToolbar.roomToolbarPresenceImageView.render(roomSummary.isDirect, roomSummary.directUserPresence) + views.includeRoomToolbar.roomToolbarPublicImageView.isVisible = roomSummary.isPublic && !roomSummary.isDirect + } } else { - views.roomToolbarContentView.isClickable = roomSummary.membership == Membership.JOIN - views.roomToolbarTitleView.text = roomSummary.displayName - avatarRenderer.render(roomSummary.toMatrixItem(), views.roomToolbarAvatarImageView) - renderSubTitle(typingMessage, roomSummary.topic) - views.roomToolbarDecorationImageView.render(roomSummary.roomEncryptionTrustLevel) - views.roomToolbarPresenceImageView.render(roomSummary.isDirect, roomSummary.directUserPresence) - views.roomToolbarPublicImageView.isVisible = roomSummary.isPublic && !roomSummary.isDirect + views.includeRoomToolbar.roomToolbarContentView.isVisible = false + views.includeThreadToolbar.roomToolbarThreadConstraintLayout.isVisible = true + timelineArgs.threadTimelineArgs?.let { + val matrixItem = MatrixItem.RoomItem(it.roomId, it.displayName, it.avatarUrl) + avatarRenderer.render(matrixItem, views.includeThreadToolbar.roomToolbarThreadImageView) + views.includeThreadToolbar.roomToolbarThreadSubtitleTextView.text = it.displayName + } } } private fun renderSubTitle(typingMessage: String?, topic: String) { // TODO Temporary place to put typing data val subtitle = typingMessage?.takeIf { it.isNotBlank() } ?: topic - views.roomToolbarSubtitleView.apply { + views.includeRoomToolbar.roomToolbarSubtitleView.apply { setTextOrHide(subtitle) if (typingMessage.isNullOrBlank()) { setTextColor(colorProvider.getColorFromAttribute(R.attr.vctr_content_primary)) @@ -1592,14 +1617,14 @@ class RoomDetailFragment @Inject constructor( is RoomDetailAction.RequestVerification -> { Timber.v("## SAS RequestVerification action") VerificationBottomSheet.withArgs( - roomDetailArgs.roomId, + timelineArgs.roomId, data.userId ).show(parentFragmentManager, "REQ") } is RoomDetailAction.AcceptVerificationRequest -> { Timber.v("## SAS AcceptVerificationRequest action") VerificationBottomSheet.withArgs( - roomDetailArgs.roomId, + timelineArgs.roomId, data.otherUserId, data.transactionId ).show(parentFragmentManager, "REQ") @@ -1609,7 +1634,7 @@ class RoomDetailFragment @Inject constructor( VerificationBottomSheet().apply { arguments = Bundle().apply { putParcelable(Mavericks.KEY_ARG, VerificationBottomSheet.VerificationArgs( - otherUserId, data.transactionId, roomId = roomDetailArgs.roomId)) + otherUserId, data.transactionId, roomId = timelineArgs.roomId)) } }.show(parentFragmentManager, "REQ") } @@ -1624,7 +1649,7 @@ class RoomDetailFragment @Inject constructor( .launch(requireActivity(), url, object : NavigationInterceptor { override fun navToRoom(roomId: String?, eventId: String?, deepLink: Uri?): Boolean { // Same room? - if (roomId == roomDetailArgs.roomId) { + if (roomId == timelineArgs.roomId) { // Navigation to same room if (eventId == null) { showSnackWithMessage(getString(R.string.navigate_to_room_when_already_in_the_room)) @@ -1691,7 +1716,7 @@ class RoomDetailFragment @Inject constructor( override fun onImageMessageClicked(messageImageContent: MessageImageInfoContent, mediaData: ImageContentRenderer.Data, view: View) { navigator.openMediaViewer( activity = requireActivity(), - roomId = roomDetailArgs.roomId, + roomId = timelineArgs.roomId, mediaData = mediaData, view = view ) { pairs -> @@ -1703,7 +1728,7 @@ class RoomDetailFragment @Inject constructor( override fun onVideoMessageClicked(messageVideoContent: MessageVideoContent, mediaData: VideoContentRenderer.Data, view: View) { navigator.openMediaViewer( activity = requireActivity(), - roomId = roomDetailArgs.roomId, + roomId = timelineArgs.roomId, mediaData = mediaData, view = view ) { pairs -> @@ -1738,7 +1763,7 @@ class RoomDetailFragment @Inject constructor( roomDetailViewModel.handle(RoomDetailAction.LoadMoreTimelineEvents(direction)) } - override fun onEventCellClicked(informationData: MessageInformationData, messageContent: Any?, view: View) { + override fun onEventCellClicked(informationData: MessageInformationData, messageContent: Any?, view: View, isRootThreadEvent: Boolean) { when (messageContent) { is MessageVerificationRequestContent -> { roomDetailViewModel.handle(RoomDetailAction.ResumeVerification(informationData.eventId, null)) @@ -1751,11 +1776,14 @@ class RoomDetailFragment @Inject constructor( roomDetailViewModel.handle(RoomDetailAction.TapOnFailedToDecrypt(informationData.eventId)) } } + if (isRootThreadEvent) { + navigateToThreadTimeline(informationData.eventId) + } } override fun onEventLongClicked(informationData: MessageInformationData, messageContent: Any?, view: View): Boolean { view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) - val roomId = roomDetailArgs.roomId + val roomId = timelineArgs.roomId this.view?.hideKeyboard() MessageActionsBottomSheet @@ -1786,7 +1814,7 @@ class RoomDetailFragment @Inject constructor( } private fun openRoomMemberProfile(userId: String) { - navigator.openRoomMemberProfile(userId = userId, roomId = roomDetailArgs.roomId, context = requireActivity()) + navigator.openRoomMemberProfile(userId = userId, roomId = timelineArgs.roomId, context = requireActivity()) } override fun onMemberNameClicked(informationData: MessageInformationData) { @@ -1804,12 +1832,12 @@ class RoomDetailFragment @Inject constructor( } override fun onLongClickOnReactionPill(informationData: MessageInformationData, reaction: String) { - ViewReactionsBottomSheet.newInstance(roomDetailArgs.roomId, informationData) + ViewReactionsBottomSheet.newInstance(timelineArgs.roomId, informationData) .show(requireActivity().supportFragmentManager, "DISPLAY_REACTIONS") } override fun onEditedDecorationClicked(informationData: MessageInformationData) { - ViewEditHistoryBottomSheet.newInstance(roomDetailArgs.roomId, informationData) + ViewEditHistoryBottomSheet.newInstance(timelineArgs.roomId, informationData) .show(requireActivity().supportFragmentManager, "DISPLAY_EDITS") } @@ -1921,7 +1949,7 @@ class RoomDetailFragment @Inject constructor( emojiActivityResultLauncher.launch(EmojiReactionPickerActivity.intent(requireContext(), action.eventId)) } is EventSharedAction.ViewReactions -> { - ViewReactionsBottomSheet.newInstance(roomDetailArgs.roomId, action.messageInformationData) + ViewReactionsBottomSheet.newInstance(timelineArgs.roomId, action.messageInformationData) .show(requireActivity().supportFragmentManager, "DISPLAY_REACTIONS") } is EventSharedAction.Copy -> { @@ -1978,20 +2006,13 @@ class RoomDetailFragment @Inject constructor( } is EventSharedAction.ReplyInThread -> { if (!views.voiceMessageRecorderView.isActive()) { - context?.let { - val roomThreadDetailArgs = RoomThreadDetailArgs( - roomId = roomDetailArgs.roomId, - displayName = roomDetailViewModel.getRoomSummary()?.displayName, - avatarUrl = roomDetailViewModel.getRoomSummary()?.avatarUrl, - eventId = action.eventId) - startActivity(RoomThreadDetailActivity.newIntent(it, roomThreadDetailArgs)) - } + navigateToThreadTimeline(action.eventId) } else { requireActivity().toast(R.string.error_voice_message_cannot_reply_or_edit) } } is EventSharedAction.CopyPermalink -> { - val permalink = session.permalinkService().createPermalink(roomDetailArgs.roomId, action.eventId) + val permalink = session.permalinkService().createPermalink(timelineArgs.roomId, action.eventId) copyToClipboard(requireContext(), permalink, false) showSnackWithMessage(getString(R.string.copied_to_clipboard)) } @@ -2114,15 +2135,31 @@ class RoomDetailFragment @Inject constructor( .show() } + /** + * Navigate to Threads timeline for the specified threadRootEventId + * using the RoomThreadDetailActivity + */ + + private fun navigateToThreadTimeline(rootThreadEventId: String) { + context?.let { + val roomThreadDetailArgs = ThreadTimelineArgs( + roomId = timelineArgs.roomId, + displayName = roomDetailViewModel.getRoomSummary()?.displayName, + avatarUrl = roomDetailViewModel.getRoomSummary()?.avatarUrl, + rootThreadEventId = rootThreadEventId) + navigator.openThread(it, roomThreadDetailArgs) + } + } + // VectorInviteView.Callback override fun onAcceptInvite() { - notificationDrawerManager.clearMemberShipNotificationForRoom(roomDetailArgs.roomId) + notificationDrawerManager.clearMemberShipNotificationForRoom(timelineArgs.roomId) roomDetailViewModel.handle(RoomDetailAction.AcceptInvite) } override fun onRejectInvite() { - notificationDrawerManager.clearMemberShipNotificationForRoom(roomDetailArgs.roomId) + notificationDrawerManager.clearMemberShipNotificationForRoom(timelineArgs.roomId) roomDetailViewModel.handle(RoomDetailAction.RejectInvite) } @@ -2221,6 +2258,13 @@ class RoomDetailFragment @Inject constructor( } } - private fun isThreadTimeLine(): Boolean = roomDetailArgs.roomThreadDetailArgs != null - fun getRootThreadEventId(): String? = roomDetailArgs.roomThreadDetailArgs?.eventId + /** + * Returns true if the current room is a Thread room, false otherwise + */ + private fun isThreadTimeLine(): Boolean = timelineArgs.threadTimelineArgs?.rootThreadEventId != null + + /** + * 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/java/im/vector/app/features/home/room/detail/arguments/TimelineArgs.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/arguments/TimelineArgs.kt new file mode 100644 index 00000000000..26455e04c72 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/arguments/TimelineArgs.kt @@ -0,0 +1,31 @@ +/* + * 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.home.room.detail.arguments + +import android.os.Parcelable +import im.vector.app.features.home.room.threads.arguments.ThreadTimelineArgs +import im.vector.app.features.share.SharedData +import kotlinx.parcelize.Parcelize + +@Parcelize +data class TimelineArgs( + val roomId: String, + val eventId: String? = null, + val sharedData: SharedData? = null, + val openShareSpaceForId: String? = null, + val threadTimelineArgs: ThreadTimelineArgs? = null +) : Parcelable diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt index bcc26247a2f..af870c3313d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt @@ -29,7 +29,7 @@ import im.vector.app.core.resources.StringProvider import im.vector.app.features.command.CommandParser import im.vector.app.features.command.ParsedCommand import im.vector.app.features.home.room.detail.ChatEffect -import im.vector.app.features.home.room.detail.RoomDetailFragment +import im.vector.app.features.home.room.detail.TimelineFragment import im.vector.app.features.home.room.detail.composer.rainbow.RainbowGenerator import im.vector.app.features.home.room.detail.toMessageType import im.vector.app.features.powerlevel.PowerLevelsFlowFactory @@ -42,7 +42,6 @@ import org.commonmark.renderer.html.HtmlRenderer 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.EventType -import org.matrix.android.sdk.api.session.events.model.getRootThreadEventId 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.PowerLevelsContent @@ -772,7 +771,7 @@ class TextComposerViewModel @AssistedInject constructor( @JvmStatic override fun create(viewModelContext: ViewModelContext, state: TextComposerViewState): TextComposerViewModel { - val fragment: RoomDetailFragment = (viewModelContext as FragmentViewModelContext).fragment() + val fragment: TimelineFragment = (viewModelContext as FragmentViewModelContext).fragment() return fragment.textComposerViewModelFactory.create(state) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewState.kt index f4dd5adebeb..0e8d9e1e86b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewState.kt @@ -17,7 +17,7 @@ package im.vector.app.features.home.room.detail.composer import com.airbnb.mvrx.MavericksState -import im.vector.app.features.home.room.detail.RoomDetailArgs +import im.vector.app.features.home.room.detail.arguments.TimelineArgs import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent /** @@ -53,9 +53,9 @@ data class TextComposerViewState( val isComposerVisible: Boolean get() = canSendMessage && !isVoiceRecording - constructor(args: RoomDetailArgs) : this( + constructor(args: TimelineArgs) : this( roomId = args.roomId, - rootThreadEventId = args.roomThreadDetailArgs?.eventId) + rootThreadEventId = args.threadTimelineArgs?.rootThreadEventId) fun isInThreadTimeline(): Boolean = rootThreadEventId != null } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt index d08259d7392..11c90b34827 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt @@ -140,7 +140,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec } interface BaseCallback { - fun onEventCellClicked(informationData: MessageInformationData, messageContent: Any?, view: View) + fun onEventCellClicked(informationData: MessageInformationData, messageContent: Any?, view: View, isRootThreadEvent: Boolean) fun onEventLongClicked(informationData: MessageInformationData, messageContent: Any?, view: View): Boolean } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt index 11061cbc9ac..a30a0b851e5 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt @@ -43,7 +43,7 @@ class MessageItemAttributesFactory @Inject constructor( callback?.onEventLongClicked(informationData, messageContent, view) ?: false }, itemClickListener = { view -> - callback?.onEventCellClicked(informationData, messageContent, view) + callback?.onEventCellClicked(informationData, messageContent, view, threadDetails?.isRootThread ?: false) }, memberClickListener = { callback?.onMemberNameClicked(informationData) diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/RoomThreadsActivity.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/RoomThreadsActivity.kt deleted file mode 100644 index 0ad1d02ffb3..00000000000 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/RoomThreadsActivity.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 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.home.room.threads - -import android.content.Context -import android.content.Intent -import android.os.Bundle -import androidx.appcompat.widget.SearchView -import im.vector.app.R -import im.vector.app.core.di.ScreenComponent -import im.vector.app.core.extensions.replaceFragment -import im.vector.app.core.platform.VectorBaseActivity -import im.vector.app.databinding.ActivityFilteredRoomsBinding -import im.vector.app.databinding.ActivityRoomThreadsBinding -import im.vector.app.features.home.RoomListDisplayMode -import im.vector.app.features.home.room.list.RoomListFragment -import im.vector.app.features.home.room.list.RoomListParams - -class RoomThreadsActivity : VectorBaseActivity() { - -// private val roomListFragment: RoomListFragment? -// get() { -// return supportFragmentManager.findFragmentByTag(FRAGMENT_TAG) as? RoomListFragment -// } - - override fun getBinding() = ActivityRoomThreadsBinding.inflate(layoutInflater) - - override fun getCoordinatorLayout() = views.coordinatorLayout - - override fun injectWith(injector: ScreenComponent) { - injector.inject(this) - } - - override fun getMenuRes() = R.menu.menu_room_threads - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - configureToolbar(views.roomThreadsToolbar) -// if (isFirstCreation()) { -// val params = RoomListParams(RoomListDisplayMode.FILTERED) -// replaceFragment(R.id.filteredRoomsFragmentContainer, RoomListFragment::class.java, params, FRAGMENT_TAG) -// } - } - - companion object { - private const val FRAGMENT_TAG = "RoomListFragment" - - fun newIntent(context: Context): Intent { - return Intent(context, RoomThreadsActivity::class.java) - } - } -} diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt new file mode 100644 index 00000000000..73da1354af8 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt @@ -0,0 +1,132 @@ +/* + * 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.home.room.threads + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import com.google.android.material.appbar.MaterialToolbar +import im.vector.app.R +import im.vector.app.core.di.ScreenComponent +import im.vector.app.core.extensions.replaceFragment +import im.vector.app.core.platform.ToolbarConfigurable +import im.vector.app.core.platform.VectorBaseActivity +import im.vector.app.core.platform.VectorViewModelAction +import im.vector.app.databinding.ActivityThreadsBinding +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.TimelineFragment +import im.vector.app.features.home.room.threads.arguments.ThreadListArgs +import im.vector.app.features.home.room.threads.arguments.ThreadTimelineArgs +import im.vector.app.features.home.room.threads.detail.ThreadListFragment +import javax.inject.Inject + +class ThreadsActivity : VectorBaseActivity(), ToolbarConfigurable { + + @Inject + lateinit var avatarRenderer: AvatarRenderer + +// private val roomThreadDetailFragment: RoomThreadDetailFragment? +// get() { +// return supportFragmentManager.findFragmentByTag(FRAGMENT_TAG) as? RoomThreadDetailFragment +// } + + override fun getBinding() = ActivityThreadsBinding.inflate(layoutInflater) + + override fun getCoordinatorLayout() = views.coordinatorLayout + + override fun injectWith(injector: ScreenComponent) { + injector.inject(this) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + initFragment() + } + + private fun initFragment() { + if (isFirstCreation()) { + when (val fragment = fragmentToNavigate()) { + is DisplayFragment.ThreadList -> { + initThreadListFragment(fragment.threadListArgs) + } + is DisplayFragment.ThreadTimeLine -> { + initThreadTimelineFragment(fragment.threadTimelineArgs) + } + is DisplayFragment.ErrorFragment -> { + finish() + } + } + } + } + + private fun initThreadListFragment(threadListArgs: ThreadListArgs) { + replaceFragment( + R.id.threadsActivityFragmentContainer, + ThreadListFragment::class.java, + threadListArgs) + } + + private fun initThreadTimelineFragment(threadTimelineArgs: ThreadTimelineArgs) = + replaceFragment( + R.id.threadsActivityFragmentContainer, + TimelineFragment::class.java, + TimelineArgs( + roomId = threadTimelineArgs.roomId, + threadTimelineArgs = threadTimelineArgs + )) + + override fun configure(toolbar: MaterialToolbar) { + configureToolbar(toolbar) + } + + /** + * Determine in witch fragment we should navigate + */ + private fun fragmentToNavigate(): DisplayFragment { + getThreadTimelineArgs()?.let { + return DisplayFragment.ThreadTimeLine(it) + } + getThreadListArgs()?.let { + return DisplayFragment.ThreadList(it) + } + return DisplayFragment.ErrorFragment + } + + private fun getThreadTimelineArgs(): ThreadTimelineArgs? = intent?.extras?.getParcelable(THREAD_TIMELINE_ARGS) + private fun getThreadListArgs(): ThreadListArgs? = intent?.extras?.getParcelable(THREAD_LIST_ARGS) + + companion object { + // private val FRAGMENT_TAG = RoomThreadDetailFragment::class.simpleName + const val THREAD_TIMELINE_ARGS = "THREAD_TIMELINE_ARGS" + const val THREAD_LIST_ARGS = "THREAD_LIST_ARGS" + + fun newIntent(context: Context, threadTimelineArgs: ThreadTimelineArgs?, threadListArgs: ThreadListArgs?): Intent { + return Intent(context, ThreadsActivity::class.java).apply { + putExtra(THREAD_TIMELINE_ARGS, threadTimelineArgs) + putExtra(THREAD_LIST_ARGS, threadListArgs) + + } + } + } + + sealed class DisplayFragment { + data class ThreadList(val threadListArgs: ThreadListArgs) : DisplayFragment() + data class ThreadTimeLine(val threadTimelineArgs: ThreadTimelineArgs) : DisplayFragment() + object ErrorFragment : DisplayFragment() + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/arguments/ThreadListArgs.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/arguments/ThreadListArgs.kt new file mode 100644 index 00000000000..23b72e5f32f --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/arguments/ThreadListArgs.kt @@ -0,0 +1,25 @@ +/* + * 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.home.room.threads.arguments + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +data class ThreadListArgs( + val roomId: String +) : Parcelable diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/detail/arguments/RoomThreadDetailArgs.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/arguments/ThreadTimelineArgs.kt similarity index 85% rename from vector/src/main/java/im/vector/app/features/home/room/threads/detail/arguments/RoomThreadDetailArgs.kt rename to vector/src/main/java/im/vector/app/features/home/room/threads/arguments/ThreadTimelineArgs.kt index 5fce24cd0de..2ebed2f7454 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/detail/arguments/RoomThreadDetailArgs.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/arguments/ThreadTimelineArgs.kt @@ -14,15 +14,15 @@ * limitations under the License. */ -package im.vector.app.features.home.room.threads.detail.arguments +package im.vector.app.features.home.room.threads.arguments import android.os.Parcelable import kotlinx.parcelize.Parcelize @Parcelize -data class RoomThreadDetailArgs( +data class ThreadTimelineArgs( val roomId: String, val displayName: String?, val avatarUrl: String?, - val eventId: String? = null, + val rootThreadEventId: String? = null ) : Parcelable diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/detail/RoomThreadDetailActivity.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/detail/RoomThreadDetailActivity.kt deleted file mode 100644 index c82fa353e4f..00000000000 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/detail/RoomThreadDetailActivity.kt +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 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.home.room.threads.detail - -import android.content.Context -import android.content.Intent -import android.os.Bundle -import im.vector.app.R -import im.vector.app.core.di.ScreenComponent -import im.vector.app.core.extensions.replaceFragment -import im.vector.app.core.platform.VectorBaseActivity -import im.vector.app.databinding.ActivityRoomThreadDetailBinding -import im.vector.app.features.home.AvatarRenderer -import im.vector.app.features.home.room.detail.RoomDetailArgs -import im.vector.app.features.home.room.detail.RoomDetailFragment -import im.vector.app.features.home.room.threads.detail.arguments.RoomThreadDetailArgs -import org.matrix.android.sdk.api.util.MatrixItem -import javax.inject.Inject - -class RoomThreadDetailActivity : VectorBaseActivity() { - - @Inject - lateinit var avatarRenderer: AvatarRenderer - -// private val roomThreadDetailFragment: RoomThreadDetailFragment? -// get() { -// return supportFragmentManager.findFragmentByTag(FRAGMENT_TAG) as? RoomThreadDetailFragment -// } - - override fun getBinding() = ActivityRoomThreadDetailBinding.inflate(layoutInflater) - - override fun getCoordinatorLayout() = views.coordinatorLayout - - override fun injectWith(injector: ScreenComponent) { - injector.inject(this) - } - - override fun getMenuRes() = R.menu.menu_room_threads - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - initToolbar() - initFragment() - } - - private fun initToolbar() { - configureToolbar(views.roomThreadDetailToolbar) - getRoomThreadDetailArgs()?.let { - val matrixItem = MatrixItem.RoomItem(it.roomId, it.displayName, it.avatarUrl) - avatarRenderer.render(matrixItem, views.roomThreadDetailToolbarImageView) - } - } - - private fun initFragment() { - if (isFirstCreation()) { - getRoomThreadDetailArgs()?.let { - replaceFragment( - R.id.roomThreadDetailFragmentContainer, - RoomDetailFragment::class.java, - RoomDetailArgs( - roomId = it.roomId, - roomThreadDetailArgs = it - )) - } -// replaceFragment(R.id.roomThreadDetailFragmentContainer, RoomThreadDetailFragment::class.java, getRoomThreadDetailArgs(), FRAGMENT_TAG) - } - } - - private fun getRoomThreadDetailArgs(): RoomThreadDetailArgs? = intent?.extras?.getParcelable(ROOM_THREAD_DETAIL_ARGS) - - companion object { - private val FRAGMENT_TAG = RoomThreadDetailFragment::class.simpleName - const val ROOM_THREAD_DETAIL_ARGS = "ROOM_THREAD_DETAIL_ARGS" - - fun newIntent(context: Context, roomThreadDetailArgs: RoomThreadDetailArgs): Intent { - return Intent(context, RoomThreadDetailActivity::class.java).apply { - putExtra(ROOM_THREAD_DETAIL_ARGS, roomThreadDetailArgs) - } - } - } -} diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/detail/RoomThreadDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/detail/ThreadListFragment.kt similarity index 74% rename from vector/src/main/java/im/vector/app/features/home/room/threads/detail/RoomThreadDetailFragment.kt rename to vector/src/main/java/im/vector/app/features/home/room/threads/detail/ThreadListFragment.kt index fee21128cd5..4e870bd53b5 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/detail/RoomThreadDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/detail/ThreadListFragment.kt @@ -16,34 +16,31 @@ package im.vector.app.features.home.room.threads.detail -import android.annotation.SuppressLint 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 im.vector.app.core.platform.VectorBaseFragment -import im.vector.app.databinding.FragmentRoomThreadDetailBinding -import im.vector.app.features.home.room.threads.detail.arguments.RoomThreadDetailArgs +import im.vector.app.databinding.FragmentThreadListBinding +import im.vector.app.features.home.room.threads.arguments.ThreadTimelineArgs import org.matrix.android.sdk.api.session.Session import javax.inject.Inject -class RoomThreadDetailFragment @Inject constructor( +class ThreadListFragment @Inject constructor( private val session: Session -) : VectorBaseFragment() { +) : VectorBaseFragment() { - private val roomThreadDetailArgs: RoomThreadDetailArgs by args() + private val threadTimelineArgs: ThreadTimelineArgs by args() - override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRoomThreadDetailBinding { - return FragmentRoomThreadDetailBinding.inflate(inflater, container, false) + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentThreadListBinding { + return FragmentThreadListBinding.inflate(inflater, container, false) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) } - @SuppressLint("SetTextI18n") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) initTextComposer() @@ -59,7 +56,7 @@ class RoomThreadDetailFragment @Inject constructor( } private fun initTextComposer(){ - views.roomThreadDetailTextComposerView.views.sendButton.isVisible = true +// views.roomThreadDetailTextComposerView.views.sendButton.isVisible = true } } diff --git a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt index debdf3739c9..fdf1a242614 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt @@ -49,10 +49,13 @@ import im.vector.app.features.crypto.verification.VerificationBottomSheet import im.vector.app.features.debug.DebugMenuActivity import im.vector.app.features.devtools.RoomDevToolActivity import im.vector.app.features.home.room.detail.RoomDetailActivity -import im.vector.app.features.home.room.detail.RoomDetailArgs +import im.vector.app.features.home.room.detail.arguments.TimelineArgs import im.vector.app.features.home.room.detail.search.SearchActivity import im.vector.app.features.home.room.detail.search.SearchArgs import im.vector.app.features.home.room.filtered.FilteredRoomsActivity +import im.vector.app.features.home.room.threads.ThreadsActivity +import im.vector.app.features.home.room.threads.arguments.ThreadListArgs +import im.vector.app.features.home.room.threads.arguments.ThreadTimelineArgs import im.vector.app.features.invite.InviteUsersToRoomActivity import im.vector.app.features.login.LoginActivity import im.vector.app.features.login.LoginConfig @@ -118,7 +121,7 @@ class DefaultNavigator @Inject constructor( fatalError("Trying to open an unknown room $roomId", vectorPreferences.failFast()) return } - val args = RoomDetailArgs(roomId, eventId) + val args = TimelineArgs(roomId, eventId) val intent = RoomDetailActivity.newIntent(context, args) startActivity(context, intent, buildTask) } @@ -141,7 +144,7 @@ class DefaultNavigator @Inject constructor( startActivity(context, SpaceManageActivity.newIntent(context, spaceId, ManageType.AddRooms), false) } is Navigator.PostSwitchSpaceAction.OpenDefaultRoom -> { - val args = RoomDetailArgs( + val args = TimelineArgs( postSwitchSpaceAction.roomId, eventId = null, openShareSpaceForId = spaceId.takeIf { postSwitchSpaceAction.showShareSheet } @@ -239,7 +242,7 @@ class DefaultNavigator @Inject constructor( } override fun openRoomForSharingAndFinish(activity: Activity, roomId: String, sharedData: SharedData) { - val args = RoomDetailArgs(roomId, null, sharedData) + val args = TimelineArgs(roomId, null, sharedData) val intent = RoomDetailActivity.newIntent(activity, args) activity.startActivity(intent) activity.finish() @@ -507,4 +510,11 @@ class DefaultNavigator @Inject constructor( context.startActivity(intent) } } + + override fun openThread(context: Context, threadTimelineArgs: ThreadTimelineArgs) { + context.startActivity(ThreadsActivity.newIntent( + context = context, + threadTimelineArgs = threadTimelineArgs, + threadListArgs =null)) + } } diff --git a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt index 612643c8041..eeeb2a1b35a 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt @@ -24,6 +24,7 @@ import androidx.activity.result.ActivityResultLauncher import androidx.core.util.Pair import im.vector.app.features.crypto.recover.SetupMode import im.vector.app.features.displayname.getBestName +import im.vector.app.features.home.room.threads.arguments.ThreadTimelineArgs import im.vector.app.features.login.LoginConfig import im.vector.app.features.media.AttachmentData import im.vector.app.features.pin.PinMode @@ -140,4 +141,7 @@ interface Navigator { fun openDevTools(context: Context, roomId: String) fun openCallTransfer(context: Context, callId: String) + + fun openThread(context: Context, threadTimelineArgs: ThreadTimelineArgs) + } diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt index 92feb3d038f..7f41049c21a 100755 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt @@ -56,7 +56,7 @@ import im.vector.app.features.call.webrtc.WebRtcCall import im.vector.app.features.displayname.getBestName import im.vector.app.features.home.HomeActivity import im.vector.app.features.home.room.detail.RoomDetailActivity -import im.vector.app.features.home.room.detail.RoomDetailArgs +import im.vector.app.features.home.room.detail.arguments.TimelineArgs import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.troubleshoot.TestNotificationReceiver import im.vector.app.features.themes.ThemeUtils @@ -497,7 +497,7 @@ class NotificationUtils @Inject constructor(private val context: Context, val contentPendingIntent = TaskStackBuilder.create(context) .addNextIntentWithParentStack(HomeActivity.newIntent(context)) - .addNextIntent(RoomDetailActivity.newIntent(context, RoomDetailArgs(callInformation.nativeRoomId))) + .addNextIntent(RoomDetailActivity.newIntent(context, TimelineArgs(callInformation.nativeRoomId))) .getPendingIntent(System.currentTimeMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT) builder.setContentIntent(contentPendingIntent) @@ -735,7 +735,7 @@ class NotificationUtils @Inject constructor(private val context: Context, } private fun buildOpenRoomIntent(roomId: String): PendingIntent? { - val roomIntentTap = RoomDetailActivity.newIntent(context, RoomDetailArgs(roomId)) + val roomIntentTap = RoomDetailActivity.newIntent(context, TimelineArgs(roomId)) roomIntentTap.action = TAP_TO_VIEW_ACTION // pending intent get reused by system, this will mess up the extra params, so put unique info to avoid that roomIntentTap.data = Uri.parse("foobar://openRoom?$roomId") diff --git a/vector/src/main/res/drawable/ic_thread_link_menu_item.xml b/vector/src/main/res/drawable/ic_thread_link_menu_item.xml new file mode 100644 index 00000000000..779c9d832c1 --- /dev/null +++ b/vector/src/main/res/drawable/ic_thread_link_menu_item.xml @@ -0,0 +1,12 @@ + + + diff --git a/vector/src/main/res/drawable/ic_thread_menu_item.xml b/vector/src/main/res/drawable/ic_thread_menu_item.xml new file mode 100644 index 00000000000..2d77251c533 --- /dev/null +++ b/vector/src/main/res/drawable/ic_thread_menu_item.xml @@ -0,0 +1,10 @@ + + + diff --git a/vector/src/main/res/drawable/ic_thread_share_menu_item.xml b/vector/src/main/res/drawable/ic_thread_share_menu_item.xml new file mode 100644 index 00000000000..cb863c39bfe --- /dev/null +++ b/vector/src/main/res/drawable/ic_thread_share_menu_item.xml @@ -0,0 +1,19 @@ + + + + + + diff --git a/vector/src/main/res/drawable/ic_thread_view_in_room_menu_item.xml b/vector/src/main/res/drawable/ic_thread_view_in_room_menu_item.xml new file mode 100644 index 00000000000..f408f997136 --- /dev/null +++ b/vector/src/main/res/drawable/ic_thread_view_in_room_menu_item.xml @@ -0,0 +1,30 @@ + + + + + + diff --git a/vector/src/main/res/layout/activity_room_thread_detail.xml b/vector/src/main/res/layout/activity_room_thread_detail.xml deleted file mode 100644 index 94c52ab959d..00000000000 --- a/vector/src/main/res/layout/activity_room_thread_detail.xml +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/vector/src/main/res/layout/activity_room_threads.xml b/vector/src/main/res/layout/activity_room_threads.xml deleted file mode 100644 index b469c7de42f..00000000000 --- a/vector/src/main/res/layout/activity_room_threads.xml +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/vector/src/main/res/layout/activity_threads.xml b/vector/src/main/res/layout/activity_threads.xml new file mode 100644 index 00000000000..c34be9687df --- /dev/null +++ b/vector/src/main/res/layout/activity_threads.xml @@ -0,0 +1,24 @@ + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/fragment_room_detail.xml b/vector/src/main/res/layout/fragment_room_detail.xml index 7725cd5e920..5d0e5f3ebdc 100644 --- a/vector/src/main/res/layout/fragment_room_detail.xml +++ b/vector/src/main/res/layout/fragment_room_detail.xml @@ -26,101 +26,13 @@ android:layout_height="?actionBarSize" android:transitionName="toolbar"> - + - - - - - - - - - - - - - + diff --git a/vector/src/main/res/layout/fragment_room_thread_detail.xml b/vector/src/main/res/layout/fragment_room_thread_detail.xml deleted file mode 100644 index cadc819d28c..00000000000 --- a/vector/src/main/res/layout/fragment_room_thread_detail.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - diff --git a/vector/src/main/res/layout/view_room_detail_thread_toolbar.xml b/vector/src/main/res/layout/view_room_detail_thread_toolbar.xml new file mode 100644 index 00000000000..dcb60a44d7d --- /dev/null +++ b/vector/src/main/res/layout/view_room_detail_thread_toolbar.xml @@ -0,0 +1,51 @@ + + + + + + + + + + diff --git a/vector/src/main/res/layout/view_room_detail_toolbar.xml b/vector/src/main/res/layout/view_room_detail_toolbar.xml new file mode 100644 index 00000000000..fdc3f6819e4 --- /dev/null +++ b/vector/src/main/res/layout/view_room_detail_toolbar.xml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + diff --git a/vector/src/main/res/menu/menu_thread_timeline.xml b/vector/src/main/res/menu/menu_thread_timeline.xml new file mode 100644 index 00000000000..4698559bae7 --- /dev/null +++ b/vector/src/main/res/menu/menu_thread_timeline.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/menu/menu_timeline.xml b/vector/src/main/res/menu/menu_timeline.xml index 54967f1706c..532b63dd380 100644 --- a/vector/src/main/res/menu/menu_timeline.xml +++ b/vector/src/main/res/menu/menu_timeline.xml @@ -37,11 +37,20 @@ app:showAsAction="always" tools:visible="true" /> - + + + app:showAsAction="always" /> + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index b7816927336..a85bf0629e5 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -441,6 +441,7 @@ Are you sure you want to sign out? Voice Call Video Call + View Threads Global search Mark all as read Historical @@ -456,6 +457,11 @@ Disable Return + + View in room + Copy link to thread + Share + Confirmation Warning @@ -1023,6 +1029,7 @@ Filter Threads in room + Thread Reason for reporting this content From 722f367690b0d7268397efaf6f4c05cfbe97180b Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Tue, 23 Nov 2021 13:34:24 +0200 Subject: [PATCH 011/581] View all threads screen implementation & UI Add user friendly message thread summary on the SDK side Fix not encrypted rooms thread summaries --- .../org/matrix/android/sdk/flow/FlowRoom.kt | 8 ++ .../sdk/api/session/events/model/Event.kt | 39 ++++++-- .../session/room/timeline/TimelineService.kt | 13 +++ .../database/helper/ThreadEventsHelper.kt | 13 ++- .../internal/database/mapper/EventMapper.kt | 4 +- .../session/room/timeline/DefaultTimeline.kt | 6 +- .../room/timeline/DefaultTimelineService.kt | 15 +++ .../debug/res/layout/fragment_thread_list.xml | 14 --- .../im/vector/app/core/di/FragmentModule.kt | 2 +- .../home/room/detail/TimelineFragment.kt | 24 ++++- .../detail/timeline/item/AbsMessageItem.kt | 18 ++-- .../home/room/threads/ThreadsActivity.kt | 3 +- .../room/threads/arguments/ThreadListArgs.kt | 4 +- .../room/threads/detail/ThreadListFragment.kt | 62 ------------- .../threads/list/model/ThreadSummaryModel.kt | 66 ++++++++++++++ .../list/viewmodel/ThreadSummaryController.kt | 73 +++++++++++++++ .../list/viewmodel/ThreadSummaryViewModel.kt | 72 +++++++++++++++ .../list/viewmodel/ThreadSummaryViewState.kt | 31 +++++++ .../threads/list/views/ThreadListFragment.kt | 91 +++++++++++++++++++ .../features/navigation/DefaultNavigator.kt | 10 ++ .../app/features/navigation/Navigator.kt | 1 + .../main/res/layout/fragment_thread_list.xml | 40 ++++++++ .../main/res/layout/item_thread_summary.xml | 91 +++++++++++++++++++ .../res/layout/item_timeline_event_base.xml | 24 ++++- .../res/layout/view_thread_room_summary.xml | 58 +++++------- .../src/main/res/menu/menu_room_threads.xml | 9 -- vector/src/main/res/menu/menu_thread_list.xml | 13 +++ .../main/res/menu/menu_thread_timeline.xml | 36 -------- 28 files changed, 654 insertions(+), 186 deletions(-) delete mode 100644 vector/src/debug/res/layout/fragment_thread_list.xml delete mode 100644 vector/src/main/java/im/vector/app/features/home/room/threads/detail/ThreadListFragment.kt create mode 100644 vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadSummaryModel.kt create mode 100644 vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadSummaryController.kt create mode 100644 vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadSummaryViewModel.kt create mode 100644 vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadSummaryViewState.kt create mode 100644 vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt create mode 100644 vector/src/main/res/layout/fragment_thread_list.xml create mode 100644 vector/src/main/res/layout/item_thread_summary.xml delete mode 100644 vector/src/main/res/menu/menu_room_threads.xml create mode 100644 vector/src/main/res/menu/menu_thread_list.xml delete mode 100644 vector/src/main/res/menu/menu_thread_timeline.xml diff --git a/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowRoom.kt b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowRoom.kt index 42c1476b795..70919059913 100644 --- a/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowRoom.kt +++ b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowRoom.kt @@ -21,6 +21,7 @@ import kotlinx.coroutines.flow.Flow import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.Room +import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams import org.matrix.android.sdk.api.session.room.members.RoomMemberQueryParams import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary import org.matrix.android.sdk.api.session.room.model.ReadReceipt @@ -98,6 +99,13 @@ class FlowRoom(private val room: Room) { fun liveNotificationState(): Flow { return room.getLiveRoomNotificationState().asFlow() } + + fun liveThreadList(): Flow> { + return room.getAllThreadsLive().asFlow() + .startWith(room.coroutineDispatchers.io) { + room.getAllThreads() + } + } } fun Room.flow(): FlowRoom { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt index ccf98f7754f..77285dd463e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt @@ -27,11 +27,13 @@ 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.send.SendState import org.matrix.android.sdk.api.session.threads.ThreadDetails +import org.matrix.android.sdk.api.util.ContentUtils import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.session.presence.model.PresenceContent +import org.matrix.android.sdk.internal.session.room.send.removeInReplyFallbacks import timber.log.Timber typealias Content = JsonDict @@ -188,14 +190,39 @@ data class Event( return contentMap?.let { JSONObject(adapter.toJson(it)).toString(4) } } - fun getDecryptedMessageText(): String { - return getValueFromPayload(mxDecryptionResult?.payload).orEmpty() + /** + * Returns a user friendly content depending on the message type. + * It can be used especially for message summaries. + * It will return a decrypted text message or an empty string otherwise. + */ + fun getDecryptedUserFriendlyTextSummary(): String { + val text = getDecryptedValue().orEmpty() + return when { + isReply() || isQuote() -> ContentUtils.extractUsefulTextFromReply(text) + isFileMessage() -> "sent a file." + isAudioMessage() -> "sent an audio file." + isImageMessage() -> "sent an image." + isVideoMessage() -> "sent a video." + else -> text + } + } + + private fun Event.isQuote(): Boolean { + if (isReply()) return false + return getDecryptedValue("formatted_body")?.contains("
") ?: false } - @Suppress("UNCHECKED_CAST") - private fun getValueFromPayload(payload: JsonDict?, key: String = "body"): String? { - val content = payload?.get("content") as? JsonDict - return content?.get(key) as? String + /** + * Decrypt the message, or return the pure payload value if there is no encryption + */ + private fun getDecryptedValue(key: String = "body"): String? { + return if (isEncrypted()) { + @Suppress("UNCHECKED_CAST") + val content = mxDecryptionResult?.payload?.get("content") as? JsonDict + content?.get(key) as? String + } else { + content?.get(key) as? String + } } /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineService.kt index 3c021384e13..aa703432796 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineService.kt @@ -55,4 +55,17 @@ interface TimelineService { * Returns a snapshot list of TimelineEvent with EventType.MESSAGE and MessageType.MSGTYPE_IMAGE or MessageType.MSGTYPE_VIDEO. */ fun getAttachmentMessages(): List + + /** + * Get a live list of all the thread for the specified roomId + * @return the [LiveData] of [TimelineEvent] + */ + fun getAllThreadsLive(): LiveData> + + /** + * Get a list of all the thread for the specified roomId + * @return the [LiveData] of [TimelineEvent] + */ + fun getAllThreads(): List + } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt index 597e08e3072..755891af3e7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.database.helper import io.realm.Realm +import io.realm.RealmQuery import io.realm.RealmResults import io.realm.Sort import org.matrix.android.sdk.BuildConfig @@ -82,7 +83,17 @@ internal fun EventEntity.findAllThreadsForRootEventId(realm: Realm, rootThreadEv TimelineEventEntity .whereRoomId(realm, roomId = roomId) .equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, rootThreadEventId) - .sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING).findAll() + .sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING) + .findAll() +/** + * Find all TimelineEventEntity that are root threads for the specified room + * @param roomId The room that all stored root threads will be returned + */ +internal fun TimelineEventEntity.Companion.findAllThreadsForRoomId(realm: Realm, roomId: String): RealmQuery = + TimelineEventEntity + .whereRoomId(realm, roomId = roomId) + .equalTo(TimelineEventEntityFields.ROOT.IS_ROOT_THREAD,true) + .sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt index de4be16493b..aded11e815a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt @@ -22,11 +22,9 @@ 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.UnsignedData import org.matrix.android.sdk.api.session.events.model.getRootThreadEventId -import org.matrix.android.sdk.api.session.events.model.isThread import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.sender.SenderInfo import org.matrix.android.sdk.api.session.threads.ThreadDetails -import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult import org.matrix.android.sdk.internal.database.model.EventEntity import org.matrix.android.sdk.internal.di.MoshiProvider @@ -113,7 +111,7 @@ internal object EventMapper { avatarUrl = timelineEventEntity.senderAvatar ) }, - threadSummaryLatestTextMessage = eventEntity.threadSummaryLatestMessage?.root?.asDomain()?.getDecryptedMessageText().orEmpty() + threadSummaryLatestTextMessage = eventEntity.threadSummaryLatestMessage?.root?.asDomain()?.getDecryptedUserFriendlyTextSummary().orEmpty() ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt index a8a72d8a527..4d417fddbbf 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt @@ -168,7 +168,11 @@ internal class DefaultTimeline( TimelineEventEntity .whereRoomId(realm, roomId = roomId) .equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, it) - .sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING).findAll() + .or() + .equalTo(TimelineEventEntityFields.ROOT.EVENT_ID, rootThreadEventId) + .sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING) + .findAll() + } ?: buildEventQuery(realm).sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING).findAll() timelineEvents.addChangeListener(eventsChangeListener) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt index 47e8f7e3a33..690f3008270 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt @@ -31,9 +31,11 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineService import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.database.RealmSessionProvider +import org.matrix.android.sdk.internal.database.helper.findAllThreadsForRoomId import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper import org.matrix.android.sdk.internal.database.model.TimelineEventEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields +import org.matrix.android.sdk.internal.database.query.findAllInRoomWithSendStates import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask @@ -102,4 +104,17 @@ internal class DefaultTimelineService @AssistedInject constructor( .orEmpty() } } + + override fun getAllThreadsLive(): LiveData> { + return monarchy.findAllMappedWithChanges( + { TimelineEventEntity.findAllThreadsForRoomId(it, roomId = roomId) }, + { timelineEventMapper.map(it) } + ) + } + override fun getAllThreads(): List { + return monarchy.fetchAllMappedSync( + { TimelineEventEntity.findAllThreadsForRoomId(it, roomId = roomId) }, + { timelineEventMapper.map(it) } + ) + } } diff --git a/vector/src/debug/res/layout/fragment_thread_list.xml b/vector/src/debug/res/layout/fragment_thread_list.xml deleted file mode 100644 index cf3a79e7760..00000000000 --- a/vector/src/debug/res/layout/fragment_thread_list.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt index 4763e6f9353..37418f4c63a 100644 --- a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt @@ -58,7 +58,7 @@ import im.vector.app.features.home.room.breadcrumbs.BreadcrumbsFragment import im.vector.app.features.home.room.detail.TimelineFragment import im.vector.app.features.home.room.detail.search.SearchFragment import im.vector.app.features.home.room.list.RoomListFragment -import im.vector.app.features.home.room.threads.detail.ThreadListFragment +import im.vector.app.features.home.room.threads.list.views.ThreadListFragment import im.vector.app.features.login.LoginCaptchaFragment import im.vector.app.features.login.LoginFragment import im.vector.app.features.login.LoginGenericTextInputFormFragment 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 9afd5a2fc93..a8e8e11b573 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 @@ -68,6 +68,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.jakewharton.rxbinding3.view.focusChanges import com.jakewharton.rxbinding3.widget.textChanges import com.vanniktech.emoji.EmojiPopup +import im.vector.app.BuildConfig import im.vector.app.R import im.vector.app.core.dialogs.ConfirmationDialogBuilder import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper @@ -971,7 +972,7 @@ class TimelineFragment @Inject constructor( true } R.id.threads -> { - requireActivity().toast("View All Threads") + navigateToThreadList() true } R.id.search -> { @@ -1776,7 +1777,7 @@ class TimelineFragment @Inject constructor( roomDetailViewModel.handle(RoomDetailAction.TapOnFailedToDecrypt(informationData.eventId)) } } - if (isRootThreadEvent) { + if (BuildConfig.THREADING_ENABLED && isRootThreadEvent && !isThreadTimeLine()) { navigateToThreadTimeline(informationData.eventId) } } @@ -2136,8 +2137,8 @@ class TimelineFragment @Inject constructor( } /** - * Navigate to Threads timeline for the specified threadRootEventId - * using the RoomThreadDetailActivity + * Navigate to Threads timeline for the specified rootThreadEventId + * using the ThreadsActivity */ private fun navigateToThreadTimeline(rootThreadEventId: String) { @@ -2151,6 +2152,21 @@ class TimelineFragment @Inject constructor( } } + /** + * Navigate to Threads list for the current room + * using the ThreadsActivity + */ + + private fun navigateToThreadList() { + context?.let { + val roomThreadDetailArgs = ThreadTimelineArgs( + roomId = timelineArgs.roomId, + displayName = roomDetailViewModel.getRoomSummary()?.displayName, + avatarUrl = roomDetailViewModel.getRoomSummary()?.avatarUrl) + navigator.openThreadList(it, roomThreadDetailArgs) + } + } + // VectorInviteView.Callback override fun onAcceptInvite() { 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 0649755c2a3..188a195ae61 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 @@ -27,6 +27,7 @@ import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.view.isInvisible import androidx.core.view.isVisible import com.airbnb.epoxy.EpoxyAttribute +import im.vector.app.BuildConfig import im.vector.app.R import im.vector.app.core.epoxy.ClickListener import im.vector.app.core.epoxy.onClick @@ -105,14 +106,17 @@ abstract class AbsMessageItem : AbsBaseMessageItem holder.eventSendingIndicator.isVisible = attributes.informationData.sendStateDecoration == SendStateDecoration.SENDING_MEDIA // Threads - attributes.threadDetails?.let { threadDetails -> - threadDetails.isRootThread - holder.threadSummaryConstraintLayout.isVisible = threadDetails.isRootThread - holder.threadSummaryCounterTextView.text = threadDetails.numberOfThreads.toString() - holder.threadSummaryInfoTextView.text = threadDetails.threadSummaryLatestTextMessage - threadDetails.threadSummarySenderInfo?.let { senderInfo -> - attributes.avatarRenderer.render(MatrixItem.UserItem(senderInfo.userId, senderInfo.displayName, senderInfo.avatarUrl), holder.threadSummaryAvatarImageView) + if(BuildConfig.THREADING_ENABLED) { + attributes.threadDetails?.let { threadDetails -> + holder.threadSummaryConstraintLayout.isVisible = threadDetails.isRootThread + holder.threadSummaryCounterTextView.text = threadDetails.numberOfThreads.toString() + holder.threadSummaryInfoTextView.text = threadDetails.threadSummaryLatestTextMessage + threadDetails.threadSummarySenderInfo?.let { senderInfo -> + attributes.avatarRenderer.render(MatrixItem.UserItem(senderInfo.userId, senderInfo.displayName, senderInfo.avatarUrl), holder.threadSummaryAvatarImageView) + } } + }else{ + holder.threadSummaryConstraintLayout.isVisible = false } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt index 73da1354af8..007b4195325 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt @@ -25,14 +25,13 @@ import im.vector.app.core.di.ScreenComponent import im.vector.app.core.extensions.replaceFragment import im.vector.app.core.platform.ToolbarConfigurable import im.vector.app.core.platform.VectorBaseActivity -import im.vector.app.core.platform.VectorViewModelAction import im.vector.app.databinding.ActivityThreadsBinding 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.TimelineFragment import im.vector.app.features.home.room.threads.arguments.ThreadListArgs import im.vector.app.features.home.room.threads.arguments.ThreadTimelineArgs -import im.vector.app.features.home.room.threads.detail.ThreadListFragment +import im.vector.app.features.home.room.threads.list.views.ThreadListFragment import javax.inject.Inject class ThreadsActivity : VectorBaseActivity(), ToolbarConfigurable { diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/arguments/ThreadListArgs.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/arguments/ThreadListArgs.kt index 23b72e5f32f..50819a30174 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/arguments/ThreadListArgs.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/arguments/ThreadListArgs.kt @@ -21,5 +21,7 @@ import kotlinx.parcelize.Parcelize @Parcelize data class ThreadListArgs( - val roomId: String + val roomId: String, + val displayName: String?, + val avatarUrl: String?, ) : Parcelable diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/detail/ThreadListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/detail/ThreadListFragment.kt deleted file mode 100644 index 4e870bd53b5..00000000000 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/detail/ThreadListFragment.kt +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 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.home.room.threads.detail - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import com.airbnb.mvrx.args -import im.vector.app.core.platform.VectorBaseFragment -import im.vector.app.databinding.FragmentThreadListBinding -import im.vector.app.features.home.room.threads.arguments.ThreadTimelineArgs -import org.matrix.android.sdk.api.session.Session -import javax.inject.Inject - -class ThreadListFragment @Inject constructor( - private val session: Session -) : VectorBaseFragment() { - - private val threadTimelineArgs: ThreadTimelineArgs by args() - - override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentThreadListBinding { - return FragmentThreadListBinding.inflate(inflater, container, false) - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - initTextComposer() -// lifecycleScope.launch(Dispatchers.IO) { -// Realm.getInstance(realmConfiguration).executeTransaction { -// val eventId = roomThreadDetailArgs.eventId ?: return@executeTransaction -// val r = EventEntity.where(it, eventId = eventId) -// .findFirst() ?: return@executeTransaction -// Timber.i("------> $eventId isThread: ${EventMapper.map(r).isThread()}") -// } -// } -//// views.testTextVeiwddasda.text = "${roomThreadDetailArgs.eventId} -- ${roomThreadDetailArgs.roomId}" - } - - private fun initTextComposer(){ -// views.roomThreadDetailTextComposerView.views.sendButton.isVisible = true - } - -} diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadSummaryModel.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadSummaryModel.kt new file mode 100644 index 00000000000..85e375d00db --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadSummaryModel.kt @@ -0,0 +1,66 @@ +/* + * 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.threads.list.model + +import android.widget.ImageView +import android.widget.TextView +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.app.R +import im.vector.app.core.epoxy.VectorEpoxyHolder +import im.vector.app.core.epoxy.VectorEpoxyModel +import im.vector.app.features.displayname.getBestName +import im.vector.app.features.home.AvatarRenderer +import org.matrix.android.sdk.api.util.MatrixItem + +@EpoxyModelClass(layout = R.layout.item_thread_summary) +abstract class ThreadSummaryModel : VectorEpoxyModel() { + + @EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer + @EpoxyAttribute lateinit var matrixItem: MatrixItem + @EpoxyAttribute lateinit var title: String + @EpoxyAttribute lateinit var date: String + @EpoxyAttribute lateinit var rootMessage: String + @EpoxyAttribute lateinit var lastMessage: String + @EpoxyAttribute lateinit var lastMessageCounter: String + @EpoxyAttribute lateinit var lastMessageMatrixItem: MatrixItem + + override fun bind(holder: Holder) { + super.bind(holder) + avatarRenderer.render(matrixItem, holder.avatarImageView) + holder.avatarImageView.contentDescription = matrixItem.getBestName() + holder.titleTextView.text = title + holder.dateTextView.text = date + holder.rootMessageTextView.text = rootMessage + + // Last message summary + avatarRenderer.render(lastMessageMatrixItem, holder.lastMessageAvatarImageView) + holder.lastMessageAvatarImageView.contentDescription = lastMessageMatrixItem.getBestName() + holder.lastMessageTextView.text = lastMessage + holder.lastMessageCounterTextView.text = lastMessageCounter + } + + class Holder : VectorEpoxyHolder() { + val avatarImageView by bind(R.id.threadSummaryAvatarImageView) + val titleTextView by bind(R.id.threadSummaryTitleTextView) + val dateTextView by bind(R.id.threadSummaryDateTextView) + val rootMessageTextView by bind(R.id.threadSummaryRootMessageTextView) + val lastMessageAvatarImageView by bind(R.id.messageThreadSummaryAvatarImageView) + val lastMessageCounterTextView by bind(R.id.messageThreadSummaryCounterTextView) + val lastMessageTextView by bind(R.id.messageThreadSummaryInfoTextView) + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadSummaryController.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadSummaryController.kt new file mode 100644 index 00000000000..bd19c8e3ff2 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadSummaryController.kt @@ -0,0 +1,73 @@ +/* + * Copyright 2020 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.threads.list.viewmodel + +import com.airbnb.epoxy.EpoxyController +import im.vector.app.features.home.AvatarRenderer +import im.vector.app.features.home.room.threads.list.model.threadSummary +import org.matrix.android.sdk.api.util.toMatrixItem +import javax.inject.Inject + +class ThreadSummaryController @Inject constructor( + private val avatarRenderer: AvatarRenderer +) : EpoxyController() { + + var listener: Listener? = null + + private var viewState: ThreadSummaryViewState? = null + + init { + // We are requesting a model build directly as the first build of epoxy is on the main thread. + // It avoids to build the whole list of breadcrumbs on the main thread. + requestModelBuild() + } + + fun update(viewState: ThreadSummaryViewState) { + this.viewState = viewState + requestModelBuild() + } + + override fun buildModels() { + val safeViewState = viewState ?: return + val host = this + // Add a ZeroItem to avoid automatic scroll when the breadcrumbs are updated from another client +// zeroItem { +// id("top") +// } + + // An empty breadcrumbs list can only be temporary because when entering in a room, + // this one is added to the breadcrumbs + safeViewState.rootThreadEventList.invoke() + ?.forEach { timelineEvent -> + threadSummary { + id(timelineEvent.eventId) + avatarRenderer(host.avatarRenderer) + matrixItem(timelineEvent.senderInfo.toMatrixItem()) + title(timelineEvent.senderInfo.displayName) + date(timelineEvent.root.ageLocalTs.toString()) + rootMessage(timelineEvent.root.getDecryptedUserFriendlyTextSummary()) + lastMessage(timelineEvent.root.threadDetails?.threadSummaryLatestTextMessage.orEmpty()) + lastMessageCounter(timelineEvent.root.threadDetails?.numberOfThreads.toString()) + lastMessageMatrixItem(timelineEvent.root.threadDetails?.threadSummarySenderInfo?.toMatrixItem()) + } + } + } + + interface Listener { + fun onBreadcrumbClicked(roomId: String) + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadSummaryViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadSummaryViewModel.kt new file mode 100644 index 00000000000..385213470a8 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadSummaryViewModel.kt @@ -0,0 +1,72 @@ +/* + * Copyright 2020 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.threads.list.viewmodel + +import com.airbnb.mvrx.FragmentViewModelContext +import com.airbnb.mvrx.MavericksViewModelFactory +import com.airbnb.mvrx.ViewModelContext +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import im.vector.app.core.platform.EmptyAction +import im.vector.app.core.platform.EmptyViewEvents +import im.vector.app.core.platform.VectorViewModel +import im.vector.app.features.home.room.threads.list.views.ThreadListFragment +import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.room.Room +import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams +import org.matrix.android.sdk.flow.flow + +class ThreadSummaryViewModel @AssistedInject constructor(@Assisted val initialState: ThreadSummaryViewState, + private val session: Session) : + VectorViewModel(initialState) { + + private val room = session.getRoom(initialState.roomId) + + @AssistedFactory + interface Factory { + fun create(initialState: ThreadSummaryViewState): ThreadSummaryViewModel + } + + companion object : MavericksViewModelFactory { + + @JvmStatic + override fun create(viewModelContext: ViewModelContext, state: ThreadSummaryViewState): ThreadSummaryViewModel? { + val fragment: ThreadListFragment = (viewModelContext as FragmentViewModelContext).fragment() + return fragment.threadSummaryViewModelFactory.create(state) + } + } + + init { + observeThreadsSummary() + } + + override fun handle(action: EmptyAction) { + // No op + } + + + private fun observeThreadsSummary() { + room?.flow() + ?.liveThreadList() + ?.execute { asyncThreads -> + copy(rootThreadEventList = asyncThreads) + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadSummaryViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadSummaryViewState.kt new file mode 100644 index 00000000000..b0c9c2ea261 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadSummaryViewState.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2020 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.threads.list.viewmodel + +import com.airbnb.mvrx.Async +import com.airbnb.mvrx.MavericksState +import com.airbnb.mvrx.Uninitialized +import im.vector.app.features.home.room.threads.arguments.ThreadListArgs +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent + +data class ThreadSummaryViewState( + val rootThreadEventList: Async> = Uninitialized, + val roomId: String +) : MavericksState{ + + constructor(args: ThreadListArgs) : this(roomId = args.roomId) +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt new file mode 100644 index 00000000000..d2551b58c1d --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt @@ -0,0 +1,91 @@ +/* + * 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.home.room.threads.list.views + +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.fragmentViewModel +import com.airbnb.mvrx.withState +import im.vector.app.R +import im.vector.app.core.extensions.cleanup +import im.vector.app.core.extensions.configureWith +import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.databinding.FragmentThreadListBinding +import im.vector.app.features.home.AvatarRenderer +import im.vector.app.features.home.room.breadcrumbs.BreadcrumbsAnimator +import im.vector.app.features.home.room.breadcrumbs.BreadcrumbsViewModel +import im.vector.app.features.home.room.detail.RoomDetailSharedActionViewModel +import im.vector.app.features.home.room.threads.arguments.ThreadListArgs +import im.vector.app.features.home.room.threads.list.viewmodel.ThreadSummaryController +import im.vector.app.features.home.room.threads.list.viewmodel.ThreadSummaryViewModel +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.util.MatrixItem +import javax.inject.Inject + +class ThreadListFragment @Inject constructor( + private val session: Session, + private val avatarRenderer: AvatarRenderer, + private val threadSummaryController: ThreadSummaryController, + val threadSummaryViewModelFactory: ThreadSummaryViewModel.Factory +) : VectorBaseFragment() { + + private val threadSummaryViewModel: ThreadSummaryViewModel by fragmentViewModel() + + private val threadListArgs: ThreadListArgs by args() + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentThreadListBinding { + return FragmentThreadListBinding.inflate(inflater, container, false) + } + + override fun getMenuRes() = R.menu.menu_thread_list + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + initToolbar() + views.threadListRecyclerView.configureWith(threadSummaryController, BreadcrumbsAnimator(), hasFixedSize = false) +// threadSummaryController.listener = this + } + + override fun onDestroyView() { + views.threadListRecyclerView.cleanup() +// breadcrumbsController.listener = null + super.onDestroyView() + } + private fun initToolbar(){ + setupToolbar(views.threadListToolbar) + renderToolbar() + } + + override fun invalidate() = withState(threadSummaryViewModel) { state -> + threadSummaryController.update(state) + } + + private fun renderToolbar() { + views.includeThreadListToolbar.roomToolbarThreadConstraintLayout.isVisible = true + val matrixItem = MatrixItem.RoomItem(threadListArgs.roomId, threadListArgs.displayName, threadListArgs.avatarUrl) + avatarRenderer.render(matrixItem, views.includeThreadListToolbar.roomToolbarThreadImageView) + views.includeThreadListToolbar.roomToolbarThreadSubtitleTextView.text = threadListArgs.displayName + } +} diff --git a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt index fdf1a242614..5f4a2168e95 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt @@ -517,4 +517,14 @@ class DefaultNavigator @Inject constructor( threadTimelineArgs = threadTimelineArgs, threadListArgs =null)) } + override fun openThreadList(context: Context, threadTimelineArgs: ThreadTimelineArgs) { + context.startActivity(ThreadsActivity.newIntent( + context = context, + threadTimelineArgs = null, + threadListArgs = ThreadListArgs( + roomId = threadTimelineArgs.roomId, + displayName = threadTimelineArgs.displayName, + avatarUrl = threadTimelineArgs.avatarUrl + ))) + } } diff --git a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt index eeeb2a1b35a..02452cf6fcd 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt @@ -143,5 +143,6 @@ interface Navigator { fun openCallTransfer(context: Context, callId: String) fun openThread(context: Context, threadTimelineArgs: ThreadTimelineArgs) + fun openThreadList(context: Context, threadTimelineArgs: ThreadTimelineArgs) } diff --git a/vector/src/main/res/layout/fragment_thread_list.xml b/vector/src/main/res/layout/fragment_thread_list.xml new file mode 100644 index 00000000000..25dd2007377 --- /dev/null +++ b/vector/src/main/res/layout/fragment_thread_list.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/item_thread_summary.xml b/vector/src/main/res/layout/item_thread_summary.xml new file mode 100644 index 00000000000..075709ef008 --- /dev/null +++ b/vector/src/main/res/layout/item_thread_summary.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/item_timeline_event_base.xml b/vector/src/main/res/layout/item_timeline_event_base.xml index a1e1827d526..7094a28daa0 100644 --- a/vector/src/main/res/layout/item_timeline_event_base.xml +++ b/vector/src/main/res/layout/item_timeline_event_base.xml @@ -200,7 +200,27 @@ - + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/view_thread_room_summary.xml b/vector/src/main/res/layout/view_thread_room_summary.xml index 31bdd5ce062..59e2952b464 100644 --- a/vector/src/main/res/layout/view_thread_room_summary.xml +++ b/vector/src/main/res/layout/view_thread_room_summary.xml @@ -1,74 +1,58 @@ - + xmlns:tools="http://schemas.android.com/tools" + tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout"> + android:src="@drawable/ic_thread_summary" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> - + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toEndOf="@id/messageThreadSummaryAvatarImageView" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintWidth_default="wrap" + tools:text="Hello There, whats up! Its a large centence, whats up! Its a large centence" /> + diff --git a/vector/src/main/res/menu/menu_room_threads.xml b/vector/src/main/res/menu/menu_room_threads.xml deleted file mode 100644 index 3d4478332aa..00000000000 --- a/vector/src/main/res/menu/menu_room_threads.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - \ No newline at end of file diff --git a/vector/src/main/res/menu/menu_thread_list.xml b/vector/src/main/res/menu/menu_thread_list.xml new file mode 100644 index 00000000000..6da0f801127 --- /dev/null +++ b/vector/src/main/res/menu/menu_thread_list.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/menu/menu_thread_timeline.xml b/vector/src/main/res/menu/menu_thread_timeline.xml deleted file mode 100644 index 4698559bae7..00000000000 --- a/vector/src/main/res/menu/menu_thread_timeline.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file From 5e5ce614ef6d2c0116cf9b69379852e0cbc6d0bf Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Tue, 23 Nov 2021 17:09:58 +0200 Subject: [PATCH 012/581] Add date in view all threads UI Improvements on threads summary Add View In Room bottom sheet action from within thread timeline root message --- .../home/room/detail/RoomDetailViewModel.kt | 4 +-- .../home/room/detail/TimelineFragment.kt | 7 ++++ .../timeline/TimelineEventController.kt | 10 +++--- .../timeline/action/EventSharedAction.kt | 6 +++- .../action/MessageActionsViewModel.kt | 35 +++++++++++++++++++ .../factory/MergedHeaderItemFactory.kt | 2 +- .../timeline/factory/MessageItemFactory.kt | 11 +++++- .../timeline/factory/TimelineItemFactory.kt | 10 +++--- .../factory/TimelineItemFactoryParams.kt | 2 ++ .../helper/TimelineEventVisibilityHelper.kt | 17 ++++----- .../detail/timeline/item/AbsMessageItem.kt | 4 +-- .../threads/list/model/ThreadSummaryModel.kt | 8 +++-- .../list/viewmodel/ThreadSummaryController.kt | 8 +++-- .../main/res/layout/item_thread_summary.xml | 12 +++---- .../res/layout/view_thread_room_summary.xml | 3 +- vector/src/main/res/values/strings.xml | 1 + 16 files changed, 103 insertions(+), 37 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index 5854d35fb6a..1f5550b27f5 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -676,7 +676,7 @@ class RoomDetailViewModel @AssistedInject constructor( if (initialState.isThreadTimeline()) { when (itemId) { R.id.menu_thread_timeline_more -> true - else -> false + else -> false } } else { when (itemId) { @@ -688,7 +688,7 @@ class RoomDetailViewModel @AssistedInject constructor( // Show Join conference button only if there is an active conf id not joined. Otherwise fallback to default video disabled. ^ R.id.join_conference -> !state.isWebRTCCallOptionAvailable() && state.jitsiState.confId != null && !state.jitsiState.hasJoined R.id.search -> true - R.id.threads -> true + R.id.threads -> BuildConfig.THREADING_ENABLED R.id.dev_tools -> vectorPreferences.developerMode() else -> false } 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 a8e8e11b573..30d4a881f2c 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 @@ -2012,6 +2012,13 @@ class TimelineFragment @Inject constructor( requireActivity().toast(R.string.error_voice_message_cannot_reply_or_edit) } } + is EventSharedAction.ViewInRoom -> { + if (!views.voiceMessageRecorderView.isActive()) { + handleViewInRoomAction() + } else { + requireActivity().toast(R.string.error_voice_message_cannot_reply_or_edit) + } + } is EventSharedAction.CopyPermalink -> { val permalink = session.permalinkService().createPermalink(timelineArgs.roomId, action.eventId) copyToClipboard(requireContext(), permalink, false) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt index 11c90b34827..caa47835738 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt @@ -104,6 +104,8 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec roomSummary = state.asyncRoomSummary(), rootThreadEventId = state.rootThreadEventId ) + + fun isFromThreadTimeline():Boolean = rootThreadEventId != null } interface Callback : @@ -193,7 +195,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec // it's sent by the same user so we are sure we have up to date information. val invalidatedSenderId: String? = currentSnapshot.getOrNull(position)?.senderInfo?.userId val prevDisplayableEventIndex = currentSnapshot.subList(0, position).indexOfLast { - timelineEventVisibilityHelper.shouldShowEvent(it, partialState.highlightedEventId, partialState.rootThreadEventId ) + timelineEventVisibilityHelper.shouldShowEvent(it, partialState.highlightedEventId, partialState.isFromThreadTimeline() ) } if (prevDisplayableEventIndex != -1 && currentSnapshot[prevDisplayableEventIndex].senderInfo.userId == invalidatedSenderId) { modelCache[prevDisplayableEventIndex] = null @@ -370,7 +372,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec val nextEvent = currentSnapshot.nextOrNull(position) val prevEvent = currentSnapshot.prevOrNull(position) val nextDisplayableEvent = currentSnapshot.subList(position + 1, currentSnapshot.size).firstOrNull { - timelineEventVisibilityHelper.shouldShowEvent(it, partialState.highlightedEventId, partialState.rootThreadEventId) + timelineEventVisibilityHelper.shouldShowEvent(it, partialState.highlightedEventId, partialState.isFromThreadTimeline()) } // Should be build if not cached or if model should be refreshed if (modelCache[position] == null || modelCache[position]?.isCacheable == false) { @@ -452,7 +454,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec return null } // If the event is not shown, we go to the next one - if (!timelineEventVisibilityHelper.shouldShowEvent(event, partialState.highlightedEventId, partialState.rootThreadEventId)) { + if (!timelineEventVisibilityHelper.shouldShowEvent(event, partialState.highlightedEventId, partialState.isFromThreadTimeline())) { continue } // If the event is sent by us, we update the holder with the eventId and stop the search @@ -474,7 +476,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec val currentReadReceipts = ArrayList(event.readReceipts).filter { it.user.userId != session.myUserId } - if (timelineEventVisibilityHelper.shouldShowEvent(event, partialState.highlightedEventId, partialState.rootThreadEventId)) { + if (timelineEventVisibilityHelper.shouldShowEvent(event, partialState.highlightedEventId, partialState.isFromThreadTimeline())) { lastShownEventId = event.eventId } if (lastShownEventId == null) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/EventSharedAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/EventSharedAction.kt index c57d8449740..7bffba69b4c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/EventSharedAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/EventSharedAction.kt @@ -48,10 +48,14 @@ sealed class EventSharedAction(@StringRes val titleRes: Int, data class Reply(val eventId: String) : EventSharedAction(R.string.reply, R.drawable.ic_reply) + // TODO add translations data class ReplyInThread(val eventId: String) : - // TODO add translations EventSharedAction(R.string.reply_in_thread, R.drawable.ic_reply_in_thread) + // TODO add translations + object ViewInRoom : + EventSharedAction(R.string.view_in_room, R.drawable.ic_thread_view_in_room_menu_item) + data class Share(val eventId: String, val messageContent: MessageContent) : EventSharedAction(R.string.share, R.drawable.ic_share) 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 6762ed1479f..76e415ecebe 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 @@ -331,6 +331,10 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted add(EventSharedAction.ReplyInThread(eventId)) } + if (canViewInRoom(timelineEvent, messageContent, actionPermissions)) { + add(EventSharedAction.ViewInRoom) + } + if (canEdit(timelineEvent, session.myUserId, actionPermissions)) { add(EventSharedAction.Edit(eventId)) } @@ -417,6 +421,11 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted } } + /** + * Determine whether or not the Reply In Thread bottom sheet setting will be visible + * to the user + */ + // TODO handle reply in thread for images etc private fun canReplyInThread(event: TimelineEvent, messageContent: MessageContent?, actionPermissions: ActionPermissions): Boolean { @@ -437,6 +446,32 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted } } + /** + * Determine whether or no the selected event is a root thread event from within + * a thread timeline + */ + private fun canViewInRoom(event: TimelineEvent, + messageContent: MessageContent?, + actionPermissions: ActionPermissions): Boolean { + // Only event of type EventType.MESSAGE are supported for the moment + if (!BuildConfig.THREADING_ENABLED) return false + if (!initialState.isFromThreadTimeline) return false + if (event.root.getClearType() != EventType.MESSAGE) return false + if (!actionPermissions.canSendMessage) return false + + return when (messageContent?.msgType) { + MessageType.MSGTYPE_TEXT -> event.root.threadDetails?.isRootThread ?: false +// MessageType.MSGTYPE_NOTICE, +// MessageType.MSGTYPE_EMOTE, +// MessageType.MSGTYPE_IMAGE, +// MessageType.MSGTYPE_VIDEO, +// MessageType.MSGTYPE_AUDIO, +// MessageType.MSGTYPE_FILE -> true + else -> false + } + } + + private fun canQuote(event: TimelineEvent, messageContent: MessageContent?, actionPermissions: ActionPermissions): Boolean { // Only event of type EventType.MESSAGE are supported for the moment if (event.root.getClearType() != EventType.MESSAGE) return false diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt index fa699f0c786..1c25f923cfa 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt @@ -83,7 +83,7 @@ class MergedHeaderItemFactory @Inject constructor(private val activeSessionHolde eventIdToHighlight: String?, requestModelBuild: () -> Unit, callback: TimelineEventController.Callback?): MergedMembershipEventsItem_? { - val mergedEvents = timelineEventVisibilityHelper.prevSameTypeEvents(items, currentPosition, 2, eventIdToHighlight, partialState.rootThreadEventId) + val mergedEvents = timelineEventVisibilityHelper.prevSameTypeEvents(items, currentPosition, 2, eventIdToHighlight, partialState.isFromThreadTimeline()) return if (mergedEvents.isEmpty()) { null } else { 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 0ee28404df4..5e25b524739 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 @@ -125,6 +125,9 @@ class MessageItemFactory @Inject constructor( pillsPostProcessorFactory.create(roomId) } + + + fun create(params: TimelineItemFactoryParams): VectorEpoxyModel<*>? { val event = params.event val highlight = params.isHighlighted @@ -149,7 +152,10 @@ class MessageItemFactory @Inject constructor( // This is an edit event, we should display it when debugging as a notice event return noticeItemFactory.create(params) } - val attributes = messageItemAttributesFactory.create(messageContent, informationData, callback, event.root.threadDetails) + + // always hide summary when we are on thread timeline + val threadDetails = if(params.isFromThreadTimeline()) null else event.root.threadDetails + val attributes = messageItemAttributesFactory.create(messageContent, informationData, callback, threadDetails) // val all = event.root.toContent() // val ev = all.toModel() @@ -174,6 +180,9 @@ class MessageItemFactory @Inject constructor( } } + private fun isFromThreadTimeline(params: TimelineItemFactoryParams){ + params.rootThreadEventId + } private fun buildOptionsMessageItem(messageContent: MessageOptionsContent, informationData: MessageInformationData, highlight: Boolean, 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 96786e33770..11c46026d6f 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 @@ -42,8 +42,8 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me fun create(params: TimelineItemFactoryParams): VectorEpoxyModel<*> { val event = params.event val computedModel = try { - if (!timelineEventVisibilityHelper.shouldShowEvent(event, params.highlightedEventId, params.rootThreadEventId)) { - return buildEmptyItem(event, params.prevEvent, params.highlightedEventId, params.rootThreadEventId) + if (!timelineEventVisibilityHelper.shouldShowEvent(event, params.highlightedEventId, params.isFromThreadTimeline())) { + return buildEmptyItem(event, params.prevEvent, params.highlightedEventId, params.isFromThreadTimeline()) } when (event.root.getClearType()) { // Message itemsX @@ -109,11 +109,11 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me Timber.e(throwable, "failed to create message item") defaultItemFactory.create(params, throwable) } - return computedModel ?: buildEmptyItem(event, params.prevEvent, params.highlightedEventId, params.rootThreadEventId) + return computedModel ?: buildEmptyItem(event, params.prevEvent, params.highlightedEventId, params.isFromThreadTimeline()) } - private fun buildEmptyItem(timelineEvent: TimelineEvent, prevEvent: TimelineEvent?, highlightedEventId: String?, rootThreadEventId: String?): TimelineEmptyItem { - val isNotBlank = prevEvent == null || timelineEventVisibilityHelper.shouldShowEvent(prevEvent, highlightedEventId, rootThreadEventId) + private fun buildEmptyItem(timelineEvent: TimelineEvent, prevEvent: TimelineEvent?, highlightedEventId: String?, isFromThreadTimeline: Boolean): TimelineEmptyItem { + val isNotBlank = prevEvent == null || timelineEventVisibilityHelper.shouldShowEvent(prevEvent, highlightedEventId, isFromThreadTimeline) return TimelineEmptyItem_() .id(timelineEvent.localId) .eventId(timelineEvent.eventId) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactoryParams.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactoryParams.kt index 94e94911c05..8479d6b5891 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactoryParams.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactoryParams.kt @@ -38,4 +38,6 @@ data class TimelineItemFactoryParams( get() = partialState.rootThreadEventId val isHighlighted = highlightedEventId == event.eventId + + fun isFromThreadTimeline(): Boolean = rootThreadEventId != null } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt index c56e9d1336f..59a6c82affc 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt @@ -40,7 +40,7 @@ class TimelineEventVisibilityHelper @Inject constructor(private val userPreferen * * @return a list of timeline events which have sequentially the same type following the next direction. */ - private fun nextSameTypeEvents(timelineEvents: List, index: Int, minSize: Int, eventIdToHighlight: String?, rootThreadEventId: String?): List { + private fun nextSameTypeEvents(timelineEvents: List, index: Int, minSize: Int, eventIdToHighlight: String?, isFromThreadTimeline: Boolean): List { if (index >= timelineEvents.size - 1) { return emptyList() } @@ -62,7 +62,7 @@ class TimelineEventVisibilityHelper @Inject constructor(private val userPreferen } else { nextSameDayEvents.subList(0, indexOfFirstDifferentEventType) } - val filteredSameTypeEvents = sameTypeEvents.filter { shouldShowEvent(it, eventIdToHighlight, rootThreadEventId) } + val filteredSameTypeEvents = sameTypeEvents.filter { shouldShowEvent(it, eventIdToHighlight, isFromThreadTimeline) } if (filteredSameTypeEvents.size < minSize) { return emptyList() } @@ -77,21 +77,22 @@ class TimelineEventVisibilityHelper @Inject constructor(private val userPreferen * * @return a list of timeline events which have sequentially the same type following the prev direction. */ - fun prevSameTypeEvents(timelineEvents: List, index: Int, minSize: Int, eventIdToHighlight: String?, rootThreadEventId: String?): List { + fun prevSameTypeEvents(timelineEvents: List, index: Int, minSize: Int, eventIdToHighlight: String?, isFromThreadTimeline: Boolean): List { val prevSub = timelineEvents.subList(0, index + 1) return prevSub .reversed() .let { - nextSameTypeEvents(it, 0, minSize, eventIdToHighlight, rootThreadEventId) + nextSameTypeEvents(it, 0, minSize, eventIdToHighlight, isFromThreadTimeline) } } /** * @param timelineEvent the event to check for visibility * @param highlightedEventId can be checked to force visibility to true + * @param rootThreadEventId if this param is null it means we are in the original timeline * @return true if the event should be shown in the timeline. */ - fun shouldShowEvent(timelineEvent: TimelineEvent, highlightedEventId: String?, rootThreadEventId: String?): Boolean { + fun shouldShowEvent(timelineEvent: TimelineEvent, highlightedEventId: String?, isFromThreadTimeline: Boolean): Boolean { // If show hidden events is true we should always display something if (userPreferencesProvider.shouldShowHiddenEvents()) { return true @@ -105,14 +106,14 @@ class TimelineEventVisibilityHelper @Inject constructor(private val userPreferen } // Check for special case where we should hide the event, like redacted, relation, memberships... according to user preferences. - return !timelineEvent.shouldBeHidden(rootThreadEventId) + return !timelineEvent.shouldBeHidden(isFromThreadTimeline) } private fun TimelineEvent.isDisplayable(): Boolean { return TimelineDisplayableEvents.DISPLAYABLE_TYPES.contains(root.getClearType()) } - private fun TimelineEvent.shouldBeHidden(rootThreadEventId: String?): Boolean { + private fun TimelineEvent.shouldBeHidden(isFromThreadTimeline: Boolean): Boolean { if (root.isRedacted() && !userPreferencesProvider.shouldShowRedactedMessages()) { return true } @@ -125,7 +126,7 @@ class TimelineEventVisibilityHelper @Inject constructor(private val userPreferen if ((diff.isAvatarChange || diff.isDisplaynameChange) && !userPreferencesProvider.shouldShowAvatarDisplayNameChanges()) return true } - if(BuildConfig.THREADING_ENABLED && rootThreadEventId == null && root.isThread() && root.getRootThreadEventId() != null){ + if(BuildConfig.THREADING_ENABLED && !isFromThreadTimeline && root.isThread() && root.getRootThreadEventId() != null){ return true } 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 188a195ae61..ba7865ac576 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 @@ -114,9 +114,7 @@ abstract class AbsMessageItem : AbsBaseMessageItem threadDetails.threadSummarySenderInfo?.let { senderInfo -> attributes.avatarRenderer.render(MatrixItem.UserItem(senderInfo.userId, senderInfo.displayName, senderInfo.avatarUrl), holder.threadSummaryAvatarImageView) } - } - }else{ - holder.threadSummaryConstraintLayout.isVisible = false + } ?: run{holder.threadSummaryConstraintLayout.isVisible = false} } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadSummaryModel.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadSummaryModel.kt index 85e375d00db..57cba163eb8 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadSummaryModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadSummaryModel.kt @@ -37,7 +37,7 @@ abstract class ThreadSummaryModel : VectorEpoxyModel( @EpoxyAttribute lateinit var rootMessage: String @EpoxyAttribute lateinit var lastMessage: String @EpoxyAttribute lateinit var lastMessageCounter: String - @EpoxyAttribute lateinit var lastMessageMatrixItem: MatrixItem + @EpoxyAttribute var lastMessageMatrixItem: MatrixItem? = null override fun bind(holder: Holder) { super.bind(holder) @@ -48,8 +48,10 @@ abstract class ThreadSummaryModel : VectorEpoxyModel( holder.rootMessageTextView.text = rootMessage // Last message summary - avatarRenderer.render(lastMessageMatrixItem, holder.lastMessageAvatarImageView) - holder.lastMessageAvatarImageView.contentDescription = lastMessageMatrixItem.getBestName() + lastMessageMatrixItem?.let { + avatarRenderer.render(it, holder.lastMessageAvatarImageView) + } + holder.lastMessageAvatarImageView.contentDescription = lastMessageMatrixItem?.getBestName() holder.lastMessageTextView.text = lastMessage holder.lastMessageCounterTextView.text = lastMessageCounter } diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadSummaryController.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadSummaryController.kt index bd19c8e3ff2..a47d2b6c3b1 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadSummaryController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadSummaryController.kt @@ -17,13 +17,16 @@ package im.vector.app.features.home.room.threads.list.viewmodel import com.airbnb.epoxy.EpoxyController +import im.vector.app.core.date.DateFormatKind +import im.vector.app.core.date.VectorDateFormatter import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.threads.list.model.threadSummary import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject class ThreadSummaryController @Inject constructor( - private val avatarRenderer: AvatarRenderer + private val avatarRenderer: AvatarRenderer, + private val dateFormatter: VectorDateFormatter ) : EpoxyController() { var listener: Listener? = null @@ -53,12 +56,13 @@ class ThreadSummaryController @Inject constructor( // this one is added to the breadcrumbs safeViewState.rootThreadEventList.invoke() ?.forEach { timelineEvent -> + val date = dateFormatter.format(timelineEvent.root.originServerTs, DateFormatKind.ROOM_LIST) threadSummary { id(timelineEvent.eventId) avatarRenderer(host.avatarRenderer) matrixItem(timelineEvent.senderInfo.toMatrixItem()) title(timelineEvent.senderInfo.displayName) - date(timelineEvent.root.ageLocalTs.toString()) + date(date) rootMessage(timelineEvent.root.getDecryptedUserFriendlyTextSummary()) lastMessage(timelineEvent.root.threadDetails?.threadSummaryLatestTextMessage.orEmpty()) lastMessageCounter(timelineEvent.root.threadDetails?.numberOfThreads.toString()) diff --git a/vector/src/main/res/layout/item_thread_summary.xml b/vector/src/main/res/layout/item_thread_summary.xml index 075709ef008..130dae44b1e 100644 --- a/vector/src/main/res/layout/item_thread_summary.xml +++ b/vector/src/main/res/layout/item_thread_summary.xml @@ -31,7 +31,7 @@ app:layout_constraintEnd_toStartOf="@id/threadSummaryDateTextView" app:layout_constraintStart_toEndOf="@id/threadSummaryAvatarImageView" app:layout_constraintTop_toTopOf="parent" - tools:text="Aris" /> + tools:text="Aris Kots" /> + tools:text="10 minutes" /> + tools:text="192" /> Edit Reply Reply In Thread + View In Room Retry "Join a room to start using the app." From e2bf3e7097317e54a61861bef53205ac6f92141b Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Tue, 23 Nov 2021 22:22:58 +0200 Subject: [PATCH 013/581] Add navigation to thread from the thread list Add thread creation from photos, files, images, audios, replies etc Add slide animation to view threads from thread list Add click listener on room summary to handle cases like ( there is an image and the user upon click should view the image instead of navigating to the thread, so he can click the thread summary ) --- .../home/room/detail/TimelineFragment.kt | 22 +++++++++---- .../timeline/TimelineEventController.kt | 5 +++ .../action/MessageActionsViewModel.kt | 28 ++++++++-------- .../helper/MessageItemAttributesFactory.kt | 1 + .../timeline/item/AbsBaseMessageItem.kt | 1 - .../detail/timeline/item/AbsMessageItem.kt | 8 +++++ .../room/detail/timeline/item/NoticeItem.kt | 3 +- .../home/room/threads/ThreadsActivity.kt | 33 +++++++++++++++++++ .../threads/list/model/ThreadSummaryModel.kt | 8 +++++ .../list/viewmodel/ThreadSummaryController.kt | 6 +++- .../threads/list/views/ThreadListFragment.kt | 26 +++++++++++---- .../app/features/navigation/Navigator.kt | 1 + .../main/res/anim/animation_slide_in_left.xml | 5 +++ .../res/anim/animation_slide_in_right.xml | 5 +++ .../res/anim/animation_slide_out_left.xml | 5 +++ .../res/anim/animation_slide_out_right.xml | 5 +++ .../main/res/layout/item_thread_summary.xml | 10 ++++-- .../view_room_detail_thread_toolbar.xml | 8 ++--- .../res/layout/view_thread_room_summary.xml | 8 ++--- vector/src/main/res/values/strings.xml | 3 +- 20 files changed, 149 insertions(+), 42 deletions(-) create mode 100644 vector/src/main/res/anim/animation_slide_in_left.xml create mode 100644 vector/src/main/res/anim/animation_slide_in_right.xml create mode 100644 vector/src/main/res/anim/animation_slide_out_left.xml create mode 100644 vector/src/main/res/anim/animation_slide_out_right.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 30d4a881f2c..1c37b26c60d 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 @@ -1002,10 +1002,10 @@ class TimelineFragment @Inject constructor( /** * View and highlight the original root thread message in the main timeline */ - private fun handleViewInRoomAction(){ + private fun handleViewInRoomAction() { getRootThreadEventId()?.let { - val newRoom = timelineArgs.copy(threadTimelineArgs = null,eventId = it) - context?.let{ con -> + val newRoom = timelineArgs.copy(threadTimelineArgs = null, eventId = it) + context?.let { con -> val int = RoomDetailActivity.newIntent(con, newRoom) int.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK con.startActivity(int) @@ -1473,6 +1473,7 @@ class TimelineFragment @Inject constructor( avatarRenderer.render(matrixItem, views.includeThreadToolbar.roomToolbarThreadImageView) views.includeThreadToolbar.roomToolbarThreadSubtitleTextView.text = it.displayName } + views.includeThreadToolbar.roomToolbarThreadTitleTextView.text = resources.getText(R.string.thread_timeline_title) } } @@ -1776,9 +1777,9 @@ class TimelineFragment @Inject constructor( is EncryptedEventContent -> { roomDetailViewModel.handle(RoomDetailAction.TapOnFailedToDecrypt(informationData.eventId)) } - } - if (BuildConfig.THREADING_ENABLED && isRootThreadEvent && !isThreadTimeLine()) { - navigateToThreadTimeline(informationData.eventId) + else -> { + onThreadSummaryClicked(informationData.eventId, isRootThreadEvent) + } } } @@ -1809,6 +1810,13 @@ class TimelineFragment @Inject constructor( } } + override fun onThreadSummaryClicked(eventId: String, isRootThreadEvent: Boolean) { + if (BuildConfig.THREADING_ENABLED && isRootThreadEvent && !isThreadTimeLine()) { + navigateToThreadTimeline(eventId) + } + } + + override fun onAvatarClicked(informationData: MessageInformationData) { // roomDetailViewModel.handle(RoomDetailAction.RequestVerification(informationData.userId)) openRoomMemberProfile(informationData.senderId) @@ -2012,7 +2020,7 @@ class TimelineFragment @Inject constructor( requireActivity().toast(R.string.error_voice_message_cannot_reply_or_edit) } } - is EventSharedAction.ViewInRoom -> { + is EventSharedAction.ViewInRoom -> { if (!views.voiceMessageRecorderView.isActive()) { handleViewInRoomAction() } else { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt index caa47835738..b091ea2fb70 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt @@ -112,6 +112,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec BaseCallback, ReactionPillCallback, AvatarCallback, + ThreadCallback, UrlClickCallback, ReadReceiptsCallback, PreviewUrlCallback { @@ -151,6 +152,10 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec fun onMemberNameClicked(informationData: MessageInformationData) } + interface ThreadCallback { + fun onThreadSummaryClicked(eventId: String, isRootThreadEvent: Boolean) + } + interface ReadReceiptsCallback { fun onReadReceiptsClicked(readReceipts: List) fun onReadMarkerVisible() 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 76e415ecebe..5f30ca6c39a 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 @@ -435,13 +435,13 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted if (event.root.getClearType() != EventType.MESSAGE) return false if (!actionPermissions.canSendMessage) return false return when (messageContent?.msgType) { - MessageType.MSGTYPE_TEXT -> true -// MessageType.MSGTYPE_NOTICE, -// MessageType.MSGTYPE_EMOTE, -// MessageType.MSGTYPE_IMAGE, -// MessageType.MSGTYPE_VIDEO, -// MessageType.MSGTYPE_AUDIO, -// MessageType.MSGTYPE_FILE -> true + MessageType.MSGTYPE_TEXT, + MessageType.MSGTYPE_NOTICE, + MessageType.MSGTYPE_EMOTE, + MessageType.MSGTYPE_IMAGE, + MessageType.MSGTYPE_VIDEO, + MessageType.MSGTYPE_AUDIO, + MessageType.MSGTYPE_FILE -> true else -> false } } @@ -460,13 +460,13 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted if (!actionPermissions.canSendMessage) return false return when (messageContent?.msgType) { - MessageType.MSGTYPE_TEXT -> event.root.threadDetails?.isRootThread ?: false -// MessageType.MSGTYPE_NOTICE, -// MessageType.MSGTYPE_EMOTE, -// MessageType.MSGTYPE_IMAGE, -// MessageType.MSGTYPE_VIDEO, -// MessageType.MSGTYPE_AUDIO, -// MessageType.MSGTYPE_FILE -> true + MessageType.MSGTYPE_TEXT, + MessageType.MSGTYPE_NOTICE, + MessageType.MSGTYPE_EMOTE, + MessageType.MSGTYPE_IMAGE, + MessageType.MSGTYPE_VIDEO, + MessageType.MSGTYPE_AUDIO, + MessageType.MSGTYPE_FILE -> event.root.threadDetails?.isRootThread ?: false else -> false } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt index a30a0b851e5..8cc5ffe1ee6 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt @@ -50,6 +50,7 @@ class MessageItemAttributesFactory @Inject constructor( }, reactionPillCallback = callback, avatarCallback = callback, + threadCallback = callback, readReceiptsCallback = callback, emojiTypeFace = emojiCompatFontProvider.typeface, threadDetails = threadDetails diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt index 080b7662580..a3e808c7bb5 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt @@ -127,7 +127,6 @@ abstract class AbsBaseMessageItem : BaseEventItem val messageColorProvider: MessageColorProvider val itemLongClickListener: View.OnLongClickListener? val itemClickListener: ClickListener? - // val memberClickListener: ClickListener? val reactionPillCallback: TimelineEventController.ReactionPillCallback? 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 ba7865ac576..977a5b426a6 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 @@ -67,6 +67,11 @@ abstract class AbsMessageItem : AbsBaseMessageItem } } + private val _threadClickListener = object : ClickListener { + override fun invoke(p1: View) { + attributes.threadCallback?.onThreadSummaryClicked(attributes.informationData.eventId, attributes.threadDetails?.isRootThread ?: false) + } + } override fun bind(holder: H) { super.bind(holder) if (attributes.informationData.showInformation) { @@ -107,6 +112,7 @@ abstract class AbsMessageItem : AbsBaseMessageItem // Threads if(BuildConfig.THREADING_ENABLED) { + holder.threadSummaryConstraintLayout.onClick(_threadClickListener) attributes.threadDetails?.let { threadDetails -> holder.threadSummaryConstraintLayout.isVisible = threadDetails.isRootThread holder.threadSummaryCounterTextView.text = threadDetails.numberOfThreads.toString() @@ -125,6 +131,7 @@ abstract class AbsMessageItem : AbsBaseMessageItem holder.avatarImageView.setOnLongClickListener(null) holder.memberNameView.setOnClickListener(null) holder.memberNameView.setOnLongClickListener(null) + holder.threadSummaryConstraintLayout.setOnClickListener(null) super.unbind(holder) } @@ -156,6 +163,7 @@ abstract class AbsMessageItem : AbsBaseMessageItem val memberClickListener: ClickListener? = null, override val reactionPillCallback: TimelineEventController.ReactionPillCallback? = null, val avatarCallback: TimelineEventController.AvatarCallback? = null, + val threadCallback: TimelineEventController.ThreadCallback? = null, override val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null, val emojiTypeFace: Typeface? = null, val threadDetails: ThreadDetails? = null diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/NoticeItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/NoticeItem.kt index 4876e8e500c..3693a5002ea 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/NoticeItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/NoticeItem.kt @@ -77,7 +77,8 @@ abstract class NoticeItem : BaseEventItem() { val noticeText: CharSequence, val itemLongClickListener: View.OnLongClickListener? = null, val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null, - val avatarClickListener: ClickListener? = null + val avatarClickListener: ClickListener? = null, + val threadSummaryClickListener: ClickListener? = null ) companion object { diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt index 007b4195325..db052a42d33 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt @@ -19,9 +19,16 @@ package im.vector.app.features.home.room.threads import android.content.Context import android.content.Intent import android.os.Bundle +import android.view.View +import android.view.ViewGroup +import androidx.core.view.ViewCompat +import androidx.core.view.children +import androidx.fragment.app.FragmentTransaction import com.google.android.material.appbar.MaterialToolbar import im.vector.app.R import im.vector.app.core.di.ScreenComponent +import im.vector.app.core.extensions.addFragment +import im.vector.app.core.extensions.addFragmentToBackstack import im.vector.app.core.extensions.replaceFragment import im.vector.app.core.platform.ToolbarConfigurable import im.vector.app.core.platform.VectorBaseActivity @@ -32,6 +39,7 @@ import im.vector.app.features.home.room.detail.TimelineFragment import im.vector.app.features.home.room.threads.arguments.ThreadListArgs import im.vector.app.features.home.room.threads.arguments.ThreadTimelineArgs import im.vector.app.features.home.room.threads.list.views.ThreadListFragment +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import javax.inject.Inject class ThreadsActivity : VectorBaseActivity(), ToolbarConfigurable { @@ -89,6 +97,31 @@ class ThreadsActivity : VectorBaseActivity(), ToolbarCon threadTimelineArgs = threadTimelineArgs )) + /** + * This function is used to navigate to the selected thread timeline. + * One usage of that is from the Threads Activity + */ + fun navigateToThreadTimeline( + timelineEvent: TimelineEvent) { + val roomThreadDetailArgs = ThreadTimelineArgs( + roomId = timelineEvent.roomId, + displayName = timelineEvent.senderInfo.displayName, + avatarUrl = timelineEvent.senderInfo.avatarUrl, + rootThreadEventId = timelineEvent.eventId) + val commonOption: (FragmentTransaction) -> Unit = { + it.setCustomAnimations(R.anim.animation_slide_in_right, R.anim.animation_slide_out_left, R.anim.animation_slide_in_left, R.anim.animation_slide_out_right) + } + addFragmentToBackstack( + frameId = R.id.threadsActivityFragmentContainer, + fragmentClass = TimelineFragment::class.java, + params = TimelineArgs( + roomId = timelineEvent.roomId, + threadTimelineArgs = roomThreadDetailArgs + ), + option = commonOption + ) + } + override fun configure(toolbar: MaterialToolbar) { configureToolbar(toolbar) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadSummaryModel.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadSummaryModel.kt index 57cba163eb8..8ed19a97c83 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadSummaryModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadSummaryModel.kt @@ -16,13 +16,17 @@ package im.vector.app.features.home.room.threads.list.model +import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView +import androidx.constraintlayout.widget.ConstraintLayout import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.app.R +import im.vector.app.core.epoxy.ClickListener import im.vector.app.core.epoxy.VectorEpoxyHolder import im.vector.app.core.epoxy.VectorEpoxyModel +import im.vector.app.core.epoxy.onClick import im.vector.app.features.displayname.getBestName import im.vector.app.features.home.AvatarRenderer import org.matrix.android.sdk.api.util.MatrixItem @@ -38,9 +42,11 @@ abstract class ThreadSummaryModel : VectorEpoxyModel( @EpoxyAttribute lateinit var lastMessage: String @EpoxyAttribute lateinit var lastMessageCounter: String @EpoxyAttribute var lastMessageMatrixItem: MatrixItem? = null + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var itemClickListener: ClickListener? = null override fun bind(holder: Holder) { super.bind(holder) + holder.rootView.onClick(itemClickListener) avatarRenderer.render(matrixItem, holder.avatarImageView) holder.avatarImageView.contentDescription = matrixItem.getBestName() holder.titleTextView.text = title @@ -54,6 +60,7 @@ abstract class ThreadSummaryModel : VectorEpoxyModel( holder.lastMessageAvatarImageView.contentDescription = lastMessageMatrixItem?.getBestName() holder.lastMessageTextView.text = lastMessage holder.lastMessageCounterTextView.text = lastMessageCounter + } class Holder : VectorEpoxyHolder() { @@ -64,5 +71,6 @@ abstract class ThreadSummaryModel : VectorEpoxyModel( val lastMessageAvatarImageView by bind(R.id.messageThreadSummaryAvatarImageView) val lastMessageCounterTextView by bind(R.id.messageThreadSummaryCounterTextView) val lastMessageTextView by bind(R.id.messageThreadSummaryInfoTextView) + val rootView by bind(R.id.threadSummaryRootConstraintLayout) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadSummaryController.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadSummaryController.kt index a47d2b6c3b1..7b7480092c1 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadSummaryController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadSummaryController.kt @@ -21,6 +21,7 @@ import im.vector.app.core.date.DateFormatKind import im.vector.app.core.date.VectorDateFormatter import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.threads.list.model.threadSummary +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject @@ -67,11 +68,14 @@ class ThreadSummaryController @Inject constructor( lastMessage(timelineEvent.root.threadDetails?.threadSummaryLatestTextMessage.orEmpty()) lastMessageCounter(timelineEvent.root.threadDetails?.numberOfThreads.toString()) lastMessageMatrixItem(timelineEvent.root.threadDetails?.threadSummarySenderInfo?.toMatrixItem()) + itemClickListener { + host.listener?.onThreadClicked(timelineEvent) + } } } } interface Listener { - fun onBreadcrumbClicked(roomId: String) + fun onThreadClicked(timelineEvent: TimelineEvent) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt index d2551b58c1d..eb732c44c9e 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt @@ -20,7 +20,9 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.Toast import androidx.core.view.isVisible +import androidx.transition.TransitionInflater import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState @@ -33,10 +35,13 @@ import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.breadcrumbs.BreadcrumbsAnimator import im.vector.app.features.home.room.breadcrumbs.BreadcrumbsViewModel import im.vector.app.features.home.room.detail.RoomDetailSharedActionViewModel +import im.vector.app.features.home.room.threads.ThreadsActivity import im.vector.app.features.home.room.threads.arguments.ThreadListArgs +import im.vector.app.features.home.room.threads.arguments.ThreadTimelineArgs import im.vector.app.features.home.room.threads.list.viewmodel.ThreadSummaryController import im.vector.app.features.home.room.threads.list.viewmodel.ThreadSummaryViewModel import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.util.MatrixItem import javax.inject.Inject @@ -45,7 +50,8 @@ class ThreadListFragment @Inject constructor( private val avatarRenderer: AvatarRenderer, private val threadSummaryController: ThreadSummaryController, val threadSummaryViewModelFactory: ThreadSummaryViewModel.Factory -) : VectorBaseFragment() { +) : VectorBaseFragment(), + ThreadSummaryController.Listener { private val threadSummaryViewModel: ThreadSummaryViewModel by fragmentViewModel() @@ -65,15 +71,16 @@ class ThreadListFragment @Inject constructor( super.onViewCreated(view, savedInstanceState) initToolbar() views.threadListRecyclerView.configureWith(threadSummaryController, BreadcrumbsAnimator(), hasFixedSize = false) -// threadSummaryController.listener = this + threadSummaryController.listener = this } override fun onDestroyView() { views.threadListRecyclerView.cleanup() -// breadcrumbsController.listener = null + threadSummaryController.listener = null super.onDestroyView() } - private fun initToolbar(){ + + private fun initToolbar() { setupToolbar(views.threadListToolbar) renderToolbar() } @@ -84,8 +91,13 @@ class ThreadListFragment @Inject constructor( private fun renderToolbar() { views.includeThreadListToolbar.roomToolbarThreadConstraintLayout.isVisible = true - val matrixItem = MatrixItem.RoomItem(threadListArgs.roomId, threadListArgs.displayName, threadListArgs.avatarUrl) - avatarRenderer.render(matrixItem, views.includeThreadListToolbar.roomToolbarThreadImageView) - views.includeThreadListToolbar.roomToolbarThreadSubtitleTextView.text = threadListArgs.displayName + val matrixItem = MatrixItem.RoomItem(threadListArgs.roomId, threadListArgs.displayName, threadListArgs.avatarUrl) + avatarRenderer.render(matrixItem, views.includeThreadListToolbar.roomToolbarThreadImageView) + views.includeThreadListToolbar.roomToolbarThreadTitleTextView.text = resources.getText(R.string.thread_list_title) + views.includeThreadListToolbar.roomToolbarThreadSubtitleTextView.text = threadListArgs.displayName + } + + override fun onThreadClicked(timelineEvent: TimelineEvent) { + (activity as? ThreadsActivity)?.navigateToThreadTimeline(timelineEvent) } } diff --git a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt index 02452cf6fcd..37783d022be 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt @@ -143,6 +143,7 @@ interface Navigator { fun openCallTransfer(context: Context, callId: String) fun openThread(context: Context, threadTimelineArgs: ThreadTimelineArgs) + fun openThreadList(context: Context, threadTimelineArgs: ThreadTimelineArgs) } diff --git a/vector/src/main/res/anim/animation_slide_in_left.xml b/vector/src/main/res/anim/animation_slide_in_left.xml new file mode 100644 index 00000000000..46547c691d0 --- /dev/null +++ b/vector/src/main/res/anim/animation_slide_in_left.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/vector/src/main/res/anim/animation_slide_in_right.xml b/vector/src/main/res/anim/animation_slide_in_right.xml new file mode 100644 index 00000000000..d0366bc633f --- /dev/null +++ b/vector/src/main/res/anim/animation_slide_in_right.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/vector/src/main/res/anim/animation_slide_out_left.xml b/vector/src/main/res/anim/animation_slide_out_left.xml new file mode 100644 index 00000000000..3d734533df5 --- /dev/null +++ b/vector/src/main/res/anim/animation_slide_out_left.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/vector/src/main/res/anim/animation_slide_out_right.xml b/vector/src/main/res/anim/animation_slide_out_right.xml new file mode 100644 index 00000000000..60a3f227217 --- /dev/null +++ b/vector/src/main/res/anim/animation_slide_out_right.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/item_thread_summary.xml b/vector/src/main/res/layout/item_thread_summary.xml index 130dae44b1e..8cf93c54047 100644 --- a/vector/src/main/res/layout/item_thread_summary.xml +++ b/vector/src/main/res/layout/item_thread_summary.xml @@ -1,12 +1,18 @@ - + android:paddingEnd="0dp" + android:background="?android:colorBackground" + android:clickable="true" + android:focusable="true" + android:foreground="?attr/selectableItemBackground"> + android:visibility="gone" + tools:visibility="visible"> + app:layout_constraintTop_toTopOf="parent" + tools:text="@string/thread_timeline_title" /> + tools:text="Hello There, whats up! Its a large sentence whats up! Its a large centence" /> diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index e99cb880e1d..316b24f4fbd 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1029,7 +1029,8 @@ Filter Threads in room - Thread + Thread + Threads Reason for reporting this content From afc69c77bda302c845014bdddd0c5ce78191706f Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Wed, 24 Nov 2021 18:23:33 +0200 Subject: [PATCH 014/581] Add local filtering in thread list --- .../sdk/api/session/events/model/Event.kt | 2 +- .../session/room/timeline/TimelineService.kt | 7 ++ .../database/helper/ThreadEventsHelper.kt | 17 ++++- .../internal/database/mapper/EventMapper.kt | 2 +- .../room/timeline/DefaultTimelineService.kt | 14 +++- .../list/viewmodel/ThreadSummaryController.kt | 16 +---- .../list/viewmodel/ThreadSummaryViewModel.kt | 39 ++++++----- .../list/viewmodel/ThreadSummaryViewState.kt | 3 +- .../list/views/ThreadListBottomSheet.kt | 69 +++++++++++++++++++ .../threads/list/views/ThreadListFragment.kt | 22 +++--- .../res/layout/bottom_sheet_thread_list.xml | 47 +++++++++++++ vector/src/main/res/values/strings.xml | 7 +- 12 files changed, 199 insertions(+), 46 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListBottomSheet.kt create mode 100644 vector/src/main/res/layout/bottom_sheet_thread_list.xml diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt index 77285dd463e..621e525bd3e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt @@ -195,7 +195,7 @@ data class Event( * It can be used especially for message summaries. * It will return a decrypted text message or an empty string otherwise. */ - fun getDecryptedUserFriendlyTextSummary(): String { + fun getDecryptedTextSummary(): String { val text = getDecryptedValue().orEmpty() return when { isReply() || isQuote() -> ContentUtils.extractUsefulTextFromReply(text) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineService.kt index aa703432796..6b1ad5554b8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineService.kt @@ -68,4 +68,11 @@ interface TimelineService { */ fun getAllThreads(): List + /** + * Returns whether or not the current user is participating in the thread + * @param rootThreadEventId the eventId of the current thread + */ + fun isUserParticipatingInThread(rootThreadEventId: String, senderId: String): Boolean + + } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt index 755891af3e7..aa3ba0fc253 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt @@ -86,7 +86,6 @@ internal fun EventEntity.findAllThreadsForRootEventId(realm: Realm, rootThreadEv .sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING) .findAll() - /** * Find all TimelineEventEntity that are root threads for the specified room * @param roomId The room that all stored root threads will be returned @@ -94,6 +93,20 @@ internal fun EventEntity.findAllThreadsForRootEventId(realm: Realm, rootThreadEv internal fun TimelineEventEntity.Companion.findAllThreadsForRoomId(realm: Realm, roomId: String): RealmQuery = TimelineEventEntity .whereRoomId(realm, roomId = roomId) - .equalTo(TimelineEventEntityFields.ROOT.IS_ROOT_THREAD,true) + .equalTo(TimelineEventEntityFields.ROOT.IS_ROOT_THREAD, true) .sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING) +/** + * Returns whether or not the given user is participating in a current thread + * @param roomId the room that the thread exists + * @param rootThreadEventId the thread that the search will be done + * @param senderId the user that will try to find participation + */ +internal fun TimelineEventEntity.Companion.isUserParticipatingInThread(realm: Realm, roomId: String, rootThreadEventId: String, senderId: String): Boolean = + TimelineEventEntity + .whereRoomId(realm, roomId = roomId) + .equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, rootThreadEventId) + .equalTo(TimelineEventEntityFields.ROOT.SENDER, senderId) + .findFirst() + ?.let { true } + ?: false diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt index aded11e815a..cf16138196b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt @@ -111,7 +111,7 @@ internal object EventMapper { avatarUrl = timelineEventEntity.senderAvatar ) }, - threadSummaryLatestTextMessage = eventEntity.threadSummaryLatestMessage?.root?.asDomain()?.getDecryptedUserFriendlyTextSummary().orEmpty() + threadSummaryLatestTextMessage = eventEntity.threadSummaryLatestMessage?.root?.asDomain()?.getDecryptedTextSummary().orEmpty() ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt index 690f3008270..2335f7bcd2a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt @@ -21,6 +21,7 @@ import com.zhuinden.monarchy.Monarchy import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import io.realm.Realm import io.realm.Sort import io.realm.kotlin.where import org.matrix.android.sdk.api.session.events.model.isImageMessage @@ -32,10 +33,10 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.database.RealmSessionProvider import org.matrix.android.sdk.internal.database.helper.findAllThreadsForRoomId +import org.matrix.android.sdk.internal.database.helper.isUserParticipatingInThread import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper import org.matrix.android.sdk.internal.database.model.TimelineEventEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields -import org.matrix.android.sdk.internal.database.query.findAllInRoomWithSendStates import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask @@ -111,10 +112,21 @@ internal class DefaultTimelineService @AssistedInject constructor( { timelineEventMapper.map(it) } ) } + override fun getAllThreads(): List { return monarchy.fetchAllMappedSync( { TimelineEventEntity.findAllThreadsForRoomId(it, roomId = roomId) }, { timelineEventMapper.map(it) } ) } + + override fun isUserParticipatingInThread(rootThreadEventId: String, senderId: String): Boolean { + return Realm.getInstance(monarchy.realmConfiguration).use { + TimelineEventEntity.isUserParticipatingInThread( + realm = it, + roomId = roomId, + rootThreadEventId = rootThreadEventId, + senderId = senderId) + } + } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadSummaryController.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadSummaryController.kt index 7b7480092c1..2e3b58bb773 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadSummaryController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadSummaryController.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020 New Vector Ltd + * Copyright 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. @@ -34,12 +34,6 @@ class ThreadSummaryController @Inject constructor( private var viewState: ThreadSummaryViewState? = null - init { - // We are requesting a model build directly as the first build of epoxy is on the main thread. - // It avoids to build the whole list of breadcrumbs on the main thread. - requestModelBuild() - } - fun update(viewState: ThreadSummaryViewState) { this.viewState = viewState requestModelBuild() @@ -48,13 +42,7 @@ class ThreadSummaryController @Inject constructor( override fun buildModels() { val safeViewState = viewState ?: return val host = this - // Add a ZeroItem to avoid automatic scroll when the breadcrumbs are updated from another client -// zeroItem { -// id("top") -// } - // An empty breadcrumbs list can only be temporary because when entering in a room, - // this one is added to the breadcrumbs safeViewState.rootThreadEventList.invoke() ?.forEach { timelineEvent -> val date = dateFormatter.format(timelineEvent.root.originServerTs, DateFormatKind.ROOM_LIST) @@ -64,7 +52,7 @@ class ThreadSummaryController @Inject constructor( matrixItem(timelineEvent.senderInfo.toMatrixItem()) title(timelineEvent.senderInfo.displayName) date(date) - rootMessage(timelineEvent.root.getDecryptedUserFriendlyTextSummary()) + rootMessage(timelineEvent.root.getDecryptedTextSummary()) lastMessage(timelineEvent.root.threadDetails?.threadSummaryLatestTextMessage.orEmpty()) lastMessageCounter(timelineEvent.root.threadDetails?.numberOfThreads.toString()) lastMessageMatrixItem(timelineEvent.root.threadDetails?.threadSummarySenderInfo?.toMatrixItem()) diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadSummaryViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadSummaryViewModel.kt index 385213470a8..449090cc739 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadSummaryViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadSummaryViewModel.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020 New Vector Ltd + * Copyright 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. @@ -26,16 +26,14 @@ import im.vector.app.core.platform.EmptyAction import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.home.room.threads.list.views.ThreadListFragment -import org.matrix.android.sdk.api.query.QueryStringValue +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.session.room.Room -import org.matrix.android.sdk.api.session.room.model.Membership -import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.flow.flow class ThreadSummaryViewModel @AssistedInject constructor(@Assisted val initialState: ThreadSummaryViewState, private val session: Session) : - VectorViewModel(initialState) { + VectorViewModel(initialState) { private val room = session.getRoom(initialState.roomId) @@ -54,19 +52,28 @@ class ThreadSummaryViewModel @AssistedInject constructor(@Assisted val initialSt } init { - observeThreadsSummary() + observeThreadsList(initialState.shouldFilterThreads) } - override fun handle(action: EmptyAction) { - // No op - } + override fun handle(action: EmptyAction) {} + private fun observeThreadsList(shouldFilterThreads: Boolean) = + room?.flow() + ?.liveThreadList() + ?.map { + if (!shouldFilterThreads) return@map it + it.filter { timelineEvent -> + room.isUserParticipatingInThread(timelineEvent.eventId, session.myUserId) + } + } + ?.flowOn(room.coroutineDispatchers.io) + ?.execute { asyncThreads -> + copy( + rootThreadEventList = asyncThreads, + shouldFilterThreads = shouldFilterThreads) + } - private fun observeThreadsSummary() { - room?.flow() - ?.liveThreadList() - ?.execute { asyncThreads -> - copy(rootThreadEventList = asyncThreads) - } + fun applyFiltering(shouldFilterThreads: Boolean) { + observeThreadsList(shouldFilterThreads) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadSummaryViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadSummaryViewState.kt index b0c9c2ea261..13f11897083 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadSummaryViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadSummaryViewState.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020 New Vector Ltd + * Copyright 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. @@ -24,6 +24,7 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent data class ThreadSummaryViewState( val rootThreadEventList: Async> = Uninitialized, + val shouldFilterThreads: Boolean = false, val roomId: String ) : MavericksState{ diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListBottomSheet.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListBottomSheet.kt new file mode 100644 index 00000000000..b3938a10a84 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListBottomSheet.kt @@ -0,0 +1,69 @@ +/* + * 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.home.room.threads.list.views + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.appcompat.content.res.AppCompatResources.getDrawable +import androidx.core.content.ContextCompat +import com.airbnb.mvrx.parentFragmentViewModel +import im.vector.app.R +import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment +import im.vector.app.databinding.BottomSheetThreadListBinding +import im.vector.app.features.home.room.threads.list.viewmodel.ThreadSummaryViewModel +import im.vector.app.features.home.room.threads.list.viewmodel.ThreadSummaryViewState + +class ThreadListBottomSheet : VectorBaseBottomSheetDialogFragment() { + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetThreadListBinding { + return BottomSheetThreadListBinding.inflate(inflater, container, false) + } + + + private val threadListViewModel: ThreadSummaryViewModel by parentFragmentViewModel() + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + threadListViewModel.subscribe(this){ + renderState(it) + } + views.threadListModalAllThreads.views.bottomSheetActionClickableZone.debouncedClicks { + threadListViewModel.applyFiltering(false) + dismiss() + } + views.threadListModalMyThreads.views.bottomSheetActionClickableZone.debouncedClicks { + threadListViewModel.applyFiltering(true) + dismiss() + } + + } + + private fun renderState(state: ThreadSummaryViewState) { + + if(state.shouldFilterThreads){ + views.threadListModalAllThreads.rightIcon = null + views.threadListModalMyThreads.rightIcon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_tick) + }else{ + views.threadListModalAllThreads.rightIcon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_tick) + views.threadListModalMyThreads.rightIcon = null + } + + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt index eb732c44c9e..0f342704817 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt @@ -18,11 +18,10 @@ package im.vector.app.features.home.room.threads.list.views import android.os.Bundle import android.view.LayoutInflater +import android.view.MenuItem import android.view.View import android.view.ViewGroup -import android.widget.Toast import androidx.core.view.isVisible -import androidx.transition.TransitionInflater import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState @@ -32,21 +31,16 @@ import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentThreadListBinding import im.vector.app.features.home.AvatarRenderer -import im.vector.app.features.home.room.breadcrumbs.BreadcrumbsAnimator -import im.vector.app.features.home.room.breadcrumbs.BreadcrumbsViewModel -import im.vector.app.features.home.room.detail.RoomDetailSharedActionViewModel +import im.vector.app.features.home.room.detail.timeline.animation.TimelineItemAnimator import im.vector.app.features.home.room.threads.ThreadsActivity import im.vector.app.features.home.room.threads.arguments.ThreadListArgs -import im.vector.app.features.home.room.threads.arguments.ThreadTimelineArgs import im.vector.app.features.home.room.threads.list.viewmodel.ThreadSummaryController import im.vector.app.features.home.room.threads.list.viewmodel.ThreadSummaryViewModel -import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.util.MatrixItem import javax.inject.Inject class ThreadListFragment @Inject constructor( - private val session: Session, private val avatarRenderer: AvatarRenderer, private val threadSummaryController: ThreadSummaryController, val threadSummaryViewModelFactory: ThreadSummaryViewModel.Factory @@ -67,10 +61,20 @@ class ThreadListFragment @Inject constructor( super.onCreate(savedInstanceState) } + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.menu_thread_list_filter -> { + ThreadListBottomSheet().show(childFragmentManager, "Filtering") + true + } + else -> super.onOptionsItemSelected(item) + } + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) initToolbar() - views.threadListRecyclerView.configureWith(threadSummaryController, BreadcrumbsAnimator(), hasFixedSize = false) + views.threadListRecyclerView.configureWith(threadSummaryController, TimelineItemAnimator(), hasFixedSize = false) threadSummaryController.listener = this } diff --git a/vector/src/main/res/layout/bottom_sheet_thread_list.xml b/vector/src/main/res/layout/bottom_sheet_thread_list.xml new file mode 100644 index 00000000000..3fd75e18235 --- /dev/null +++ b/vector/src/main/res/layout/bottom_sheet_thread_list.xml @@ -0,0 +1,47 @@ + + + + + + + + + + diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 316b24f4fbd..ea7ce4cf840 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1027,10 +1027,15 @@ INVITED JOINED - + Filter Threads in room Thread Threads + Filter + All Threads + Shows all threads from current room + My Threads + Shows all threads you’ve participated in Reason for reporting this content From c4967a287144547de8bcba6ea77fa8cdf887530d Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Thu, 25 Nov 2021 17:59:28 +0200 Subject: [PATCH 015/581] Handle chunks merging with thread summary Add animation to fragment transition with offset for recyclerview initialization Support threads on deleted events --- .../database/helper/ChunkEntityHelper.kt | 13 +++++ .../database/query/EventEntityQueries.kt | 7 +++ .../room/timeline/TokenChunkEventPersistor.kt | 49 ------------------- .../timeline/factory/MessageItemFactory.kt | 12 ++--- .../detail/timeline/item/AbsMessageItem.kt | 15 +++--- ...readSummaryModel.kt => ThreadListModel.kt} | 5 +- ...yController.kt => ThreadListController.kt} | 10 ++-- ...aryViewModel.kt => ThreadListViewModel.kt} | 14 +++--- ...aryViewState.kt => ThreadListViewState.kt} | 2 +- .../list/views/ThreadListBottomSheet.kt | 25 +++------- .../threads/list/views/ThreadListFragment.kt | 22 ++++----- .../main/res/anim/animation_slide_in_left.xml | 3 +- .../res/anim/animation_slide_in_right.xml | 4 +- .../res/anim/animation_slide_out_left.xml | 4 +- .../res/anim/animation_slide_out_right.xml | 3 +- .../main/res/layout/fragment_thread_list.xml | 2 +- ...hread_summary.xml => item_thread_list.xml} | 0 17 files changed, 80 insertions(+), 110 deletions(-) rename vector/src/main/java/im/vector/app/features/home/room/threads/list/model/{ThreadSummaryModel.kt => ThreadListModel.kt} (95%) rename vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/{ThreadSummaryController.kt => ThreadListController.kt} (92%) rename vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/{ThreadSummaryViewModel.kt => ThreadListViewModel.kt} (81%) rename vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/{ThreadSummaryViewState.kt => ThreadListViewState.kt} (97%) rename vector/src/main/res/layout/{item_thread_summary.xml => item_thread_list.xml} (100%) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt index f74e4b0f4c6..b0d15ce8dac 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt @@ -34,6 +34,7 @@ import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields import org.matrix.android.sdk.internal.database.query.find import org.matrix.android.sdk.internal.database.query.getOrCreate import org.matrix.android.sdk.internal.database.query.where +import org.matrix.android.sdk.internal.database.query.whereRoomId import org.matrix.android.sdk.internal.extensions.assertIsManaged import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection import timber.log.Timber @@ -157,9 +158,21 @@ private fun ChunkEntity.addTimelineEventFromMerge(realm: Realm, timelineEventEnt this.senderName = timelineEventEntity.senderName this.isUniqueDisplayName = timelineEventEntity.isUniqueDisplayName } + handleThreadSummary(realm, eventId, copied) timelineEvents.add(copied) } +/** + * Upon copy of the timeline events we should update the latestMessage TimelineEventEntity with the new one + */ +private fun handleThreadSummary(realm: Realm, oldEventId: String, newTimelineEventEntity: TimelineEventEntity) { + EventEntity + .whereRoomId(realm, newTimelineEventEntity.roomId) + .equalTo(EventEntityFields.IS_ROOT_THREAD, true) + .equalTo(EventEntityFields.THREAD_SUMMARY_LATEST_MESSAGE.EVENT_ID, oldEventId) + .findFirst()?.threadSummaryLatestMessage = newTimelineEventEntity +} + private fun handleReadReceipts(realm: Realm, roomId: String, eventEntity: EventEntity, senderId: String): ReadReceiptsSummaryEntity { val readReceiptsSummaryEntity = ReadReceiptsSummaryEntity.where(realm, eventEntity.eventId).findFirst() ?: realm.createObject(eventEntity.eventId).apply { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt index e27130442dd..a439d6aae72 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt @@ -49,6 +49,13 @@ internal fun EventEntity.Companion.where(realm: Realm, eventId: String): RealmQu .equalTo(EventEntityFields.EVENT_ID, eventId) } + +internal fun EventEntity.Companion.whereRoomId(realm: Realm, roomId: String): RealmQuery { + return realm.where() + .equalTo(EventEntityFields.ROOM_ID, roomId) +} + + internal fun EventEntity.Companion.where(realm: Realm, eventIds: List): RealmQuery { return realm.where() .`in`(EventEntityFields.EVENT_ID, eventIds.toTypedArray()) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt index ba34e88ff7b..f6441c9d607 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt @@ -270,53 +270,4 @@ internal class TokenChunkEventPersistor @Inject constructor(@SessionDatabase pri optimizedThreadSummaryMap.updateThreadSummaryIfNeeded() } - -// /** -// * Mark or update the thread root event accordingly. If the Threading is disabled -// * no action is done -// */ -// private fun updateRootThreadEventIfNeeded(realm: Realm, eventEntity: EventEntity) { -// -// if (!BuildConfig.THREADING_ENABLED) return -// -// val rootThreadEventId = eventEntity.rootThreadEventId -// -// if (eventEntity.isThread && rootThreadEventId != null) { -// markEventAsRootEvent(realm, rootThreadEventId) -// } else { -// markAsRootEventIfNeeded(realm, eventEntity.eventId) -// } -// } - -// /** -// * Finds the event with rootThreadEventId and marks it as a root thread -// */ -// private fun markEventAsRootEvent(realm: Realm, rootThreadEventId: String) { -// val rootThreadEvent = EventEntity -// .where(realm, rootThreadEventId) -// .equalTo(EventEntityFields.IS_THREAD, false).findFirst() ?: return -// rootThreadEvent.isThread = true -// } -// -// /** -// * Also check if there is at least one thread message for that rootThreadEventId, -// * that means it is a root thread so it should be updated accordingly -// */ -// private fun markAsRootEventIfNeeded(realm: Realm, candidateIdRootThread: String) { -// EventEntity -// .whereRootThreadEventId(realm, candidateIdRootThread) -// .findFirst() ?: return -// -// markEventAsRootEvent(realm, candidateIdRootThread) -// } - -// /** -// * Returns the chunk for the current room if exists, otherwise it creates a new ChunkEntity -// */ -// private fun getOrCreateThreadChunk(realm: Realm, roomId: String, rootThreadEventId: String): ChunkEntity { -// return ChunkEntity.findThreadChunkOfRoom(realm, roomId, rootThreadEventId) -// ?: realm.createObject().apply { -// this.rootThreadEventId = rootThreadEventId -// } -// } } 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 5e25b524739..57de9d7233d 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 @@ -125,19 +125,19 @@ class MessageItemFactory @Inject constructor( pillsPostProcessorFactory.create(roomId) } - - - fun create(params: TimelineItemFactoryParams): VectorEpoxyModel<*>? { + val event = params.event val highlight = params.isHighlighted val callback = params.callback event.root.eventId ?: return null roomId = event.roomId val informationData = messageInformationDataFactory.create(params) + val threadDetails = if (params.isFromThreadTimeline()) null else event.root.threadDetails + if (event.root.isRedacted()) { // message is redacted - val attributes = messageItemAttributesFactory.create(null, informationData, callback) + val attributes = messageItemAttributesFactory.create(null, informationData, callback, threadDetails) return buildRedactedItem(attributes, highlight) } @@ -154,7 +154,6 @@ class MessageItemFactory @Inject constructor( } // always hide summary when we are on thread timeline - val threadDetails = if(params.isFromThreadTimeline()) null else event.root.threadDetails val attributes = messageItemAttributesFactory.create(messageContent, informationData, callback, threadDetails) // val all = event.root.toContent() @@ -180,9 +179,10 @@ class MessageItemFactory @Inject constructor( } } - private fun isFromThreadTimeline(params: TimelineItemFactoryParams){ + private fun isFromThreadTimeline(params: TimelineItemFactoryParams) { params.rootThreadEventId } + private fun buildOptionsMessageItem(messageContent: MessageOptionsContent, informationData: MessageInformationData, highlight: Boolean, 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 977a5b426a6..903a6507861 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 @@ -72,6 +72,7 @@ abstract class AbsMessageItem : AbsBaseMessageItem attributes.threadCallback?.onThreadSummaryClicked(attributes.informationData.eventId, attributes.threadDetails?.isRootThread ?: false) } } + override fun bind(holder: H) { super.bind(holder) if (attributes.informationData.showInformation) { @@ -111,26 +112,28 @@ abstract class AbsMessageItem : AbsBaseMessageItem holder.eventSendingIndicator.isVisible = attributes.informationData.sendStateDecoration == SendStateDecoration.SENDING_MEDIA // Threads - if(BuildConfig.THREADING_ENABLED) { + if (BuildConfig.THREADING_ENABLED) { holder.threadSummaryConstraintLayout.onClick(_threadClickListener) attributes.threadDetails?.let { threadDetails -> holder.threadSummaryConstraintLayout.isVisible = threadDetails.isRootThread holder.threadSummaryCounterTextView.text = threadDetails.numberOfThreads.toString() holder.threadSummaryInfoTextView.text = threadDetails.threadSummaryLatestTextMessage - threadDetails.threadSummarySenderInfo?.let { senderInfo -> - attributes.avatarRenderer.render(MatrixItem.UserItem(senderInfo.userId, senderInfo.displayName, senderInfo.avatarUrl), holder.threadSummaryAvatarImageView) - } - } ?: run{holder.threadSummaryConstraintLayout.isVisible = false} + + val userId = threadDetails.threadSummarySenderInfo?.userId ?: return@let + val displayName = threadDetails.threadSummarySenderInfo?.displayName + val avatarUrl = threadDetails.threadSummarySenderInfo?.avatarUrl + attributes.avatarRenderer.render(MatrixItem.UserItem(userId, displayName, avatarUrl), holder.threadSummaryAvatarImageView) + } ?: run { holder.threadSummaryConstraintLayout.isVisible = false } } } - override fun unbind(holder: H) { attributes.avatarRenderer.clear(holder.avatarImageView) holder.avatarImageView.setOnClickListener(null) holder.avatarImageView.setOnLongClickListener(null) holder.memberNameView.setOnClickListener(null) holder.memberNameView.setOnLongClickListener(null) + attributes.avatarRenderer.clear(holder.threadSummaryAvatarImageView) holder.threadSummaryConstraintLayout.setOnClickListener(null) super.unbind(holder) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadSummaryModel.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadListModel.kt similarity index 95% rename from vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadSummaryModel.kt rename to vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadListModel.kt index 8ed19a97c83..887a6acb4bd 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadSummaryModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadListModel.kt @@ -16,7 +16,6 @@ package im.vector.app.features.home.room.threads.list.model -import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView import androidx.constraintlayout.widget.ConstraintLayout @@ -31,8 +30,8 @@ import im.vector.app.features.displayname.getBestName import im.vector.app.features.home.AvatarRenderer import org.matrix.android.sdk.api.util.MatrixItem -@EpoxyModelClass(layout = R.layout.item_thread_summary) -abstract class ThreadSummaryModel : VectorEpoxyModel() { +@EpoxyModelClass(layout = R.layout.item_thread_list) +abstract class ThreadListModel : VectorEpoxyModel() { @EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer @EpoxyAttribute lateinit var matrixItem: MatrixItem diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadSummaryController.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListController.kt similarity index 92% rename from vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadSummaryController.kt rename to vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListController.kt index 2e3b58bb773..f0f9d1e9a26 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadSummaryController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListController.kt @@ -20,21 +20,21 @@ import com.airbnb.epoxy.EpoxyController import im.vector.app.core.date.DateFormatKind import im.vector.app.core.date.VectorDateFormatter import im.vector.app.features.home.AvatarRenderer -import im.vector.app.features.home.room.threads.list.model.threadSummary +import im.vector.app.features.home.room.threads.list.model.threadList import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject -class ThreadSummaryController @Inject constructor( +class ThreadListController @Inject constructor( private val avatarRenderer: AvatarRenderer, private val dateFormatter: VectorDateFormatter ) : EpoxyController() { var listener: Listener? = null - private var viewState: ThreadSummaryViewState? = null + private var viewState: ThreadListViewState? = null - fun update(viewState: ThreadSummaryViewState) { + fun update(viewState: ThreadListViewState) { this.viewState = viewState requestModelBuild() } @@ -46,7 +46,7 @@ class ThreadSummaryController @Inject constructor( safeViewState.rootThreadEventList.invoke() ?.forEach { timelineEvent -> val date = dateFormatter.format(timelineEvent.root.originServerTs, DateFormatKind.ROOM_LIST) - threadSummary { + threadList { id(timelineEvent.eventId) avatarRenderer(host.avatarRenderer) matrixItem(timelineEvent.senderInfo.toMatrixItem()) diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadSummaryViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt similarity index 81% rename from vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadSummaryViewModel.kt rename to vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt index 449090cc739..715478cec31 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadSummaryViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt @@ -31,23 +31,23 @@ import kotlinx.coroutines.flow.map import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.flow.flow -class ThreadSummaryViewModel @AssistedInject constructor(@Assisted val initialState: ThreadSummaryViewState, - private val session: Session) : - VectorViewModel(initialState) { +class ThreadListViewModel @AssistedInject constructor(@Assisted val initialState: ThreadListViewState, + private val session: Session) : + VectorViewModel(initialState) { private val room = session.getRoom(initialState.roomId) @AssistedFactory interface Factory { - fun create(initialState: ThreadSummaryViewState): ThreadSummaryViewModel + fun create(initialState: ThreadListViewState): ThreadListViewModel } - companion object : MavericksViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic - override fun create(viewModelContext: ViewModelContext, state: ThreadSummaryViewState): ThreadSummaryViewModel? { + override fun create(viewModelContext: ViewModelContext, state: ThreadListViewState): ThreadListViewModel? { val fragment: ThreadListFragment = (viewModelContext as FragmentViewModelContext).fragment() - return fragment.threadSummaryViewModelFactory.create(state) + return fragment.threadListViewModelFactory.create(state) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadSummaryViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewState.kt similarity index 97% rename from vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadSummaryViewState.kt rename to vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewState.kt index 13f11897083..01a5239aac4 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadSummaryViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewState.kt @@ -22,7 +22,7 @@ import com.airbnb.mvrx.Uninitialized import im.vector.app.features.home.room.threads.arguments.ThreadListArgs import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent -data class ThreadSummaryViewState( +data class ThreadListViewState( val rootThreadEventList: Async> = Uninitialized, val shouldFilterThreads: Boolean = false, val roomId: String diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListBottomSheet.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListBottomSheet.kt index b3938a10a84..a4f40a820ad 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListBottomSheet.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListBottomSheet.kt @@ -20,14 +20,13 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.appcompat.content.res.AppCompatResources.getDrawable import androidx.core.content.ContextCompat import com.airbnb.mvrx.parentFragmentViewModel import im.vector.app.R import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment import im.vector.app.databinding.BottomSheetThreadListBinding -import im.vector.app.features.home.room.threads.list.viewmodel.ThreadSummaryViewModel -import im.vector.app.features.home.room.threads.list.viewmodel.ThreadSummaryViewState +import im.vector.app.features.home.room.threads.list.viewmodel.ThreadListViewModel +import im.vector.app.features.home.room.threads.list.viewmodel.ThreadListViewState class ThreadListBottomSheet : VectorBaseBottomSheetDialogFragment() { @@ -35,13 +34,12 @@ class ThreadListBottomSheet : VectorBaseBottomSheetDialogFragment(), - ThreadSummaryController.Listener { + ThreadListController.Listener { - private val threadSummaryViewModel: ThreadSummaryViewModel by fragmentViewModel() + private val threadListViewModel: ThreadListViewModel by fragmentViewModel() private val threadListArgs: ThreadListArgs by args() @@ -74,13 +74,13 @@ class ThreadListFragment @Inject constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) initToolbar() - views.threadListRecyclerView.configureWith(threadSummaryController, TimelineItemAnimator(), hasFixedSize = false) - threadSummaryController.listener = this + views.threadListRecyclerView.configureWith(threadListController, TimelineItemAnimator(), hasFixedSize = false) + threadListController.listener = this } override fun onDestroyView() { views.threadListRecyclerView.cleanup() - threadSummaryController.listener = null + threadListController.listener = null super.onDestroyView() } @@ -89,8 +89,8 @@ class ThreadListFragment @Inject constructor( renderToolbar() } - override fun invalidate() = withState(threadSummaryViewModel) { state -> - threadSummaryController.update(state) + override fun invalidate() = withState(threadListViewModel) { state -> + threadListController.update(state) } private fun renderToolbar() { diff --git a/vector/src/main/res/anim/animation_slide_in_left.xml b/vector/src/main/res/anim/animation_slide_in_left.xml index 46547c691d0..77861c99f64 100644 --- a/vector/src/main/res/anim/animation_slide_in_left.xml +++ b/vector/src/main/res/anim/animation_slide_in_left.xml @@ -1,5 +1,6 @@ - \ No newline at end of file diff --git a/vector/src/main/res/anim/animation_slide_in_right.xml b/vector/src/main/res/anim/animation_slide_in_right.xml index d0366bc633f..cf7488cc1aa 100644 --- a/vector/src/main/res/anim/animation_slide_in_right.xml +++ b/vector/src/main/res/anim/animation_slide_in_right.xml @@ -1,5 +1,7 @@ - \ No newline at end of file diff --git a/vector/src/main/res/anim/animation_slide_out_left.xml b/vector/src/main/res/anim/animation_slide_out_left.xml index 3d734533df5..2afa66ceab8 100644 --- a/vector/src/main/res/anim/animation_slide_out_left.xml +++ b/vector/src/main/res/anim/animation_slide_out_left.xml @@ -1,5 +1,7 @@ - \ No newline at end of file diff --git a/vector/src/main/res/anim/animation_slide_out_right.xml b/vector/src/main/res/anim/animation_slide_out_right.xml index 60a3f227217..49348f1dac0 100644 --- a/vector/src/main/res/anim/animation_slide_out_right.xml +++ b/vector/src/main/res/anim/animation_slide_out_right.xml @@ -1,5 +1,6 @@ - \ No newline at end of file diff --git a/vector/src/main/res/layout/fragment_thread_list.xml b/vector/src/main/res/layout/fragment_thread_list.xml index 25dd2007377..be042a7bcec 100644 --- a/vector/src/main/res/layout/fragment_thread_list.xml +++ b/vector/src/main/res/layout/fragment_thread_list.xml @@ -34,7 +34,7 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent" android:background="?android:colorBackground" - tools:listitem="@layout/item_thread_summary" /> + tools:listitem="@layout/item_thread_list" /> \ No newline at end of file diff --git a/vector/src/main/res/layout/item_thread_summary.xml b/vector/src/main/res/layout/item_thread_list.xml similarity index 100% rename from vector/src/main/res/layout/item_thread_summary.xml rename to vector/src/main/res/layout/item_thread_list.xml From 2a83e93265ae867ae850cb8aab75412b58f7360a Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Mon, 29 Nov 2021 15:00:27 +0200 Subject: [PATCH 016/581] Delete root message UI --- .../im/vector/app/core/extensions/TextView.kt | 4 ++++ .../room/threads/list/model/ThreadListModel.kt | 15 ++++++++++++--- .../list/viewmodel/ThreadListController.kt | 1 + 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/extensions/TextView.kt b/vector/src/main/java/im/vector/app/core/extensions/TextView.kt index adb655f169e..c0911aec8b5 100644 --- a/vector/src/main/java/im/vector/app/core/extensions/TextView.kt +++ b/vector/src/main/java/im/vector/app/core/extensions/TextView.kt @@ -125,6 +125,10 @@ fun TextView.setLeftDrawable(@DrawableRes iconRes: Int, @AttrRes tintColor: Int? setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null) } +fun TextView.clearDrawables() { + this.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0) +} + /** * Set long click listener to copy the current text of the TextView to the clipboard and show a Snackbar */ diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadListModel.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadListModel.kt index 887a6acb4bd..f47f6f46cc8 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadListModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadListModel.kt @@ -26,6 +26,9 @@ import im.vector.app.core.epoxy.ClickListener import im.vector.app.core.epoxy.VectorEpoxyHolder import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.epoxy.onClick +import im.vector.app.core.extensions.clearDrawables +import im.vector.app.core.extensions.setLeftDrawable +import im.vector.app.core.utils.DimensionConverter import im.vector.app.features.displayname.getBestName import im.vector.app.features.home.AvatarRenderer import org.matrix.android.sdk.api.util.MatrixItem @@ -40,6 +43,7 @@ abstract class ThreadListModel : VectorEpoxyModel() { @EpoxyAttribute lateinit var rootMessage: String @EpoxyAttribute lateinit var lastMessage: String @EpoxyAttribute lateinit var lastMessageCounter: String + @EpoxyAttribute var rootMessageDeleted: Boolean = false @EpoxyAttribute var lastMessageMatrixItem: MatrixItem? = null @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var itemClickListener: ClickListener? = null @@ -50,8 +54,14 @@ abstract class ThreadListModel : VectorEpoxyModel() { holder.avatarImageView.contentDescription = matrixItem.getBestName() holder.titleTextView.text = title holder.dateTextView.text = date - holder.rootMessageTextView.text = rootMessage - + if (rootMessageDeleted){ + holder.rootMessageTextView.text = holder.view.context.getString(R.string.event_redacted) + holder.rootMessageTextView.setLeftDrawable(R.drawable.ic_trash_16, R.attr.colorOnPrimary) + holder.rootMessageTextView.compoundDrawablePadding = DimensionConverter(holder.view.context.resources).dpToPx(10) + }else{ + holder.rootMessageTextView.text = rootMessage + holder.rootMessageTextView.clearDrawables() + } // Last message summary lastMessageMatrixItem?.let { avatarRenderer.render(it, holder.lastMessageAvatarImageView) @@ -59,7 +69,6 @@ abstract class ThreadListModel : VectorEpoxyModel() { holder.lastMessageAvatarImageView.contentDescription = lastMessageMatrixItem?.getBestName() holder.lastMessageTextView.text = lastMessage holder.lastMessageCounterTextView.text = lastMessageCounter - } class Holder : VectorEpoxyHolder() { diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListController.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListController.kt index f0f9d1e9a26..d17dee6e514 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListController.kt @@ -52,6 +52,7 @@ class ThreadListController @Inject constructor( matrixItem(timelineEvent.senderInfo.toMatrixItem()) title(timelineEvent.senderInfo.displayName) date(date) + rootMessageDeleted(timelineEvent.root.isRedacted()) rootMessage(timelineEvent.root.getDecryptedTextSummary()) lastMessage(timelineEvent.root.threadDetails?.threadSummaryLatestTextMessage.orEmpty()) lastMessageCounter(timelineEvent.root.threadDetails?.numberOfThreads.toString()) From 53ca86dc6c5c1581adf557062db2dd4e39faacfb Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Mon, 29 Nov 2021 18:08:16 +0000 Subject: [PATCH 017/581] Permalink handling for thread events --- .../vector/app/features/home/HomeActivity.kt | 2 +- .../home/room/detail/TimelineFragment.kt | 45 ++++++++++++++----- .../home/room/threads/ThreadsActivity.kt | 11 ++++- .../features/navigation/DefaultNavigator.kt | 18 +++++--- .../app/features/navigation/Navigator.kt | 2 +- .../features/permalink/PermalinkHandler.kt | 35 ++++++++++++--- .../roomdirectory/PublicRoomsFragment.kt | 2 +- vector/src/main/res/values/strings.xml | 1 + 8 files changed, 89 insertions(+), 27 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt index ff1154acc3d..9b41cf52d88 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt @@ -556,7 +556,7 @@ class HomeActivity : return true } - override fun navToRoom(roomId: String?, eventId: String?, deepLink: Uri?): Boolean { + override fun navToRoom(roomId: String?, eventId: String?, deepLink: Uri?, rootThreadEventId: String?): Boolean { if (roomId == null) return false MatrixToBottomSheet.withLink(deepLink.toString()) .show(supportFragmentManager, "HA#MatrixToBottomSheet") 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 1c37b26c60d..7eeb6ae6654 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 @@ -984,7 +984,12 @@ class TimelineFragment @Inject constructor( true } R.id.menu_thread_timeline_copy_link -> { - requireActivity().toast("menu_thread_timeline_copy_link") + getRootThreadEventId()?.let { + val permalink = session.permalinkService().createPermalink(timelineArgs.roomId, it) + copyToClipboard(requireContext(), permalink, false) + showSnackWithMessage(getString(R.string.copied_to_clipboard)) + + } true } R.id.menu_thread_timeline_view_in_room -> { @@ -992,7 +997,10 @@ class TimelineFragment @Inject constructor( true } R.id.menu_thread_timeline_share -> { - requireActivity().toast("menu_thread_timeline_share") + getRootThreadEventId()?.let { + val permalink = session.permalinkService().createPermalink(timelineArgs.roomId, it) + shareText(requireContext(), permalink) + } true } else -> super.onOptionsItemSelected(item) @@ -1649,20 +1657,36 @@ class TimelineFragment @Inject constructor( viewLifecycleOwner.lifecycleScope.launch { val isManaged = permalinkHandler .launch(requireActivity(), url, object : NavigationInterceptor { - override fun navToRoom(roomId: String?, eventId: String?, deepLink: Uri?): Boolean { + override fun navToRoom(roomId: String?, eventId: String?, deepLink: Uri?, rootThreadEventId: String?): Boolean { // Same room? - if (roomId == timelineArgs.roomId) { - // Navigation to same room - if (eventId == null) { + if (roomId != timelineArgs.roomId) return false + // Navigation to same room + if (!isThreadTimeLine()) { + + if (rootThreadEventId != null) { + // Thread link, so PermalinkHandler will handle the navigation + return false + } + return if (eventId == null) { showSnackWithMessage(getString(R.string.navigate_to_room_when_already_in_the_room)) + true } else { // Highlight and scroll to this event roomDetailViewModel.handle(RoomDetailAction.NavigateToEvent(eventId, true)) + true + } + } else { + return if (rootThreadEventId == getRootThreadEventId() && eventId == null) { + showSnackWithMessage(getString(R.string.navigate_to_thread_when_already_in_the_thread)) + true + } else if (rootThreadEventId == getRootThreadEventId() && eventId != null) { + // we are in the same thread + roomDetailViewModel.handle(RoomDetailAction.NavigateToEvent(eventId, true)) + true + } else { + false } - return true } - // Not handled - return false } override fun navToMemberProfile(userId: String, deepLink: Uri): Boolean { @@ -1816,7 +1840,6 @@ class TimelineFragment @Inject constructor( } } - override fun onAvatarClicked(informationData: MessageInformationData) { // roomDetailViewModel.handle(RoomDetailAction.RequestVerification(informationData.userId)) openRoomMemberProfile(informationData.senderId) @@ -1862,7 +1885,7 @@ class TimelineFragment @Inject constructor( viewLifecycleOwner.lifecycleScope.launchWhenResumed { permalinkHandler .launch(requireContext(), url, object : NavigationInterceptor { - override fun navToRoom(roomId: String?, eventId: String?, deepLink: Uri?): Boolean { + override fun navToRoom(roomId: String?, eventId: String?, deepLink: Uri?, rootThreadEventId: String?): Boolean { requireActivity().finish() return false } diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt index db052a42d33..fb1a6006c45 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt @@ -94,6 +94,7 @@ class ThreadsActivity : VectorBaseActivity(), ToolbarCon TimelineFragment::class.java, TimelineArgs( roomId = threadTimelineArgs.roomId, + eventId = getEventIdToNavigate(), threadTimelineArgs = threadTimelineArgs )) @@ -141,15 +142,23 @@ class ThreadsActivity : VectorBaseActivity(), ToolbarCon private fun getThreadTimelineArgs(): ThreadTimelineArgs? = intent?.extras?.getParcelable(THREAD_TIMELINE_ARGS) private fun getThreadListArgs(): ThreadListArgs? = intent?.extras?.getParcelable(THREAD_LIST_ARGS) + private fun getEventIdToNavigate(): String? = intent?.extras?.getString(THREAD_EVENT_ID_TO_NAVIGATE) companion object { // private val FRAGMENT_TAG = RoomThreadDetailFragment::class.simpleName const val THREAD_TIMELINE_ARGS = "THREAD_TIMELINE_ARGS" + const val THREAD_EVENT_ID_TO_NAVIGATE = "THREAD_EVENT_ID_TO_NAVIGATE" const val THREAD_LIST_ARGS = "THREAD_LIST_ARGS" - fun newIntent(context: Context, threadTimelineArgs: ThreadTimelineArgs?, threadListArgs: ThreadListArgs?): Intent { + fun newIntent( + context: Context, + threadTimelineArgs: ThreadTimelineArgs?, + threadListArgs: ThreadListArgs?, + eventIdToNavigate: String? = null + ): Intent { return Intent(context, ThreadsActivity::class.java).apply { putExtra(THREAD_TIMELINE_ARGS, threadTimelineArgs) + putExtra(THREAD_EVENT_ID_TO_NAVIGATE, eventIdToNavigate) putExtra(THREAD_LIST_ARGS, threadListArgs) } diff --git a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt index 5f4a2168e95..3782a53d126 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt @@ -116,7 +116,12 @@ class DefaultNavigator @Inject constructor( context.startActivity(intent) } - override fun openRoom(context: Context, roomId: String, eventId: String?, buildTask: Boolean) { + override fun openRoom( + context: Context, + roomId: String, + eventId: String?, + buildTask: Boolean + ) { if (sessionHolder.getSafeActiveSession()?.getRoom(roomId) == null) { fatalError("Trying to open an unknown room $roomId", vectorPreferences.failFast()) return @@ -511,16 +516,19 @@ class DefaultNavigator @Inject constructor( } } - override fun openThread(context: Context, threadTimelineArgs: ThreadTimelineArgs) { + override fun openThread(context: Context, threadTimelineArgs: ThreadTimelineArgs, eventIdToNavigate: String?) { context.startActivity(ThreadsActivity.newIntent( context = context, - threadTimelineArgs = threadTimelineArgs, - threadListArgs =null)) + threadTimelineArgs = threadTimelineArgs, + threadListArgs = null, + eventIdToNavigate = eventIdToNavigate + )) } + override fun openThreadList(context: Context, threadTimelineArgs: ThreadTimelineArgs) { context.startActivity(ThreadsActivity.newIntent( context = context, - threadTimelineArgs = null, + threadTimelineArgs = null, threadListArgs = ThreadListArgs( roomId = threadTimelineArgs.roomId, displayName = threadTimelineArgs.displayName, diff --git a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt index 37783d022be..d3a1a9eb037 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt @@ -142,7 +142,7 @@ interface Navigator { fun openCallTransfer(context: Context, callId: String) - fun openThread(context: Context, threadTimelineArgs: ThreadTimelineArgs) + fun openThread(context: Context, threadTimelineArgs: ThreadTimelineArgs, eventIdToNavigate: String? = null) fun openThreadList(context: Context, threadTimelineArgs: ThreadTimelineArgs) diff --git a/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt b/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt index a02cfe75175..614a95af3bb 100644 --- a/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt +++ b/vector/src/main/java/im/vector/app/features/permalink/PermalinkHandler.kt @@ -21,12 +21,14 @@ import android.net.Uri import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.utils.toast +import im.vector.app.features.home.room.threads.arguments.ThreadTimelineArgs import im.vector.app.features.navigation.Navigator import im.vector.app.features.roomdirectory.roompreview.RoomPreviewData import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.tryOrNull +import org.matrix.android.sdk.api.session.events.model.getRootThreadEventId import org.matrix.android.sdk.api.session.permalinks.PermalinkData import org.matrix.android.sdk.api.session.permalinks.PermalinkParser import org.matrix.android.sdk.api.session.permalinks.PermalinkService @@ -74,13 +76,27 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti return when (permalinkData) { is PermalinkData.RoomLink -> { val roomId = permalinkData.getRoomId() - if (navigationInterceptor?.navToRoom(roomId, permalinkData.eventId, rawLink) != true) { + val session = activeSessionHolder.getSafeActiveSession() + + val rootThreadEventId = permalinkData.eventId?.let { eventId -> + val room = roomId?.let { session?.getRoom(it) } + // Root thread will be opened in timeline +// if(room?.getTimeLineEvent(eventId)?.root?.threadDetails?.isRootThread == true){ +// room.getTimeLineEvent(eventId)?.root?.eventId +// }else{ + room?.getTimeLineEvent(eventId)?.root?.getRootThreadEventId() +// } + + } + + if (navigationInterceptor?.navToRoom(roomId, permalinkData.eventId, rawLink, rootThreadEventId) != true) { openRoom( context = context, roomId = roomId, permalinkData = permalinkData, rawLink = rawLink, - buildTask = buildTask + buildTask = buildTask, + rootThreadEventId = rootThreadEventId ) } true @@ -115,8 +131,8 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti private fun isPermalinkSupported(context: Context, url: String): Boolean { return url.startsWith(PermalinkService.MATRIX_TO_URL_BASE) || context.resources.getStringArray(R.array.permalink_supported_hosts).any { - url.startsWith(it) - } + url.startsWith(it) + } } private suspend fun PermalinkData.RoomLink.getRoomId(): String? { @@ -145,7 +161,8 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti roomId: String?, permalinkData: PermalinkData.RoomLink, rawLink: Uri, - buildTask: Boolean + buildTask: Boolean, + rootThreadEventId: String? =null ) { val session = activeSessionHolder.getSafeActiveSession() ?: return if (roomId == null) { @@ -155,6 +172,7 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti val roomSummary = session.getRoomSummary(roomId) val membership = roomSummary?.membership val eventId = permalinkData.eventId + // val roomAlias = permalinkData.getRoomAliasOrNull() val isSpace = roomSummary?.roomType == RoomType.SPACE return when { @@ -162,7 +180,10 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti membership?.isActive().orFalse() -> { if (!isSpace && membership == Membership.JOIN) { // If it's a room you're in, let's just open it, you can tap back if needed - navigator.openRoom(context, roomId, eventId, buildTask) + rootThreadEventId?.let { + val threadTimelineArgs = ThreadTimelineArgs(roomId, displayName = roomSummary.displayName, roomSummary.avatarUrl, it) + navigator.openThread(context, threadTimelineArgs, eventId) + } ?: navigator.openRoom(context, roomId, eventId, buildTask) } else { // maybe open space preview navigator.openSpacePreview(context, roomId)? if already joined? navigator.openMatrixToBottomSheet(context, rawLink.toString()) @@ -187,7 +208,7 @@ interface NavigationInterceptor { /** * Return true if the navigation has been intercepted */ - fun navToRoom(roomId: String?, eventId: String?, deepLink: Uri? = null): Boolean { + fun navToRoom(roomId: String?, eventId: String?, deepLink: Uri? = null, rootThreadEventId: String? = null): Boolean { return false } diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsFragment.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsFragment.kt index b61583df55a..fcc1d5fbf97 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/PublicRoomsFragment.kt @@ -130,7 +130,7 @@ class PublicRoomsFragment @Inject constructor( val permalink = session.permalinkService().createPermalink(roomIdOrAlias) val isHandled = permalinkHandler .launch(requireContext(), permalink, object : NavigationInterceptor { - override fun navToRoom(roomId: String?, eventId: String?, deepLink: Uri?): Boolean { + override fun navToRoom(roomId: String?, eventId: String?, deepLink: Uri?, rootThreadEventId: String?): Boolean { requireActivity().finish() return false } diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index ea7ce4cf840..e9052ace75c 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2259,6 +2259,7 @@ Matrix SDK Version Other third party notices You are already viewing this room! + You are already viewing this thread! Quick Reactions From e7b8b90b0a4fec24df7f1b406f9b63e41ca338b4 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Tue, 30 Nov 2021 16:05:45 +0000 Subject: [PATCH 018/581] Highlight the whole message along with the thread summary --- .../detail/timeline/item/AbsMessageItem.kt | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) 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 903a6507861..a9455a0e3c6 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 @@ -18,14 +18,16 @@ package im.vector.app.features.home.room.detail.timeline.item import android.graphics.Typeface import android.view.View -import android.view.ViewStub import android.widget.ImageView +import android.widget.LinearLayout import android.widget.ProgressBar +import android.widget.RelativeLayout import android.widget.TextView import androidx.annotation.IdRes import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.view.isInvisible import androidx.core.view.isVisible +import androidx.core.view.updateLayoutParams import com.airbnb.epoxy.EpoxyAttribute import im.vector.app.BuildConfig import im.vector.app.R @@ -37,7 +39,6 @@ import im.vector.app.features.home.room.detail.timeline.MessageColorProvider import im.vector.app.features.home.room.detail.timeline.TimelineEventController import org.matrix.android.sdk.api.session.threads.ThreadDetails import org.matrix.android.sdk.api.util.MatrixItem -import timber.log.Timber /** * Base timeline item that adds an optional information bar with the sender avatar, name, time, send state @@ -123,7 +124,21 @@ abstract class AbsMessageItem : AbsBaseMessageItem val displayName = threadDetails.threadSummarySenderInfo?.displayName val avatarUrl = threadDetails.threadSummarySenderInfo?.avatarUrl attributes.avatarRenderer.render(MatrixItem.UserItem(userId, displayName, avatarUrl), holder.threadSummaryAvatarImageView) - } ?: run { holder.threadSummaryConstraintLayout.isVisible = false } + updateHighlightedMessageHeight(holder,true) + } ?: run { + holder.threadSummaryConstraintLayout.isVisible = false + updateHighlightedMessageHeight(holder,false) + } + } + } + + private fun updateHighlightedMessageHeight(holder: Holder, isExpanded: Boolean) { + holder.checkableBackground.updateLayoutParams { + if (isExpanded) { + addRule(RelativeLayout.ALIGN_BOTTOM, holder.threadSummaryConstraintLayout.id) + } else { + addRule(RelativeLayout.ALIGN_BOTTOM, holder.informationBottom.id) + } } } @@ -141,14 +156,15 @@ abstract class AbsMessageItem : AbsBaseMessageItem private fun Attributes.getMemberNameColor() = messageColorProvider.getMemberNameTextColor(informationData.matrixItem) abstract class Holder(@IdRes stubId: Int) : AbsBaseMessageItem.Holder(stubId) { + val avatarImageView by bind(R.id.messageAvatarImageView) val memberNameView by bind(R.id.messageMemberNameView) val timeView by bind(R.id.messageTimeView) val sendStateImageView by bind(R.id.messageSendStateImageView) val eventSendingIndicator by bind(R.id.eventSendingIndicator) + val informationBottom by bind(R.id.informationBottom) val threadSummaryConstraintLayout by bind(R.id.messageThreadSummaryConstraintLayout) val threadSummaryCounterTextView by bind(R.id.messageThreadSummaryCounterTextView) - val threadSummaryImageView by bind(R.id.messageThreadSummaryImageView) val threadSummaryAvatarImageView by bind(R.id.messageThreadSummaryAvatarImageView) val threadSummaryInfoTextView by bind(R.id.messageThreadSummaryInfoTextView) } From 0241d66f8e98b37f3613e85c9fec9cb2d6f9d19b Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Wed, 1 Dec 2021 12:57:53 +0000 Subject: [PATCH 019/581] Enhance search functionality to support threads --- .../home/room/detail/TimelineFragment.kt | 7 +++++- .../home/room/detail/search/SearchFragment.kt | 24 +++++++++++++++---- .../detail/search/SearchResultController.kt | 4 ---- .../features/navigation/DefaultNavigator.kt | 9 ++++--- .../app/features/navigation/Navigator.kt | 3 +-- 5 files changed, 32 insertions(+), 15 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 7eeb6ae6654..a7db0af385d 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 @@ -1024,7 +1024,12 @@ class TimelineFragment @Inject constructor( private fun handleSearchAction() { if (session.getRoom(timelineArgs.roomId)?.isEncrypted() == false) { - navigator.openSearch(requireContext(), timelineArgs.roomId) + navigator.openSearch( + context = requireContext(), + roomId = timelineArgs.roomId, + roomDisplayName = roomDetailViewModel.getRoomSummary()?.displayName, + roomAvatarUrl = roomDetailViewModel.getRoomSummary()?.avatarUrl + ) } else { showDialogWithMessage(getString(R.string.search_is_not_supported_in_e2e_room)) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchFragment.kt index 9f34cdd6795..4b189095e64 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchFragment.kt @@ -37,13 +37,17 @@ import im.vector.app.core.extensions.trackItemsVisibilityChange import im.vector.app.core.platform.StateView import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentSearchBinding +import im.vector.app.features.home.room.threads.arguments.ThreadTimelineArgs import kotlinx.parcelize.Parcelize import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.getRootThreadEventId import javax.inject.Inject @Parcelize data class SearchArgs( - val roomId: String + val roomId: String, + val roomDisplayName: String?, + val roomAvatarUrl: String? ) : Parcelable class SearchFragment @Inject constructor( @@ -112,10 +116,20 @@ class SearchFragment @Inject constructor( searchViewModel.handle(SearchAction.Retry) } - override fun onItemClicked(event: Event) { - event.roomId?.let { - navigator.openRoom(requireContext(), it, event.eventId) - } + override fun onItemClicked(event: Event) = + navigateToEvent(event) + + /** + * Navigate and highlight the event. If this is a thread event, + * user will be redirected to the appropriate thread room + * @param event the event to navigate and highlight + */ + private fun navigateToEvent(event: Event) { + val roomId = event.roomId ?: return + event.getRootThreadEventId()?.let { + val threadTimelineArgs = ThreadTimelineArgs(roomId, displayName = fragmentArgs.roomDisplayName, fragmentArgs.roomAvatarUrl, it) + navigator.openThread(requireContext(), threadTimelineArgs, event.eventId) + } ?: navigator.openRoom(requireContext(), roomId, event.eventId) } override fun loadMore() { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultController.kt index edef92ee2d9..1b4d9faaece 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultController.kt @@ -54,10 +54,6 @@ class SearchResultController @Inject constructor( fun loadMore() } - init { - setData(null) - } - override fun buildModels(data: SearchViewState?) { data ?: return diff --git a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt index 3782a53d126..0a062aae72d 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt @@ -492,8 +492,11 @@ class DefaultNavigator @Inject constructor( } } - override fun openSearch(context: Context, roomId: String) { - val intent = SearchActivity.newIntent(context, SearchArgs(roomId)) + override fun openSearch(context: Context, + roomId: String, + roomDisplayName: String?, + roomAvatarUrl: String?) { + val intent = SearchActivity.newIntent(context, SearchArgs(roomId, roomDisplayName, roomAvatarUrl)) context.startActivity(intent) } @@ -522,7 +525,7 @@ class DefaultNavigator @Inject constructor( threadTimelineArgs = threadTimelineArgs, threadListArgs = null, eventIdToNavigate = eventIdToNavigate - )) + )) } override fun openThreadList(context: Context, threadTimelineArgs: ThreadTimelineArgs) { diff --git a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt index d3a1a9eb037..a2edb27bb99 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt @@ -136,7 +136,7 @@ interface Navigator { inMemory: List = emptyList(), options: ((MutableList>) -> Unit)?) - fun openSearch(context: Context, roomId: String) + fun openSearch(context: Context, roomId: String, roomDisplayName: String?, roomAvatarUrl: String?) fun openDevTools(context: Context, roomId: String) @@ -145,5 +145,4 @@ interface Navigator { fun openThread(context: Context, threadTimelineArgs: ThreadTimelineArgs, eventIdToNavigate: String? = null) fun openThreadList(context: Context, threadTimelineArgs: ThreadTimelineArgs) - } From d1bb96cec047b9d546b6878d1dc8343007085693 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Fri, 3 Dec 2021 11:30:30 +0000 Subject: [PATCH 020/581] Threads notification badge UI --- .../src/main/res/values-v23/dimens.xml | 4 ++ .../ui-styles/src/main/res/values/dimens.xml | 4 ++ .../home/room/detail/RoomDetailViewModel.kt | 20 +++---- .../home/room/detail/TimelineFragment.kt | 41 ++++++++++++- .../main/res/drawable/notification_badge.xml | 12 ++++ .../layout/view_thread_notification_badge.xml | 58 +++++++++++++++++++ .../view_thread_notification_badge_old.xml | 53 +++++++++++++++++ vector/src/main/res/menu/menu_timeline.xml | 5 +- 8 files changed, 183 insertions(+), 14 deletions(-) create mode 100644 library/ui-styles/src/main/res/values-v23/dimens.xml create mode 100644 vector/src/main/res/drawable/notification_badge.xml create mode 100644 vector/src/main/res/layout/view_thread_notification_badge.xml create mode 100644 vector/src/main/res/layout/view_thread_notification_badge_old.xml diff --git a/library/ui-styles/src/main/res/values-v23/dimens.xml b/library/ui-styles/src/main/res/values-v23/dimens.xml new file mode 100644 index 00000000000..18b8a81a7e8 --- /dev/null +++ b/library/ui-styles/src/main/res/values-v23/dimens.xml @@ -0,0 +1,4 @@ + + + 28dp + \ No newline at end of file diff --git a/library/ui-styles/src/main/res/values/dimens.xml b/library/ui-styles/src/main/res/values/dimens.xml index e2e50449ce9..88c3d9d6e4e 100644 --- a/library/ui-styles/src/main/res/values/dimens.xml +++ b/library/ui-styles/src/main/res/values/dimens.xml @@ -41,4 +41,8 @@ 320dp + 24dp + 48dp + 48dp + \ No newline at end of file diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index 1f5550b27f5..907ca360bbd 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -680,17 +680,17 @@ class RoomDetailViewModel @AssistedInject constructor( } } else { when (itemId) { - R.id.timeline_setting -> true - R.id.invite -> state.canInvite - R.id.open_matrix_apps -> true - R.id.voice_call -> state.isWebRTCCallOptionAvailable() - R.id.video_call -> state.isWebRTCCallOptionAvailable() || state.jitsiState.confId == null || state.jitsiState.hasJoined + R.id.timeline_setting -> true + R.id.invite -> state.canInvite + R.id.open_matrix_apps -> true + R.id.voice_call -> state.isWebRTCCallOptionAvailable() + R.id.video_call -> state.isWebRTCCallOptionAvailable() || state.jitsiState.confId == null || state.jitsiState.hasJoined // Show Join conference button only if there is an active conf id not joined. Otherwise fallback to default video disabled. ^ - R.id.join_conference -> !state.isWebRTCCallOptionAvailable() && state.jitsiState.confId != null && !state.jitsiState.hasJoined - R.id.search -> true - R.id.threads -> BuildConfig.THREADING_ENABLED - R.id.dev_tools -> vectorPreferences.developerMode() - else -> false + R.id.join_conference -> !state.isWebRTCCallOptionAvailable() && state.jitsiState.confId != null && !state.jitsiState.hasJoined + R.id.search -> true + R.id.menu_timeline_thread_list -> BuildConfig.THREADING_ENABLED + R.id.dev_tools -> vectorPreferences.developerMode() + else -> false } } } 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 a7db0af385d..b37ba12f37a 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 @@ -36,12 +36,14 @@ 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.core.content.ContextCompat +import androidx.core.graphics.drawable.DrawableCompat import androidx.core.net.toUri import androidx.core.text.buildSpannedString import androidx.core.text.toSpannable @@ -61,6 +63,7 @@ import com.airbnb.epoxy.OnModelBuildFinishedListener import com.airbnb.epoxy.addGlidePreloader import com.airbnb.epoxy.glidePreloader import com.airbnb.mvrx.Mavericks +import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState @@ -133,6 +136,7 @@ import im.vector.app.features.command.Command 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.UnreadMessagesSharedViewModel import im.vector.app.features.home.room.detail.arguments.TimelineArgs import im.vector.app.features.home.room.detail.composer.SendMode import im.vector.app.features.home.room.detail.composer.TextComposerAction @@ -285,6 +289,7 @@ class TimelineFragment @Inject constructor( private val roomDetailViewModel: RoomDetailViewModel by fragmentViewModel() private val textComposerViewModel: TextComposerViewModel by fragmentViewModel() + private val debouncer = Debouncer(createUIHandler()) private lateinit var scrollOnNewMessageCallback: ScrollOnNewMessageCallback @@ -907,6 +912,13 @@ class TimelineFragment @Inject constructor( (joinConfItem.actionView as? JoinConferenceView)?.onJoinClicked = { roomDetailViewModel.handle(RoomDetailAction.JoinJitsiCall) } + + // Custom thread notification menu item + menu.findItem(R.id.menu_timeline_thread_list)?.let { menuItem -> + menuItem.actionView.setOnClickListener { + onOptionsItemSelected(menuItem) + } + } } override fun onPrepareOptionsMenu(menu: Menu) { @@ -946,6 +958,10 @@ class TimelineFragment @Inject constructor( actionView.findViewById(R.id.cart_badge).setTextOrHide("$widgetsCount") matrixAppsMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS) } + + // Handle custom threads badge notification + updateMenuThreadNotificationBadge(menu, state) + } } @@ -971,7 +987,7 @@ class TimelineFragment @Inject constructor( callActionsHandler.onVideoCallClicked() true } - R.id.threads -> { + R.id.menu_timeline_thread_list -> { navigateToThreadList() true } @@ -1007,6 +1023,29 @@ class TimelineFragment @Inject constructor( } } + /** + * Update menu thread notification badge appropriately + */ + private fun updateMenuThreadNotificationBadge(menu: Menu, state: RoomDetailViewState) { + val menuThreadList = menu.findItem(R.id.menu_timeline_thread_list).actionView + val badgeFrameLayout = menuThreadList.findViewById(R.id.threadNotificationBadgeFrameLayout) + val badgeTextView = menuThreadList.findViewById(R.id.threadNotificationBadgeTextView) + + val unreadThreadMessages = 18 + state.pushCounter + + val userIsMentioned = true + if (unreadThreadMessages > 0) { + badgeFrameLayout.isVisible = true + badgeTextView.text = unreadThreadMessages.toString() + val badgeDrawable = DrawableCompat.wrap(badgeFrameLayout.background) + val color = ContextCompat.getColor(requireContext(), if (userIsMentioned) R.color.palette_vermilion else R.color.palette_gray_200) + DrawableCompat.setTint(badgeDrawable, color) + badgeFrameLayout.background = badgeDrawable + } else { + badgeFrameLayout.isVisible = false + } + } + /** * View and highlight the original root thread message in the main timeline */ diff --git a/vector/src/main/res/drawable/notification_badge.xml b/vector/src/main/res/drawable/notification_badge.xml new file mode 100644 index 00000000000..11f4b1d2741 --- /dev/null +++ b/vector/src/main/res/drawable/notification_badge.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/view_thread_notification_badge.xml b/vector/src/main/res/layout/view_thread_notification_badge.xml new file mode 100644 index 00000000000..8e2e098d7b2 --- /dev/null +++ b/vector/src/main/res/layout/view_thread_notification_badge.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/view_thread_notification_badge_old.xml b/vector/src/main/res/layout/view_thread_notification_badge_old.xml new file mode 100644 index 00000000000..70efceda516 --- /dev/null +++ b/vector/src/main/res/layout/view_thread_notification_badge_old.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + diff --git a/vector/src/main/res/menu/menu_timeline.xml b/vector/src/main/res/menu/menu_timeline.xml index 532b63dd380..6b334ab56a4 100644 --- a/vector/src/main/res/menu/menu_timeline.xml +++ b/vector/src/main/res/menu/menu_timeline.xml @@ -38,14 +38,13 @@ tools:visible="true" /> - Date: Fri, 3 Dec 2021 18:15:25 +0000 Subject: [PATCH 021/581] Implement LOCAL thread notifications that work only on real time. --- .../org/matrix/android/sdk/flow/FlowRoom.kt | 7 +++++ .../session/room/timeline/TimelineService.kt | 17 +++++++++++ .../sdk/api/session/threads/ThreadDetails.kt | 3 +- .../database/RealmSessionStoreMigration.kt | 1 + .../database/helper/ThreadEventsHelper.kt | 27 +++++++++++++++-- .../internal/database/mapper/EventMapper.kt | 2 ++ .../internal/database/model/EventEntity.kt | 1 + .../room/timeline/DefaultTimelineService.kt | 25 ++++++++++++++++ .../room/timeline/TokenChunkEventPersistor.kt | 4 ++- .../sync/handler/room/RoomSyncHandler.kt | 2 +- .../home/room/detail/RoomDetailViewModel.kt | 26 +++++++++++++++++ .../home/room/detail/RoomDetailViewState.kt | 5 ++-- .../home/room/detail/TimelineFragment.kt | 4 +-- .../threads/list/model/ThreadListModel.kt | 5 ++++ .../list/viewmodel/ThreadListController.kt | 1 + .../src/main/res/layout/item_thread_list.xml | 29 ++++++++++++++----- 16 files changed, 141 insertions(+), 18 deletions(-) diff --git a/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowRoom.kt b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowRoom.kt index 70919059913..cdb3bdf9c2d 100644 --- a/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowRoom.kt +++ b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowRoom.kt @@ -106,6 +106,13 @@ class FlowRoom(private val room: Room) { room.getAllThreads() } } + + fun liveLocalUnreadThreadList(): Flow> { + return room.getNumberOfLocalThreadNotificationsLive().asFlow() + .startWith(room.coroutineDispatchers.io) { + room.getNumberOfLocalThreadNotifications() + } + } } fun Room.flow(): FlowRoom { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineService.kt index 6b1ad5554b8..068fa87a667 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineService.kt @@ -68,11 +68,28 @@ interface TimelineService { */ fun getAllThreads(): List + /** + * Get a live list of all the local unread threads for the specified roomId + * @return the [LiveData] of [TimelineEvent] + */ + fun getNumberOfLocalThreadNotificationsLive(): LiveData> + + /** + * Get a list of all the local unread threads for the specified roomId + * @return the [LiveData] of [TimelineEvent] + */ + fun getNumberOfLocalThreadNotifications(): List + /** * Returns whether or not the current user is participating in the thread * @param rootThreadEventId the eventId of the current thread */ fun isUserParticipatingInThread(rootThreadEventId: String, senderId: String): Boolean + /** + * Marks the current thread as read. This is a local implementation + * @param rootThreadEventId the eventId of the current thread + */ + suspend fun markThreadAsRead(rootThreadEventId: String) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadDetails.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadDetails.kt index 04dbb18797e..62568cdce14 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadDetails.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadDetails.kt @@ -22,5 +22,6 @@ data class ThreadDetails( val isRootThread: Boolean = false, val numberOfThreads: Int = 0, val threadSummarySenderInfo: SenderInfo? = null, - val threadSummaryLatestTextMessage: String? = null + val threadSummaryLatestTextMessage: String? = null, + val hasUnreadMessage: Boolean = false ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt index 111fc50e564..301a479d01b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt @@ -375,6 +375,7 @@ internal object RealmSessionStoreMigration : RealmMigration { ?.addField(EventEntityFields.IS_ROOT_THREAD, Boolean::class.java, FieldAttribute.INDEXED) ?.addField(EventEntityFields.ROOT_THREAD_EVENT_ID, String::class.java, FieldAttribute.INDEXED) ?.addField(EventEntityFields.NUMBER_OF_THREADS, Int::class.java) + ?.addField(EventEntityFields.HAS_UNREAD_THREAD_MESSAGES, Boolean::class.java) ?.addRealmObjectField(EventEntityFields.THREAD_SUMMARY_LATEST_MESSAGE.`$`, eventEntity) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt index aa3ba0fc253..34bc117ddf1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt @@ -31,7 +31,7 @@ import org.matrix.android.sdk.internal.database.query.whereRoomId * Finds the root thread event and update it with the latest message summary along with the number * of threads included. If there is no root thread event no action is done */ -internal fun Map.updateThreadSummaryIfNeeded() { +internal fun Map.updateThreadSummaryIfNeeded(isInitialSync: Boolean = false, currentUserId: String? = null) { if (!BuildConfig.THREADING_ENABLED) return @@ -47,6 +47,8 @@ internal fun Map.updateThreadSummaryIfNeeded() { val rootThreadEvent = if (eventEntity.isThread()) eventEntity.findRootThreadEvent() else eventEntity rootThreadEvent?.markEventAsRoot( + isInitialSync = isInitialSync, + currentUserId = currentUserId, threadsCounted = it.size, latestMessageTimelineEventEntity = latestMessage ) @@ -68,11 +70,20 @@ internal fun EventEntity.findRootThreadEvent(): EventEntity? = /** * Mark or update the current event a root thread event */ -internal fun EventEntity.markEventAsRoot(threadsCounted: Int, - latestMessageTimelineEventEntity: TimelineEventEntity?) { +internal fun EventEntity.markEventAsRoot( + isInitialSync: Boolean, + currentUserId: String?, + threadsCounted: Int, + latestMessageTimelineEventEntity: TimelineEventEntity?) { isRootThread = true numberOfThreads = threadsCounted threadSummaryLatestMessage = latestMessageTimelineEventEntity + // skip notification coming from messages from the same user, also retain already marked events + hasUnreadThreadMessages = if (hasUnreadThreadMessages) { + latestMessageTimelineEventEntity?.root?.sender != currentUserId + } else { + if (latestMessageTimelineEventEntity?.root?.sender == currentUserId) false else !isInitialSync + } } /** @@ -96,6 +107,16 @@ internal fun TimelineEventEntity.Companion.findAllThreadsForRoomId(realm: Realm, .equalTo(TimelineEventEntityFields.ROOT.IS_ROOT_THREAD, true) .sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING) +/** + * Find the number of all the local notifications for the specified room + * @param roomId The room that the number of notifications will be returned + */ +internal fun TimelineEventEntity.Companion.findAllLocalThreadNotificationsForRoomId(realm: Realm, roomId: String): RealmQuery = + TimelineEventEntity + .whereRoomId(realm, roomId = roomId) + .equalTo(TimelineEventEntityFields.ROOT.IS_ROOT_THREAD, true) + .equalTo(TimelineEventEntityFields.ROOT.HAS_UNREAD_THREAD_MESSAGES, true) + /** * Returns whether or not the given user is participating in a current thread * @param roomId the room that the thread exists diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt index cf16138196b..319d91b12a2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt @@ -55,6 +55,7 @@ internal object EventMapper { eventEntity.decryptionErrorReason = event.mCryptoErrorReason eventEntity.decryptionErrorCode = event.mCryptoError?.name eventEntity.isRootThread = event.threadDetails?.isRootThread ?: false + eventEntity.hasUnreadThreadMessages = event.threadDetails?.hasUnreadMessage ?: false eventEntity.rootThreadEventId = event.getRootThreadEventId() eventEntity.numberOfThreads = event.threadDetails?.numberOfThreads ?: 0 return eventEntity @@ -111,6 +112,7 @@ internal object EventMapper { avatarUrl = timelineEventEntity.senderAvatar ) }, + hasUnreadMessage = eventEntity.hasUnreadThreadMessages, threadSummaryLatestTextMessage = eventEntity.threadSummaryLatestMessage?.root?.asDomain()?.getDecryptedTextSummary().orEmpty() ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt index 1898d63af85..1ba4d564bb7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt @@ -46,6 +46,7 @@ internal open class EventEntity(@Index var eventId: String = "", @Index var isRootThread: Boolean = false, @Index var rootThreadEventId: String? = null, var numberOfThreads: Int = 0, + var hasUnreadThreadMessages: Boolean = false, var threadSummaryLatestMessage: TimelineEventEntity? = null ) : RealmObject() { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt index 2335f7bcd2a..3f702abde83 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt @@ -32,9 +32,11 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineService import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.database.RealmSessionProvider +import org.matrix.android.sdk.internal.database.helper.findAllLocalThreadNotificationsForRoomId import org.matrix.android.sdk.internal.database.helper.findAllThreadsForRoomId import org.matrix.android.sdk.internal.database.helper.isUserParticipatingInThread import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper +import org.matrix.android.sdk.internal.database.model.EventEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields import org.matrix.android.sdk.internal.database.query.where @@ -42,6 +44,7 @@ import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask import org.matrix.android.sdk.internal.session.sync.handler.room.ReadReceiptHandler import org.matrix.android.sdk.internal.task.TaskExecutor +import org.matrix.android.sdk.internal.util.awaitTransaction internal class DefaultTimelineService @AssistedInject constructor( @Assisted private val roomId: String, @@ -106,6 +109,20 @@ internal class DefaultTimelineService @AssistedInject constructor( } } + override fun getNumberOfLocalThreadNotificationsLive(): LiveData> { + return monarchy.findAllMappedWithChanges( + { TimelineEventEntity.findAllLocalThreadNotificationsForRoomId(it, roomId = roomId) }, + { timelineEventMapper.map(it) } + ) + } + + override fun getNumberOfLocalThreadNotifications(): List { + return monarchy.fetchAllMappedSync( + { TimelineEventEntity.findAllLocalThreadNotificationsForRoomId(it, roomId = roomId) }, + { timelineEventMapper.map(it) } + ) + } + override fun getAllThreadsLive(): LiveData> { return monarchy.findAllMappedWithChanges( { TimelineEventEntity.findAllThreadsForRoomId(it, roomId = roomId) }, @@ -129,4 +146,12 @@ internal class DefaultTimelineService @AssistedInject constructor( senderId = senderId) } } + + override suspend fun markThreadAsRead(rootThreadEventId: String) { + monarchy.awaitTransaction { + EventEntity.where( + realm = it, + eventId = rootThreadEventId).findFirst()?.hasUnreadThreadMessages = false + } + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt index f6441c9d607..2fa298a171e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt @@ -267,7 +267,9 @@ internal class TokenChunkEventPersistor @Inject constructor(@SessionDatabase pri RoomEntity.where(realm, roomId).findFirst()?.addIfNecessary(currentChunk) } - optimizedThreadSummaryMap.updateThreadSummaryIfNeeded() + // passing isInitialSync = true because we want to disable local notifications + // they do not work properly without the API + optimizedThreadSummaryMap.updateThreadSummaryIfNeeded(true) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt index 8d64c7fc969..8c258e7d91d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt @@ -425,7 +425,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle } } - optimizedThreadSummaryMap.updateThreadSummaryIfNeeded() + optimizedThreadSummaryMap.updateThreadSummaryIfNeeded(insertType == EventInsertType.INITIAL_SYNC, userId) // posting new events to timeline if any is registered timelineInput.onNewTimelineEvents(roomId = roomId, eventIds = eventIds) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index 907ca360bbd..cbe5e542fbd 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -189,8 +189,12 @@ class RoomDetailViewModel @AssistedInject constructor( if (OutboundSessionKeySharingStrategy.WhenEnteringRoom == BuildConfig.outboundSessionKeySharingStrategy && room.isEncrypted()) { prepareForEncryption() } + markThreadTimelineAsReadLocal() + observeLocalThreadNotifications() } + + private fun observeDataStore() { viewModelScope.launch { vectorDataStore.pushCounterFlow.collect { nbOfPush -> @@ -280,6 +284,17 @@ class RoomDetailViewModel @AssistedInject constructor( } } + /** + * Observe local unread threads + */ + private fun observeLocalThreadNotifications(){ + room.flow() + .liveLocalUnreadThreadList() + .execute { + copy(numberOfLocalUnreadThreads = it.invoke()?.size ?: 0) + } + + } fun getOtherUserIds() = room.roomSummary()?.otherMemberIds fun getRoomSummary() = room.roomSummary() @@ -1112,6 +1127,17 @@ class RoomDetailViewModel @AssistedInject constructor( } } + /** + * Mark the thread as read, while the user navigated within the thread + * This is a local implementation has nothing to do with APIs + */ + private fun markThreadTimelineAsReadLocal(){ + initialState.rootThreadEventId?.let{ + session.coroutineScope.launch { + room.markThreadAsRead(it) + } + } + } override fun onTimelineUpdated(snapshot: List) { timelineEvents.tryEmit(snapshot) 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 fa772ca0732..df6c75d30c1 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 @@ -67,8 +67,9 @@ data class RoomDetailViewState( val isAllowedToStartWebRTCCall: Boolean = true, val hasFailedSending: Boolean = false, val jitsiState: JitsiState = JitsiState(), - val rootThreadEventId: String? = null - ) : MavericksState { + val rootThreadEventId: String? = null, + val numberOfLocalUnreadThreads: Int = 0 +) : MavericksState { constructor(args: TimelineArgs) : this( roomId = args.roomId, 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 b37ba12f37a..f12ca9e84cb 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 @@ -1031,9 +1031,9 @@ class TimelineFragment @Inject constructor( val badgeFrameLayout = menuThreadList.findViewById(R.id.threadNotificationBadgeFrameLayout) val badgeTextView = menuThreadList.findViewById(R.id.threadNotificationBadgeTextView) - val unreadThreadMessages = 18 + state.pushCounter + val unreadThreadMessages = state.numberOfLocalUnreadThreads + val userIsMentioned = false - val userIsMentioned = true if (unreadThreadMessages > 0) { badgeFrameLayout.isVisible = true badgeTextView.text = unreadThreadMessages.toString() diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadListModel.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadListModel.kt index f47f6f46cc8..f3aac46ed33 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadListModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadListModel.kt @@ -19,6 +19,7 @@ package im.vector.app.features.home.room.threads.list.model import android.widget.ImageView import android.widget.TextView import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.view.isVisible import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.app.R @@ -42,6 +43,7 @@ abstract class ThreadListModel : VectorEpoxyModel() { @EpoxyAttribute lateinit var date: String @EpoxyAttribute lateinit var rootMessage: String @EpoxyAttribute lateinit var lastMessage: String + @EpoxyAttribute var unreadMessage: Boolean = false @EpoxyAttribute lateinit var lastMessageCounter: String @EpoxyAttribute var rootMessageDeleted: Boolean = false @EpoxyAttribute var lastMessageMatrixItem: MatrixItem? = null @@ -69,6 +71,7 @@ abstract class ThreadListModel : VectorEpoxyModel() { holder.lastMessageAvatarImageView.contentDescription = lastMessageMatrixItem?.getBestName() holder.lastMessageTextView.text = lastMessage holder.lastMessageCounterTextView.text = lastMessageCounter + holder.unreadImageView.isVisible = unreadMessage } class Holder : VectorEpoxyHolder() { @@ -79,6 +82,8 @@ abstract class ThreadListModel : VectorEpoxyModel() { val lastMessageAvatarImageView by bind(R.id.messageThreadSummaryAvatarImageView) val lastMessageCounterTextView by bind(R.id.messageThreadSummaryCounterTextView) val lastMessageTextView by bind(R.id.messageThreadSummaryInfoTextView) + val unreadImageView by bind(R.id.threadSummaryUnreadImageView) + val rootView by bind(R.id.threadSummaryRootConstraintLayout) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListController.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListController.kt index d17dee6e514..6e07f0a95f2 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListController.kt @@ -53,6 +53,7 @@ class ThreadListController @Inject constructor( title(timelineEvent.senderInfo.displayName) date(date) rootMessageDeleted(timelineEvent.root.isRedacted()) + unreadMessage(timelineEvent.root.threadDetails?.hasUnreadMessage ?: false) rootMessage(timelineEvent.root.getDecryptedTextSummary()) lastMessage(timelineEvent.root.threadDetails?.threadSummaryLatestTextMessage.orEmpty()) lastMessageCounter(timelineEvent.root.threadDetails?.numberOfThreads.toString()) diff --git a/vector/src/main/res/layout/item_thread_list.xml b/vector/src/main/res/layout/item_thread_list.xml index 8cf93c54047..6a1d075b7cf 100644 --- a/vector/src/main/res/layout/item_thread_list.xml +++ b/vector/src/main/res/layout/item_thread_list.xml @@ -1,18 +1,17 @@ - + android:foreground="?attr/selectableItemBackground" + android:paddingStart="12dp" + android:paddingTop="12dp" + android:paddingEnd="0dp"> + + Date: Thu, 9 Dec 2021 16:33:11 +0200 Subject: [PATCH 022/581] Add/Fix local echo to threads timeline --- .../room/relation/DefaultRelationService.kt | 32 +++++++++++-------- .../session/room/timeline/DefaultTimeline.kt | 6 +++- 2 files changed, 24 insertions(+), 14 deletions(-) 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 23862ae9638..3852dd50b8b 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 @@ -168,25 +168,31 @@ internal class DefaultRelationService @AssistedInject constructor( msgType: String, autoMarkdown: Boolean, formattedText: String?, - eventReplied: TimelineEvent?): Cancelable { - val event = eventReplied?.let { + eventReplied: TimelineEvent?): Cancelable? { + + val event = if (eventReplied != null) { eventFactory.createReplyTextEvent( roomId = roomId, eventReplied = eventReplied, replyText = replyInThreadText, autoMarkdown = autoMarkdown, rootThreadEventId = rootThreadEventId) - } ?: eventFactory.createThreadTextEvent( - rootThreadEventId = rootThreadEventId, - roomId = roomId, - text = replyInThreadText.toString(), - msgType = msgType, - autoMarkdown = autoMarkdown, - formattedText = formattedText) -// .also { -// saveLocalEcho(it) -// } - return eventSenderProcessor.postEvent(event, cryptoSessionInfoProvider.isRoomEncrypted(roomId)) + ?.also { + saveLocalEcho(it) + } ?: return null + } else { + eventFactory.createThreadTextEvent( + rootThreadEventId = rootThreadEventId, + roomId = roomId, + text = replyInThreadText.toString(), + msgType = msgType, + autoMarkdown = autoMarkdown, + formattedText = formattedText) + .also { + saveLocalEcho(it) + } + } + return eventSenderProcessor.postEvent(event, cryptoSessionInfoProvider.isRoomEncrypted(roomId)) } /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt index 4d417fddbbf..fe121090a04 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt @@ -448,7 +448,11 @@ internal class DefaultTimeline( */ private fun handleUpdates(results: RealmResults, changeSet: OrderedCollectionChangeSet) { // If changeSet has deletion we are having a gap, so we clear everything - if (changeSet.deletionRanges.isNotEmpty()) { + // I observed there is a problem with this implementation in the threads timeline upon receiving + // a local echo, after adding && !isFromThreadTimeline below fixed the issue. + // Maybe there is a deeper problem here even on the main timeline + + if (changeSet.deletionRanges.isNotEmpty() && !isFromThreadTimeline) { clearAllValues() } var postSnapshot = false From 57ef0b59ab10bae0ba22009c9b12cb8b239a6cec Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Thu, 9 Dec 2021 20:29:13 +0200 Subject: [PATCH 023/581] Disable local echo for normal messages while there is a duplication --- .../session/room/relation/DefaultRelationService.kt | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) 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 3852dd50b8b..4500c71e592 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 @@ -177,9 +177,10 @@ internal class DefaultRelationService @AssistedInject constructor( replyText = replyInThreadText, autoMarkdown = autoMarkdown, rootThreadEventId = rootThreadEventId) - ?.also { - saveLocalEcho(it) - } ?: return null +// ?.also { +// saveLocalEcho(it) +// } + ?: return null } else { eventFactory.createThreadTextEvent( rootThreadEventId = rootThreadEventId, @@ -188,9 +189,9 @@ internal class DefaultRelationService @AssistedInject constructor( msgType = msgType, autoMarkdown = autoMarkdown, formattedText = formattedText) - .also { - saveLocalEcho(it) - } +// .also { +// saveLocalEcho(it) +// } } return eventSenderProcessor.postEvent(event, cryptoSessionInfoProvider.isRoomEncrypted(roomId)) } From 5c015a7444228537ec20d6dad6c4c8cb1e8c64ab Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Fri, 10 Dec 2021 20:15:39 +0200 Subject: [PATCH 024/581] Support stickers in threads --- .../home/room/detail/RoomDetailViewModel.kt | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index cbe5e542fbd..ec0caa7b6de 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -75,6 +75,7 @@ import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.crypto.MXCryptoError import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.LocalEcho +import org.matrix.android.sdk.api.session.events.model.RelationType import org.matrix.android.sdk.api.session.events.model.isAttachmentMessage import org.matrix.android.sdk.api.session.events.model.isTextMessage import org.matrix.android.sdk.api.session.events.model.toContent @@ -87,6 +88,7 @@ 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.matrix.android.sdk.api.session.room.model.message.getFileUrl +import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent import org.matrix.android.sdk.api.session.room.model.tombstone.RoomTombstoneContent import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.room.read.ReadService @@ -193,8 +195,6 @@ class RoomDetailViewModel @AssistedInject constructor( observeLocalThreadNotifications() } - - private fun observeDataStore() { viewModelScope.launch { vectorDataStore.pushCounterFlow.collect { nbOfPush -> @@ -287,14 +287,14 @@ class RoomDetailViewModel @AssistedInject constructor( /** * Observe local unread threads */ - private fun observeLocalThreadNotifications(){ + private fun observeLocalThreadNotifications() { room.flow() .liveLocalUnreadThreadList() .execute { copy(numberOfLocalUnreadThreads = it.invoke()?.size ?: 0) } - } + fun getOtherUserIds() = room.roomSummary()?.otherMemberIds fun getRoomSummary() = room.roomSummary() @@ -448,7 +448,10 @@ class RoomDetailViewModel @AssistedInject constructor( } private fun handleSendSticker(action: RoomDetailAction.SendSticker) { - room.sendEvent(EventType.STICKER, action.stickerContent.toContent()) + val content = initialState.rootThreadEventId?.let { + action.stickerContent.copy(relatesTo = RelationDefaultContent(RelationType.THREAD, it)) + } ?: action.stickerContent + room.sendEvent(EventType.STICKER, content.toContent()) } private fun handleStartCall(action: RoomDetailAction.StartCall) { @@ -1131,13 +1134,14 @@ class RoomDetailViewModel @AssistedInject constructor( * Mark the thread as read, while the user navigated within the thread * This is a local implementation has nothing to do with APIs */ - private fun markThreadTimelineAsReadLocal(){ - initialState.rootThreadEventId?.let{ + private fun markThreadTimelineAsReadLocal() { + initialState.rootThreadEventId?.let { session.coroutineScope.launch { room.markThreadAsRead(it) } } } + override fun onTimelineUpdated(snapshot: List) { timelineEvents.tryEmit(snapshot) From d56281dca7e31da4e52dfcc52f352e47c51e5a28 Mon Sep 17 00:00:00 2001 From: ariskotsomitopoulos Date: Tue, 14 Dec 2021 13:35:08 +0200 Subject: [PATCH 025/581] - Enhance local notification to work with read receipt & the latest chunk - Local notification mentioning system - Fix/Improve thread list filtering --- .../session/room/timeline/TimelineService.kt | 2 +- .../sdk/api/session/threads/ThreadDetails.kt | 6 +- .../threads/ThreadNotificationBadgeState.kt | 25 ++++ .../threads/ThreadNotificationState.kt | 34 +++++ .../session/threads/ThreadTimelineEvent.kt | 28 ++++ .../database/RealmSessionStoreMigration.kt | 2 +- .../database/helper/ThreadEventsHelper.kt | 136 ++++++++++++++++-- .../internal/database/mapper/EventMapper.kt | 6 +- .../internal/database/model/EventEntity.kt | 12 +- .../session/room/timeline/DefaultTimeline.kt | 2 +- .../room/timeline/DefaultTimelineService.kt | 9 +- .../room/timeline/TokenChunkEventPersistor.kt | 17 +-- .../sync/handler/room/RoomSyncHandler.kt | 5 +- .../home/room/detail/RoomDetailViewModel.kt | 12 +- .../home/room/detail/RoomDetailViewState.kt | 3 +- .../home/room/detail/TimelineFragment.kt | 4 +- .../threads/list/model/ThreadListModel.kt | 28 +++- .../list/viewmodel/ThreadListController.kt | 12 +- .../list/viewmodel/ThreadListViewModel.kt | 34 ++--- .../list/viewmodel/ThreadListViewState.kt | 4 +- 20 files changed, 318 insertions(+), 63 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadNotificationBadgeState.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadNotificationState.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadTimelineEvent.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineService.kt index 068fa87a667..4ac4aab4e6f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineService.kt @@ -84,7 +84,7 @@ interface TimelineService { * Returns whether or not the current user is participating in the thread * @param rootThreadEventId the eventId of the current thread */ - fun isUserParticipatingInThread(rootThreadEventId: String, senderId: String): Boolean + fun isUserParticipatingInThread(rootThreadEventId: String): Boolean /** * Marks the current thread as read. This is a local implementation diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadDetails.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadDetails.kt index 62568cdce14..ad6e139d01a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadDetails.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadDetails.kt @@ -18,10 +18,14 @@ package org.matrix.android.sdk.api.session.threads import org.matrix.android.sdk.api.session.room.sender.SenderInfo +/** + * This class contains all the details needed for threads. + * Is is mainly used from within an Event. + */ data class ThreadDetails( val isRootThread: Boolean = false, val numberOfThreads: Int = 0, val threadSummarySenderInfo: SenderInfo? = null, val threadSummaryLatestTextMessage: String? = null, - val hasUnreadMessage: Boolean = false + var threadNotificationState: ThreadNotificationState = ThreadNotificationState.NO_NEW_MESSAGE ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadNotificationBadgeState.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadNotificationBadgeState.kt new file mode 100644 index 00000000000..8e861e73de1 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadNotificationBadgeState.kt @@ -0,0 +1,25 @@ +/* + * Copyright 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.api.session.threads + +/** + * This class defines the state of a thread notification badge + */ +data class ThreadNotificationBadgeState( + val numberOfLocalUnreadThreads: Int = 0, + val isUserMentioned: Boolean = false +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadNotificationState.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadNotificationState.kt new file mode 100644 index 00000000000..093e4a7627e --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadNotificationState.kt @@ -0,0 +1,34 @@ +/* + * Copyright 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.api.session.threads + +/** + * This class defines the state of a thread notification + */ +enum class ThreadNotificationState { + + // There are no new message + NO_NEW_MESSAGE, + + // There is at least one new message + NEW_MESSAGE, + + // The is at least one new message that should bi highlighted + // ex. "Hello @aris.kotsomitopoulos" + NEW_HIGHLIGHTED_MESSAGE; + +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadTimelineEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadTimelineEvent.kt new file mode 100644 index 00000000000..7b433566b8d --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadTimelineEvent.kt @@ -0,0 +1,28 @@ +/* + * Copyright 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.api.session.threads + +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent + +/** + * This class contains a thread TimelineEvent along with a boolean that + * determines if the current user has participated in that event + */ +data class ThreadTimelineEvent( + val timelineEvent: TimelineEvent, + val isParticipating: Boolean +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt index 301a479d01b..88fdb1c471a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt @@ -375,7 +375,7 @@ internal object RealmSessionStoreMigration : RealmMigration { ?.addField(EventEntityFields.IS_ROOT_THREAD, Boolean::class.java, FieldAttribute.INDEXED) ?.addField(EventEntityFields.ROOT_THREAD_EVENT_ID, String::class.java, FieldAttribute.INDEXED) ?.addField(EventEntityFields.NUMBER_OF_THREADS, Int::class.java) - ?.addField(EventEntityFields.HAS_UNREAD_THREAD_MESSAGES, Boolean::class.java) + ?.addField(EventEntityFields.THREAD_NOTIFICATION_STATE_STR, String::class.java) ?.addRealmObjectField(EventEntityFields.THREAD_SUMMARY_LATEST_MESSAGE.`$`, eventEntity) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt index 34bc117ddf1..32184c0ae99 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt @@ -21,9 +21,14 @@ import io.realm.RealmQuery import io.realm.RealmResults import io.realm.Sort import org.matrix.android.sdk.BuildConfig +import org.matrix.android.sdk.api.session.threads.ThreadNotificationState +import org.matrix.android.sdk.internal.database.mapper.asDomain +import org.matrix.android.sdk.internal.database.model.ChunkEntity import org.matrix.android.sdk.internal.database.model.EventEntity +import org.matrix.android.sdk.internal.database.model.ReadReceiptEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields +import org.matrix.android.sdk.internal.database.query.findIncludingEvent import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.database.query.whereRoomId @@ -31,7 +36,7 @@ import org.matrix.android.sdk.internal.database.query.whereRoomId * Finds the root thread event and update it with the latest message summary along with the number * of threads included. If there is no root thread event no action is done */ -internal fun Map.updateThreadSummaryIfNeeded(isInitialSync: Boolean = false, currentUserId: String? = null) { +internal fun Map.updateThreadSummaryIfNeeded(roomId: String, realm: Realm, currentUserId: String) { if (!BuildConfig.THREADING_ENABLED) return @@ -47,13 +52,14 @@ internal fun Map.updateThreadSummaryIfNeeded(isInitialSync: val rootThreadEvent = if (eventEntity.isThread()) eventEntity.findRootThreadEvent() else eventEntity rootThreadEvent?.markEventAsRoot( - isInitialSync = isInitialSync, - currentUserId = currentUserId, threadsCounted = it.size, latestMessageTimelineEventEntity = latestMessage ) + } } + + updateNotificationsNew(roomId, realm, currentUserId) } /** @@ -71,19 +77,11 @@ internal fun EventEntity.findRootThreadEvent(): EventEntity? = * Mark or update the current event a root thread event */ internal fun EventEntity.markEventAsRoot( - isInitialSync: Boolean, - currentUserId: String?, threadsCounted: Int, latestMessageTimelineEventEntity: TimelineEventEntity?) { isRootThread = true numberOfThreads = threadsCounted threadSummaryLatestMessage = latestMessageTimelineEventEntity - // skip notification coming from messages from the same user, also retain already marked events - hasUnreadThreadMessages = if (hasUnreadThreadMessages) { - latestMessageTimelineEventEntity?.root?.sender != currentUserId - } else { - if (latestMessageTimelineEventEntity?.root?.sender == currentUserId) false else !isInitialSync - } } /** @@ -115,7 +113,9 @@ internal fun TimelineEventEntity.Companion.findAllLocalThreadNotificationsForRoo TimelineEventEntity .whereRoomId(realm, roomId = roomId) .equalTo(TimelineEventEntityFields.ROOT.IS_ROOT_THREAD, true) - .equalTo(TimelineEventEntityFields.ROOT.HAS_UNREAD_THREAD_MESSAGES, true) + .equalTo(TimelineEventEntityFields.ROOT.THREAD_NOTIFICATION_STATE_STR, ThreadNotificationState.NEW_MESSAGE.name) + .or() + .equalTo(TimelineEventEntityFields.ROOT.THREAD_NOTIFICATION_STATE_STR, ThreadNotificationState.NEW_HIGHLIGHTED_MESSAGE.name) /** * Returns whether or not the given user is participating in a current thread @@ -131,3 +131,115 @@ internal fun TimelineEventEntity.Companion.isUserParticipatingInThread(realm: Re .findFirst() ?.let { true } ?: false + +/** + * Returns whether or not the given user is mentioned in a current thread + * @param roomId the room that the thread exists + * @param rootThreadEventId the thread that the search will be done + * @param userId the user that will try to find if there is a mention + */ +internal fun TimelineEventEntity.Companion.isUserMentionedInThread(realm: Realm, roomId: String, rootThreadEventId: String, userId: String): Boolean = + TimelineEventEntity + .whereRoomId(realm, roomId = roomId) + .equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, rootThreadEventId) + .equalTo(TimelineEventEntityFields.ROOT.SENDER, userId) + .findAll() + .firstOrNull { isUserMentioned(userId, it) } + ?.let { true } + ?: false + +/** + * Find the read receipt for the current user + */ +internal fun findMyReadReceipt(realm: Realm, roomId: String, userId: String): String? = + ReadReceiptEntity.where(realm, roomId = roomId, userId = userId) + .findFirst() + ?.eventId + +/** + * Returns whether or not the user is mentioned in the event + */ +internal fun isUserMentioned(currentUserId: String, timelineEventEntity: TimelineEventEntity?): Boolean { + val decryptedContent = timelineEventEntity?.root?.asDomain()?.getDecryptedTextSummary().orEmpty() + return decryptedContent.contains(currentUserId.replace("@", "").substringBefore(":")) +} + +/** + * Update badge notifications. Count the number of new thread events after the latest + * read receipt and aggregate. This function will find and notify new thread events + * that the user is either mentioned, or the user had participated in. + * Important: If the root thread event is not fetched notification will not work + * Important: It will work only with the latest chunk, while read marker will be changed + * immediately so we should not display wrong notifications + */ +internal fun updateNotificationsNew(roomId: String, realm: Realm, currentUserId: String) { + + val readReceipt = findMyReadReceipt(realm, roomId, currentUserId) ?: return + + val readReceiptChunk = ChunkEntity + .findIncludingEvent(realm, readReceipt) ?: return + + val readReceiptChunkTimelineEvents = readReceiptChunk + .timelineEvents + .where() + .equalTo(TimelineEventEntityFields.ROOM_ID, roomId) + .sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.ASCENDING) + .findAll() ?: return + + val readReceiptChunkPosition = readReceiptChunkTimelineEvents.indexOfFirst { it.eventId == readReceipt } + + if (readReceiptChunkPosition != -1 && readReceiptChunkPosition != readReceiptChunkTimelineEvents.lastIndex) { + // If the read receipt is found inside the chunk + + val threadEventsAfterReadReceipt = readReceiptChunkTimelineEvents + .slice(readReceiptChunkPosition..readReceiptChunkTimelineEvents.lastIndex) + .filter { it.root?.isThread() == true } + + // In order for the below code to work for old events, we should save the previous read receipt + // and then continue with the chunk search for that read receipt + /* + val newThreadEventsList = arrayListOf() + newThreadEventsList.addAll(threadEventsAfterReadReceipt) + + // got from latest chunk all new threads, lets move to the others + var nextChunk = ChunkEntity + .find(realm = realm, roomId = roomId, nextToken = readReceiptChunk.nextToken) + .takeIf { readReceiptChunk.nextToken != null } + while (nextChunk != null) { + newThreadEventsList.addAll(nextChunk.timelineEvents + .filter { it.root?.isThread() == true }) + nextChunk = ChunkEntity + .find(realm = realm, roomId = roomId, nextToken = nextChunk.nextToken) + .takeIf { readReceiptChunk.nextToken != null } + }*/ + + // Find if the user is mentioned in those events + val userMentionsList = threadEventsAfterReadReceipt + .filter { + isUserMentioned(currentUserId = currentUserId, it) + }.map { + it.root?.rootThreadEventId + } + + // Find the root events in the new thread events + val rootThreads = threadEventsAfterReadReceipt.distinctBy { it.root?.rootThreadEventId }.mapNotNull { it.root?.rootThreadEventId } + + // Update root thread events only if the user have participated in + rootThreads.forEach { eventId -> + val isUserParticipating = TimelineEventEntity.isUserParticipatingInThread( + realm = realm, + roomId = roomId, + rootThreadEventId = eventId, + senderId = currentUserId) + val rootThreadEventEntity = EventEntity.where(realm, eventId).findFirst() + + if (isUserParticipating) { + rootThreadEventEntity?.threadNotificationState = ThreadNotificationState.NEW_MESSAGE + } + + if (userMentionsList.contains(eventId)) { + rootThreadEventEntity?.threadNotificationState = ThreadNotificationState.NEW_HIGHLIGHTED_MESSAGE + } + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt index 319d91b12a2..53925b1a8f8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt @@ -25,6 +25,7 @@ import org.matrix.android.sdk.api.session.events.model.getRootThreadEventId import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.sender.SenderInfo import org.matrix.android.sdk.api.session.threads.ThreadDetails +import org.matrix.android.sdk.api.session.threads.ThreadNotificationState import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult import org.matrix.android.sdk.internal.database.model.EventEntity import org.matrix.android.sdk.internal.di.MoshiProvider @@ -55,9 +56,9 @@ internal object EventMapper { eventEntity.decryptionErrorReason = event.mCryptoErrorReason eventEntity.decryptionErrorCode = event.mCryptoError?.name eventEntity.isRootThread = event.threadDetails?.isRootThread ?: false - eventEntity.hasUnreadThreadMessages = event.threadDetails?.hasUnreadMessage ?: false eventEntity.rootThreadEventId = event.getRootThreadEventId() eventEntity.numberOfThreads = event.threadDetails?.numberOfThreads ?: 0 + eventEntity.threadNotificationState = event.threadDetails?.threadNotificationState ?: ThreadNotificationState.NO_NEW_MESSAGE return eventEntity } @@ -100,7 +101,6 @@ internal object EventMapper { MXCryptoError.ErrorType.valueOf(errorCode) } it.mCryptoErrorReason = eventEntity.decryptionErrorReason - it.threadDetails = ThreadDetails( isRootThread = eventEntity.isRootThread, numberOfThreads = eventEntity.numberOfThreads, @@ -112,7 +112,7 @@ internal object EventMapper { avatarUrl = timelineEventEntity.senderAvatar ) }, - hasUnreadMessage = eventEntity.hasUnreadThreadMessages, + threadNotificationState = eventEntity.threadNotificationState, threadSummaryLatestTextMessage = eventEntity.threadSummaryLatestMessage?.root?.asDomain()?.getDecryptedTextSummary().orEmpty() ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt index 1ba4d564bb7..b8e3de86819 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt @@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.database.model import io.realm.RealmObject import io.realm.annotations.Index import org.matrix.android.sdk.api.session.room.send.SendState +import org.matrix.android.sdk.api.session.threads.ThreadNotificationState import org.matrix.android.sdk.internal.crypto.MXEventDecryptionResult import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult import org.matrix.android.sdk.internal.di.MoshiProvider @@ -46,7 +47,7 @@ internal open class EventEntity(@Index var eventId: String = "", @Index var isRootThread: Boolean = false, @Index var rootThreadEventId: String? = null, var numberOfThreads: Int = 0, - var hasUnreadThreadMessages: Boolean = false, +// var threadNotificationState: Boolean = false, var threadSummaryLatestMessage: TimelineEventEntity? = null ) : RealmObject() { @@ -61,6 +62,15 @@ internal open class EventEntity(@Index var eventId: String = "", sendStateStr = value.name } + private var threadNotificationStateStr: String = ThreadNotificationState.NO_NEW_MESSAGE.name + var threadNotificationState: ThreadNotificationState + get() { + return ThreadNotificationState.valueOf(threadNotificationStateStr) + } + set(value) { + threadNotificationStateStr = value.name + } + companion object fun setDecryptionResult(result: MXEventDecryptionResult) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt index fe121090a04..707fe487ba4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt @@ -169,7 +169,7 @@ internal class DefaultTimeline( .whereRoomId(realm, roomId = roomId) .equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, it) .or() - .equalTo(TimelineEventEntityFields.ROOT.EVENT_ID, rootThreadEventId) + .equalTo(TimelineEventEntityFields.ROOT.EVENT_ID, it) .sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING) .findAll() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt index 3f702abde83..95fb5f85956 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt @@ -30,6 +30,7 @@ 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.TimelineService import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings +import org.matrix.android.sdk.api.session.threads.ThreadNotificationState import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.database.RealmSessionProvider import org.matrix.android.sdk.internal.database.helper.findAllLocalThreadNotificationsForRoomId @@ -41,6 +42,7 @@ import org.matrix.android.sdk.internal.database.model.TimelineEventEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask import org.matrix.android.sdk.internal.session.sync.handler.room.ReadReceiptHandler import org.matrix.android.sdk.internal.task.TaskExecutor @@ -48,6 +50,7 @@ import org.matrix.android.sdk.internal.util.awaitTransaction internal class DefaultTimelineService @AssistedInject constructor( @Assisted private val roomId: String, + @UserId private val userId: String, @SessionDatabase private val monarchy: Monarchy, private val realmSessionProvider: RealmSessionProvider, private val timelineInput: TimelineInput, @@ -137,13 +140,13 @@ internal class DefaultTimelineService @AssistedInject constructor( ) } - override fun isUserParticipatingInThread(rootThreadEventId: String, senderId: String): Boolean { + override fun isUserParticipatingInThread(rootThreadEventId: String): Boolean { return Realm.getInstance(monarchy.realmConfiguration).use { TimelineEventEntity.isUserParticipatingInThread( realm = it, roomId = roomId, rootThreadEventId = rootThreadEventId, - senderId = senderId) + senderId = userId) } } @@ -151,7 +154,7 @@ internal class DefaultTimelineService @AssistedInject constructor( monarchy.awaitTransaction { EventEntity.where( realm = it, - eventId = rootThreadEventId).findFirst()?.hasUnreadThreadMessages = false + eventId = rootThreadEventId).findFirst()?.threadNotificationState = ThreadNotificationState.NO_NEW_MESSAGE } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt index 2fa298a171e..8331d3f5f46 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt @@ -18,10 +18,7 @@ package org.matrix.android.sdk.internal.session.room.timeline import com.zhuinden.monarchy.Monarchy import io.realm.Realm -import io.realm.kotlin.createObject -import org.matrix.android.sdk.BuildConfig import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.events.model.isThread import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.RoomMemberContent import org.matrix.android.sdk.api.session.room.send.SendState @@ -44,8 +41,8 @@ import org.matrix.android.sdk.internal.database.query.findAllIncludingEvents import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfRoom import org.matrix.android.sdk.internal.database.query.getOrCreate import org.matrix.android.sdk.internal.database.query.where -import org.matrix.android.sdk.internal.database.query.whereRootThreadEventId import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryEventsHelper import org.matrix.android.sdk.internal.util.awaitTransaction import timber.log.Timber @@ -54,7 +51,9 @@ import javax.inject.Inject /** * Insert Chunk in DB, and eventually merge with existing chunk event */ -internal class TokenChunkEventPersistor @Inject constructor(@SessionDatabase private val monarchy: Monarchy) { +internal class TokenChunkEventPersistor @Inject constructor( + @SessionDatabase private val monarchy: Monarchy, + @UserId private val userId: String) { /** *
@@ -213,7 +212,7 @@ internal class TokenChunkEventPersistor @Inject constructor(@SessionDatabase pri
         }
         val eventIds = ArrayList(eventList.size)
 
-        val optimizedThreadSummaryMap = hashMapOf()
+        val optimizedThreadSummaryMap = hashMapOf()
         eventList.forEach { event ->
             if (event.eventId == null || event.senderId == null) {
                 return@forEach
@@ -260,16 +259,12 @@ internal class TokenChunkEventPersistor @Inject constructor(@SessionDatabase pri
         val shouldUpdateSummary = roomSummaryEntity.latestPreviewableEvent == null ||
                 (chunksToDelete.isNotEmpty() && currentChunk.isLastForward && direction == PaginationDirection.FORWARDS)
         if (shouldUpdateSummary) {
-            // TODO maybe add support to view latest thread message
             roomSummaryEntity.latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId)
         }
         if (currentChunk.isValid) {
             RoomEntity.where(realm, roomId).findFirst()?.addIfNecessary(currentChunk)
         }
 
-        // passing isInitialSync = true because we want to disable local notifications
-        // they do not work properly without the API
-        optimizedThreadSummaryMap.updateThreadSummaryIfNeeded(true)
-
+        optimizedThreadSummaryMap.updateThreadSummaryIfNeeded(roomId = roomId, realm = realm, currentUserId = userId)
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt
index 8c258e7d91d..9f080e06487 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt
@@ -425,7 +425,10 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
             }
         }
 
-        optimizedThreadSummaryMap.updateThreadSummaryIfNeeded(insertType == EventInsertType.INITIAL_SYNC, userId)
+        optimizedThreadSummaryMap.updateThreadSummaryIfNeeded(
+                roomId = roomId,
+                realm = realm,
+                currentUserId = userId)
 
         // posting new events to timeline if any is registered
         timelineInput.onNewTimelineEvents(roomId = roomId, eventIds = eventIds)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
index ec0caa7b6de..88be46bd0b6 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
@@ -94,6 +94,8 @@ import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
 import org.matrix.android.sdk.api.session.room.read.ReadService
 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.threads.ThreadNotificationBadgeState
+import org.matrix.android.sdk.api.session.threads.ThreadNotificationState
 import org.matrix.android.sdk.api.session.widgets.model.WidgetType
 import org.matrix.android.sdk.api.util.toOptional
 import org.matrix.android.sdk.flow.flow
@@ -291,7 +293,14 @@ class RoomDetailViewModel @AssistedInject constructor(
         room.flow()
                 .liveLocalUnreadThreadList()
                 .execute {
-                    copy(numberOfLocalUnreadThreads = it.invoke()?.size ?: 0)
+                    val threadList = it.invoke()
+                    val isUserMentioned = threadList?.firstOrNull { timelineEvent ->
+                        timelineEvent.root.threadDetails?.threadNotificationState == ThreadNotificationState.NEW_HIGHLIGHTED_MESSAGE
+                    }?.let { true } ?: false
+                    val numberOfLocalUnreadThreads = threadList?.size ?: 0
+                    copy(threadNotificationBadgeState = ThreadNotificationBadgeState(
+                            numberOfLocalUnreadThreads = numberOfLocalUnreadThreads,
+                            isUserMentioned = isUserMentioned))
                 }
     }
 
@@ -1178,6 +1187,7 @@ class RoomDetailViewModel @AssistedInject constructor(
         chatEffectManager.delegate = null
         chatEffectManager.dispose()
         callManager.removeProtocolsCheckerListener(this)
+        markThreadTimelineAsReadLocal()
         super.onCleared()
     }
 }
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 df6c75d30c1..051c9b65001 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
@@ -27,6 +27,7 @@ import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
 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.sync.SyncState
+import org.matrix.android.sdk.api.session.threads.ThreadNotificationBadgeState
 import org.matrix.android.sdk.api.session.widgets.model.Widget
 import org.matrix.android.sdk.api.session.widgets.model.WidgetType
 
@@ -68,7 +69,7 @@ data class RoomDetailViewState(
         val hasFailedSending: Boolean = false,
         val jitsiState: JitsiState = JitsiState(),
         val rootThreadEventId: String? = null,
-        val numberOfLocalUnreadThreads: Int = 0
+        val threadNotificationBadgeState: ThreadNotificationBadgeState = ThreadNotificationBadgeState()
 ) : MavericksState {
 
     constructor(args: TimelineArgs) : this(
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 f12ca9e84cb..92a8acf0cff 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
@@ -1031,8 +1031,8 @@ class TimelineFragment @Inject constructor(
         val badgeFrameLayout = menuThreadList.findViewById(R.id.threadNotificationBadgeFrameLayout)
         val badgeTextView = menuThreadList.findViewById(R.id.threadNotificationBadgeTextView)
 
-        val unreadThreadMessages = state.numberOfLocalUnreadThreads
-        val userIsMentioned = false
+        val unreadThreadMessages = state.threadNotificationBadgeState.numberOfLocalUnreadThreads
+        val userIsMentioned = state.threadNotificationBadgeState.isUserMentioned
 
         if (unreadThreadMessages > 0) {
             badgeFrameLayout.isVisible = true
diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadListModel.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadListModel.kt
index f3aac46ed33..286f0279150 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadListModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadListModel.kt
@@ -19,6 +19,7 @@ package im.vector.app.features.home.room.threads.list.model
 import android.widget.ImageView
 import android.widget.TextView
 import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.content.ContextCompat
 import androidx.core.view.isVisible
 import com.airbnb.epoxy.EpoxyAttribute
 import com.airbnb.epoxy.EpoxyModelClass
@@ -32,6 +33,7 @@ import im.vector.app.core.extensions.setLeftDrawable
 import im.vector.app.core.utils.DimensionConverter
 import im.vector.app.features.displayname.getBestName
 import im.vector.app.features.home.AvatarRenderer
+import org.matrix.android.sdk.api.session.threads.ThreadNotificationState
 import org.matrix.android.sdk.api.util.MatrixItem
 
 @EpoxyModelClass(layout = R.layout.item_thread_list)
@@ -43,7 +45,7 @@ abstract class ThreadListModel : VectorEpoxyModel() {
     @EpoxyAttribute lateinit var date: String
     @EpoxyAttribute lateinit var rootMessage: String
     @EpoxyAttribute lateinit var lastMessage: String
-    @EpoxyAttribute  var unreadMessage: Boolean = false
+    @EpoxyAttribute var threadNotificationState: ThreadNotificationState = ThreadNotificationState.NO_NEW_MESSAGE
     @EpoxyAttribute lateinit var lastMessageCounter: String
     @EpoxyAttribute var rootMessageDeleted: Boolean = false
     @EpoxyAttribute var lastMessageMatrixItem: MatrixItem? = null
@@ -56,11 +58,11 @@ abstract class ThreadListModel : VectorEpoxyModel() {
         holder.avatarImageView.contentDescription = matrixItem.getBestName()
         holder.titleTextView.text = title
         holder.dateTextView.text = date
-        if (rootMessageDeleted){
+        if (rootMessageDeleted) {
             holder.rootMessageTextView.text = holder.view.context.getString(R.string.event_redacted)
             holder.rootMessageTextView.setLeftDrawable(R.drawable.ic_trash_16, R.attr.colorOnPrimary)
             holder.rootMessageTextView.compoundDrawablePadding = DimensionConverter(holder.view.context.resources).dpToPx(10)
-        }else{
+        } else {
             holder.rootMessageTextView.text = rootMessage
             holder.rootMessageTextView.clearDrawables()
         }
@@ -71,7 +73,24 @@ abstract class ThreadListModel : VectorEpoxyModel() {
         holder.lastMessageAvatarImageView.contentDescription = lastMessageMatrixItem?.getBestName()
         holder.lastMessageTextView.text = lastMessage
         holder.lastMessageCounterTextView.text = lastMessageCounter
-        holder.unreadImageView.isVisible = unreadMessage
+        renderNotificationState(holder)
+    }
+
+    private fun renderNotificationState(holder: Holder) {
+
+        when (threadNotificationState) {
+            ThreadNotificationState.NEW_MESSAGE             -> {
+                holder.unreadImageView.isVisible = true
+                holder.unreadImageView.setColorFilter(ContextCompat.getColor(holder.view.context, R.color.palette_gray_200));
+            }
+            ThreadNotificationState.NEW_HIGHLIGHTED_MESSAGE -> {
+                holder.unreadImageView.isVisible = true
+                holder.unreadImageView.setColorFilter(ContextCompat.getColor(holder.view.context, R.color.palette_vermilion));
+            }
+            else                                            -> {
+                holder.unreadImageView.isVisible = false
+            }
+        }
     }
 
     class Holder : VectorEpoxyHolder() {
@@ -83,7 +102,6 @@ abstract class ThreadListModel : VectorEpoxyModel() {
         val lastMessageCounterTextView by bind(R.id.messageThreadSummaryCounterTextView)
         val lastMessageTextView by bind(R.id.messageThreadSummaryInfoTextView)
         val unreadImageView by bind(R.id.threadSummaryUnreadImageView)
-
         val rootView by bind(R.id.threadSummaryRootConstraintLayout)
     }
 }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListController.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListController.kt
index 6e07f0a95f2..26970e16b34 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListController.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListController.kt
@@ -22,6 +22,7 @@ import im.vector.app.core.date.VectorDateFormatter
 import im.vector.app.features.home.AvatarRenderer
 import im.vector.app.features.home.room.threads.list.model.threadList
 import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
+import org.matrix.android.sdk.api.session.threads.ThreadNotificationState
 import org.matrix.android.sdk.api.util.toMatrixItem
 import javax.inject.Inject
 
@@ -44,6 +45,15 @@ class ThreadListController @Inject constructor(
         val host = this
 
         safeViewState.rootThreadEventList.invoke()
+                ?.filter {
+                    if (safeViewState.shouldFilterThreads) {
+                        it.isParticipating
+                    } else {
+                        true
+                    }
+                }?.map {
+                    it.timelineEvent
+                }
                 ?.forEach { timelineEvent ->
                     val date = dateFormatter.format(timelineEvent.root.originServerTs, DateFormatKind.ROOM_LIST)
                     threadList {
@@ -53,7 +63,7 @@ class ThreadListController @Inject constructor(
                         title(timelineEvent.senderInfo.displayName)
                         date(date)
                         rootMessageDeleted(timelineEvent.root.isRedacted())
-                        unreadMessage(timelineEvent.root.threadDetails?.hasUnreadMessage ?: false)
+                        threadNotificationState(timelineEvent.root.threadDetails?.threadNotificationState ?: ThreadNotificationState.NO_NEW_MESSAGE)
                         rootMessage(timelineEvent.root.getDecryptedTextSummary())
                         lastMessage(timelineEvent.root.threadDetails?.threadSummaryLatestTextMessage.orEmpty())
                         lastMessageCounter(timelineEvent.root.threadDetails?.numberOfThreads.toString())
diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt
index 715478cec31..25dc14cec69 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewModel.kt
@@ -29,6 +29,7 @@ import im.vector.app.features.home.room.threads.list.views.ThreadListFragment
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import org.matrix.android.sdk.api.session.Session
+import org.matrix.android.sdk.api.session.threads.ThreadTimelineEvent
 import org.matrix.android.sdk.flow.flow
 
 class ThreadListViewModel @AssistedInject constructor(@Assisted val initialState: ThreadListViewState,
@@ -52,28 +53,29 @@ class ThreadListViewModel @AssistedInject constructor(@Assisted val initialState
     }
 
     init {
-        observeThreadsList(initialState.shouldFilterThreads)
+        observeThreadsList()
     }
 
     override fun handle(action: EmptyAction) {}
 
-    private fun observeThreadsList(shouldFilterThreads: Boolean) =
-            room?.flow()
-                    ?.liveThreadList()
-                    ?.map {
-                        if (!shouldFilterThreads) return@map it
-                        it.filter { timelineEvent ->
-                            room.isUserParticipatingInThread(timelineEvent.eventId, session.myUserId)
-                        }
-                    }
-                    ?.flowOn(room.coroutineDispatchers.io)
-                    ?.execute { asyncThreads ->
-                        copy(
-                                rootThreadEventList = asyncThreads,
-                                shouldFilterThreads = shouldFilterThreads)
+    private fun observeThreadsList() {
+        room?.flow()
+                ?.liveThreadList()
+                ?.map {
+                    it.map { timelineEvent ->
+                        val isParticipating = room.isUserParticipatingInThread(timelineEvent.eventId)
+                        ThreadTimelineEvent(timelineEvent, isParticipating)
                     }
+                }
+                ?.flowOn(room.coroutineDispatchers.io)
+                ?.execute { asyncThreads ->
+                    copy(rootThreadEventList = asyncThreads)
+                }
+    }
 
     fun applyFiltering(shouldFilterThreads: Boolean) {
-        observeThreadsList(shouldFilterThreads)
+        setState {
+            copy(shouldFilterThreads = shouldFilterThreads)
+        }
     }
 }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewState.kt
index 01a5239aac4..53d2a45344c 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewState.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewState.kt
@@ -20,10 +20,10 @@ import com.airbnb.mvrx.Async
 import com.airbnb.mvrx.MavericksState
 import com.airbnb.mvrx.Uninitialized
 import im.vector.app.features.home.room.threads.arguments.ThreadListArgs
-import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
+import org.matrix.android.sdk.api.session.threads.ThreadTimelineEvent
 
 data class ThreadListViewState(
-        val rootThreadEventList: Async> = Uninitialized,
+        val rootThreadEventList: Async> = Uninitialized,
         val shouldFilterThreads: Boolean = false,
         val roomId: String
 ) : MavericksState{

From 5ceed4096ef7f0dc30bf0fb4910169a7b7bccc6e Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Tue, 14 Dec 2021 15:44:38 +0200
Subject: [PATCH 026/581] Fix threads sort order, newest first

---
 .../sdk/internal/database/helper/ThreadEventsHelper.kt      | 6 ++++--
 .../sdk/internal/session/room/timeline/DefaultTimeline.kt   | 1 +
 2 files changed, 5 insertions(+), 2 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt
index 32184c0ae99..43f6c54c7ec 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt
@@ -103,7 +103,7 @@ internal fun TimelineEventEntity.Companion.findAllThreadsForRoomId(realm: Realm,
         TimelineEventEntity
                 .whereRoomId(realm, roomId = roomId)
                 .equalTo(TimelineEventEntityFields.ROOT.IS_ROOT_THREAD, true)
-                .sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)
+                .sort("${TimelineEventEntityFields.ROOT.THREAD_SUMMARY_LATEST_MESSAGE}.${TimelineEventEntityFields.DISPLAY_INDEX}", Sort.DESCENDING)
 
 /**
  * Find the number of all the local notifications for the specified room
@@ -188,7 +188,9 @@ internal fun updateNotificationsNew(roomId: String, realm: Realm, currentUserId:
 
     val readReceiptChunkPosition = readReceiptChunkTimelineEvents.indexOfFirst { it.eventId == readReceipt }
 
-    if (readReceiptChunkPosition != -1 && readReceiptChunkPosition != readReceiptChunkTimelineEvents.lastIndex) {
+    if(readReceiptChunkPosition == -1) return
+
+    if (readReceiptChunkPosition < readReceiptChunkTimelineEvents.lastIndex) {
         // If the read receipt is found inside the chunk
 
         val threadEventsAfterReadReceipt = readReceiptChunkTimelineEvents
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
index 707fe487ba4..f3ab1930ffc 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
@@ -167,6 +167,7 @@ internal class DefaultTimeline(
                 timelineEvents = rootThreadEventId?.let {
                     TimelineEventEntity
                             .whereRoomId(realm, roomId = roomId)
+                            .equalTo(TimelineEventEntityFields.CHUNK.IS_LAST_FORWARD, true)
                             .equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, it)
                             .or()
                             .equalTo(TimelineEventEntityFields.ROOT.EVENT_ID, it)

From 2aa24f0a0d81f524d04e149229afcfade5bdc132 Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Tue, 14 Dec 2021 16:30:59 +0200
Subject: [PATCH 027/581] Fix threads sort order, newest first

---
 .../matrix/android/sdk/api/session/threads/ThreadDetails.kt   | 1 +
 .../android/sdk/internal/database/mapper/EventMapper.kt       | 4 +++-
 .../home/room/threads/list/viewmodel/ThreadListController.kt  | 2 +-
 3 files changed, 5 insertions(+), 2 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadDetails.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadDetails.kt
index ad6e139d01a..26e8688d348 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadDetails.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadDetails.kt
@@ -27,5 +27,6 @@ data class ThreadDetails(
         val numberOfThreads: Int = 0,
         val threadSummarySenderInfo: SenderInfo? = null,
         val threadSummaryLatestTextMessage: String? = null,
+        val lastMessageTimestamp: Long? = null,
         var threadNotificationState: ThreadNotificationState = ThreadNotificationState.NO_NEW_MESSAGE
 )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt
index 53925b1a8f8..05070efe1f4 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt
@@ -113,7 +113,9 @@ internal object EventMapper {
                         )
                     },
                     threadNotificationState = eventEntity.threadNotificationState,
-                    threadSummaryLatestTextMessage = eventEntity.threadSummaryLatestMessage?.root?.asDomain()?.getDecryptedTextSummary().orEmpty()
+                    threadSummaryLatestTextMessage = eventEntity.threadSummaryLatestMessage?.root?.asDomain()?.getDecryptedTextSummary().orEmpty(),
+                    lastMessageTimestamp = eventEntity.threadSummaryLatestMessage?.root?.originServerTs
+
             )
         }
     }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListController.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListController.kt
index 26970e16b34..3f69701a314 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListController.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListController.kt
@@ -55,7 +55,7 @@ class ThreadListController @Inject constructor(
                     it.timelineEvent
                 }
                 ?.forEach { timelineEvent ->
-                    val date = dateFormatter.format(timelineEvent.root.originServerTs, DateFormatKind.ROOM_LIST)
+                    val date = dateFormatter.format(timelineEvent.root.threadDetails?.lastMessageTimestamp, DateFormatKind.ROOM_LIST)
                     threadList {
                         id(timelineEvent.eventId)
                         avatarRenderer(host.avatarRenderer)

From 6a33c4109185bf7026a68fd5469d4923b0423c6e Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Tue, 14 Dec 2021 17:45:07 +0200
Subject: [PATCH 028/581] Fix stickers in unencrypted rooms

---
 .../android/sdk/api/session/events/model/Event.kt    | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
index 621e525bd3e..d38e861ac39 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
@@ -23,6 +23,7 @@ import org.matrix.android.sdk.api.extensions.tryOrNull
 import org.matrix.android.sdk.api.failure.MatrixError
 import org.matrix.android.sdk.api.session.crypto.MXCryptoError
 import org.matrix.android.sdk.api.session.room.model.message.MessageContent
+import org.matrix.android.sdk.api.session.room.model.message.MessageStickerContent
 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.send.SendState
@@ -336,10 +337,19 @@ fun Event.isAttachmentMessage(): Boolean {
 }
 
 fun Event.getRelationContent(): RelationDefaultContent? {
+    if(eventId?.contains("MgPN5Bqb") == true)
+        Timber.i(":D")
     return if (isEncrypted()) {
         content.toModel()?.relatesTo
     } else {
-        content.toModel()?.relatesTo
+            content.toModel()?.relatesTo ?: run{
+                // Special case to handle stickers, while there is only a local msgtype for stickers
+                if (getClearType() == EventType.STICKER) {
+                    getClearContent().toModel()?.relatesTo
+                } else{
+                    null
+                }
+            }
     }
 }
 

From 20357ce5c4ede9762206f370340821fb1e00539e Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Wed, 15 Dec 2021 14:38:08 +0200
Subject: [PATCH 029/581]  - Fix remaining conflicts with develop  - Disable
 thread awareness when threads are enabled

---
 .../sdk/api/session/events/model/Event.kt     | 21 +++---
 .../session/room/timeline/TimelineEvent.kt    |  9 +++
 .../room/relation/DefaultRelationService.kt   | 12 ++--
 .../session/room/timeline/DefaultTimeline.kt  |  5 +-
 .../room/timeline/TimelineEventDecryptor.kt   | 17 +++--
 .../session/sync/SyncResponseHandler.kt       |  5 +-
 .../sync/handler/room/RoomSyncHandler.kt      | 11 ++--
 .../home/room/detail/TimelineFragment.kt      | 27 ++++----
 .../detail/composer/MessageComposerAction.kt  |  2 +-
 .../composer/MessageComposerViewModel.kt      | 14 ++--
 .../action/MessageActionsViewModel.kt         | 28 ++++----
 .../timeline/factory/MessageItemFactory.kt    | 12 ++++
 .../home/room/threads/ThreadsActivity.kt      | 18 ++----
 .../list/views/ThreadListBottomSheet.kt       |  2 +-
 .../features/permalink/PermalinkHandler.kt    | 64 ++++++++-----------
 .../room/RequireActiveMembershipViewState.kt  |  4 +-
 16 files changed, 140 insertions(+), 111 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
index 8b9d4d0f026..3e0aed87381 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
@@ -36,7 +36,6 @@ import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult
 import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
 import org.matrix.android.sdk.internal.di.MoshiProvider
 import org.matrix.android.sdk.internal.session.presence.model.PresenceContent
-import org.matrix.android.sdk.internal.session.room.send.removeInReplyFallbacks
 import timber.log.Timber
 
 typealias Content = JsonDict
@@ -338,20 +337,22 @@ fun Event.isAttachmentMessage(): Boolean {
             }
 }
 
+fun Event.isPoll(): Boolean = getClearType() == EventType.POLL_START ||  getClearType() == EventType.POLL_END
+
+fun Event.isSticker(): Boolean = getClearType() == EventType.STICKER
+
 fun Event.getRelationContent(): RelationDefaultContent? {
-    if(eventId?.contains("MgPN5Bqb") == true)
-        Timber.i(":D")
     return if (isEncrypted()) {
         content.toModel()?.relatesTo
     } else {
-            content.toModel()?.relatesTo ?: run{
-                // Special case to handle stickers, while there is only a local msgtype for stickers
-                if (getClearType() == EventType.STICKER) {
-                    getClearContent().toModel()?.relatesTo
-                } else{
-                    null
-                }
+        content.toModel()?.relatesTo ?: run {
+            // Special case to handle stickers, while there is only a local msgtype for stickers
+            if (getClearType() == EventType.STICKER) {
+                getClearContent().toModel()?.relatesTo
+            } else {
+                null
             }
+        }
     }
 }
 
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 932439c81c6..e181cc964c5 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
@@ -22,7 +22,9 @@ import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.events.model.RelationType
 import org.matrix.android.sdk.api.session.events.model.getRelationContent
 import org.matrix.android.sdk.api.session.events.model.isEdition
+import org.matrix.android.sdk.api.session.events.model.isPoll
 import org.matrix.android.sdk.api.session.events.model.isReply
+import org.matrix.android.sdk.api.session.events.model.isSticker
 import org.matrix.android.sdk.api.session.events.model.toModel
 import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary
 import org.matrix.android.sdk.api.session.room.model.ReadReceipt
@@ -145,6 +147,13 @@ fun TimelineEvent.isEdition(): Boolean {
     return root.isEdition()
 }
 
+fun TimelineEvent.isPoll(): Boolean =
+        root.isPoll()
+
+fun TimelineEvent.isSticker(): Boolean {
+    return root.isSticker()
+}
+
 /**
  * Get the latest message body, after a possible edition, stripping the reply prefix if necessary
  */
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 4500c71e592..a82d6e8d190 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
@@ -177,9 +177,9 @@ internal class DefaultRelationService @AssistedInject constructor(
                     replyText = replyInThreadText,
                     autoMarkdown = autoMarkdown,
                     rootThreadEventId = rootThreadEventId)
-//                    ?.also {
-//                        saveLocalEcho(it)
-//                    }
+                    ?.also {
+                        saveLocalEcho(it)
+                    }
                     ?: return null
         } else {
             eventFactory.createThreadTextEvent(
@@ -189,9 +189,9 @@ internal class DefaultRelationService @AssistedInject constructor(
                     msgType = msgType,
                     autoMarkdown = autoMarkdown,
                     formattedText = formattedText)
-//                    .also {
-//                        saveLocalEcho(it)
-//                    }
+                    .also {
+                        saveLocalEcho(it)
+                    }
         }
         return  eventSenderProcessor.postEvent(event, cryptoSessionInfoProvider.isRoomEncrypted(roomId))
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
index 42557809998..00388dc5fa8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
@@ -24,6 +24,7 @@ import io.realm.RealmQuery
 import io.realm.RealmResults
 import io.realm.Sort
 import kotlinx.coroutines.runBlocking
+import org.matrix.android.sdk.BuildConfig
 import org.matrix.android.sdk.api.MatrixCallback
 import org.matrix.android.sdk.api.extensions.orFalse
 import org.matrix.android.sdk.api.extensions.tryOrNull
@@ -639,7 +640,9 @@ internal class DefaultTimeline(
                 }.map {
                     EventMapper.map(it)
                 }
-        threadsAwarenessHandler.fetchRootThreadEventsIfNeeded(eventEntityList)
+        if(!BuildConfig.THREADING_ENABLED) {
+            threadsAwarenessHandler.fetchRootThreadEventsIfNeeded(eventEntityList)
+        }
     }
 
     private fun buildTimelineEvent(eventEntity: TimelineEventEntity): TimelineEvent {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt
index 75d02dfd98f..aa792f6b9b9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt
@@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.session.room.timeline
 
 import io.realm.Realm
 import io.realm.RealmConfiguration
+import org.matrix.android.sdk.BuildConfig
 import org.matrix.android.sdk.api.session.crypto.CryptoService
 import org.matrix.android.sdk.api.session.crypto.MXCryptoError
 import org.matrix.android.sdk.api.session.events.model.Event
@@ -114,11 +115,17 @@ internal class TimelineEventDecryptor @Inject constructor(
                         .findFirst()
 
                 eventEntity?.apply {
-                    val decryptedPayload = threadsAwarenessHandler.handleIfNeededDuringDecryption(
-                            it,
-                            roomId = event.roomId,
-                            event,
-                            result)
+
+                    val decryptedPayload =
+                            if (!BuildConfig.THREADING_ENABLED) {
+                                threadsAwarenessHandler.handleIfNeededDuringDecryption(
+                                        it,
+                                        roomId = event.roomId,
+                                        event,
+                                        result)
+                            } else {
+                                null
+                            }
                     setDecryptionResult(result, decryptedPayload)
                 }
             }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt
index f178074507c..5ac3eadb757 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt
@@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.sync
 
 import androidx.work.ExistingPeriodicWorkPolicy
 import com.zhuinden.monarchy.Monarchy
+import org.matrix.android.sdk.BuildConfig
 import org.matrix.android.sdk.api.pushrules.PushRuleService
 import org.matrix.android.sdk.api.pushrules.RuleScope
 import org.matrix.android.sdk.api.session.initsync.InitSyncStep
@@ -101,7 +102,9 @@ internal class SyncResponseHandler @Inject constructor(
         val aggregator = SyncResponsePostTreatmentAggregator()
 
         // Prerequisite for thread events handling in RoomSyncHandler
-        threadsAwarenessHandler.fetchRootThreadEventsIfNeeded(syncResponse)
+        if(!BuildConfig.THREADING_ENABLED) {
+            threadsAwarenessHandler.fetchRootThreadEventsIfNeeded(syncResponse)
+        }
 
         // Start one big transaction
         monarchy.awaitTransaction { realm ->
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt
index 9c3ce66b35f..f354a98f808 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt
@@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.sync.handler.room
 
 import io.realm.Realm
 import io.realm.kotlin.createObject
+import org.matrix.android.sdk.BuildConfig
 import org.matrix.android.sdk.api.session.crypto.MXCryptoError
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.events.model.EventType
@@ -375,10 +376,12 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
                 decryptIfNeeded(event, roomId)
             }
 
-            threadsAwarenessHandler.handleIfNeeded(
-                    realm = realm,
-                    roomId = roomId,
-                    event = event)
+            if(!BuildConfig.THREADING_ENABLED) {
+                threadsAwarenessHandler.handleIfNeeded(
+                        realm = realm,
+                        roomId = roomId,
+                        event = event)
+            }
 
             val ageLocalTs = event.unsignedData?.age?.let { syncLocalTimestampMillis - it }
             val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, insertType)
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 ead32a84f7b..69189131fc9 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
@@ -721,7 +721,7 @@ class TimelineFragment @Inject constructor(
             }
 
             override fun onVoiceRecordingCancelled() {
-                messageComposerViewModel.handle(MessageComposerAction.EndRecordingVoiceMessage(isCancelled = true))
+                messageComposerViewModel.handle(MessageComposerAction.EndRecordingVoiceMessage(isCancelled = true, rootThreadEventId = getRootThreadEventId()))
                 vibrate(requireContext())
                 updateRecordingUiState(RecordingUiState.Idle)
             }
@@ -737,12 +737,12 @@ class TimelineFragment @Inject constructor(
             }
 
             override fun onSendVoiceMessage() {
-                messageComposerViewModel.handle(MessageComposerAction.EndRecordingVoiceMessage(isCancelled = false))
+                messageComposerViewModel.handle(MessageComposerAction.EndRecordingVoiceMessage(isCancelled = false, rootThreadEventId = getRootThreadEventId()))
                 updateRecordingUiState(RecordingUiState.Idle)
             }
 
             override fun onDeleteVoiceMessage() {
-                messageComposerViewModel.handle(MessageComposerAction.EndRecordingVoiceMessage(isCancelled = true))
+                messageComposerViewModel.handle(MessageComposerAction.EndRecordingVoiceMessage(isCancelled = true, rootThreadEventId = getRootThreadEventId()))
                 updateRecordingUiState(RecordingUiState.Idle)
             }
 
@@ -1388,6 +1388,7 @@ class TimelineFragment @Inject constructor(
     }
 
     private fun updateJumpToReadMarkerViewVisibility() {
+        if(isThreadTimeLine()) return
         viewLifecycleOwner.lifecycleScope.launchWhenResumed {
             withState(roomDetailViewModel) {
                 val showJumpToUnreadBanner = when (it.unreadState) {
@@ -1606,28 +1607,28 @@ class TimelineFragment @Inject constructor(
 
     private fun renderSendMessageResult(sendMessageResult: MessageComposerViewEvents.SendMessageResult) {
         when (sendMessageResult) {
-            is MessageComposerViewEvents.SlashCommandLoading        -> {
+            is MessageComposerViewEvents.SlashCommandLoading               -> {
                 showLoading(null)
             }
-            is MessageComposerViewEvents.SlashCommandError          -> {
+            is MessageComposerViewEvents.SlashCommandError                 -> {
                 displayCommandError(getString(R.string.command_problem_with_parameters, sendMessageResult.command.command))
             }
-            is MessageComposerViewEvents.SlashCommandUnknown        -> {
+            is MessageComposerViewEvents.SlashCommandUnknown               -> {
                 displayCommandError(getString(R.string.unrecognized_command, sendMessageResult.command))
             }
-            is MessageComposerViewEvents.SlashCommandResultOk       -> {
+            is MessageComposerViewEvents.SlashCommandResultOk              -> {
                 dismissLoadingDialog()
                 views.composerLayout.setTextIfDifferent("")
                 sendMessageResult.messageRes?.let { showSnackWithMessage(getString(it)) }
             }
-            is MessageComposerViewEvents.SlashCommandResultError    -> {
+            is MessageComposerViewEvents.SlashCommandResultError           -> {
                 dismissLoadingDialog()
                 displayCommandError(errorFormatter.toHumanReadable(sendMessageResult.throwable))
             }
-            is MessageComposerViewEvents.SlashCommandNotImplemented -> {
+            is MessageComposerViewEvents.SlashCommandNotImplemented        -> {
                 displayCommandError(getString(R.string.not_implemented))
             }
-            is TextComposerViewEvents.SlashCommandNotSupportedInThreads -> {
+            is MessageComposerViewEvents.SlashCommandNotSupportedInThreads -> {
                 displayCommandError(getString(R.string.command_not_supported_in_threads, sendMessageResult.command))
             }
         } // .exhaustive
@@ -2145,14 +2146,14 @@ class TimelineFragment @Inject constructor(
                 }
             }
             is EventSharedAction.ReplyInThread              -> {
-                if (!views.voiceMessageRecorderView.isActive()) {
+                if (withState(messageComposerViewModel) { it.isVoiceMessageIdle }) {
                     navigateToThreadTimeline(action.eventId)
                 } else {
                     requireActivity().toast(R.string.error_voice_message_cannot_reply_or_edit)
                 }
             }
             is EventSharedAction.ViewInRoom                 -> {
-                if (!views.voiceMessageRecorderView.isActive()) {
+                if (withState(messageComposerViewModel) { it.isVoiceMessageIdle }) {
                     handleViewInRoomAction()
                 } else {
                     requireActivity().toast(R.string.error_voice_message_cannot_reply_or_edit)
@@ -2386,7 +2387,7 @@ class TimelineFragment @Inject constructor(
             AttachmentTypeSelectorView.Type.AUDIO   -> attachmentsHelper.selectAudio(attachmentAudioActivityResultLauncher)
             AttachmentTypeSelectorView.Type.CONTACT -> attachmentsHelper.selectContact(attachmentContactActivityResultLauncher)
             AttachmentTypeSelectorView.Type.STICKER -> roomDetailViewModel.handle(RoomDetailAction.SelectStickerAttachment)
-            AttachmentTypeSelectorView.Type.POLL    -> navigator.openCreatePoll(requireContext(), roomDetailArgs.roomId)
+            AttachmentTypeSelectorView.Type.POLL    -> navigator.openCreatePoll(requireContext(), timelineArgs.roomId)
         }.exhaustive
     }
 
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 690f127cbd6..25c8b172069 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
@@ -35,7 +35,7 @@ sealed class MessageComposerAction : VectorViewModelAction {
     data class InitializeVoiceRecorder(val attachmentData: ContentAttachmentData) : MessageComposerAction()
     data class OnVoiceRecordingUiStateChanged(val uiState: VoiceMessageRecorderView.RecordingUiState) : MessageComposerAction()
     object StartRecordingVoiceMessage : MessageComposerAction()
-    data class EndRecordingVoiceMessage(val isCancelled: Boolean) : MessageComposerAction()
+    data class EndRecordingVoiceMessage(val isCancelled: Boolean,val rootThreadEventId: String?) : MessageComposerAction()
     object PauseRecordingVoiceMessage : MessageComposerAction()
     data class PlayOrPauseVoicePlayback(val eventId: String, val messageAudioContent: MessageAudioContent) : MessageComposerAction()
     object PlayOrPauseRecordingPlayback : MessageComposerAction()
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 b02677cecdc..4755bddffe3 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
@@ -93,7 +93,7 @@ class MessageComposerViewModel @AssistedInject constructor(
             is MessageComposerAction.OnTextChanged                  -> handleOnTextChanged(action)
             is MessageComposerAction.OnVoiceRecordingUiStateChanged -> handleOnVoiceRecordingUiStateChanged(action)
             is MessageComposerAction.StartRecordingVoiceMessage     -> handleStartRecordingVoiceMessage()
-            is MessageComposerAction.EndRecordingVoiceMessage       -> handleEndRecordingVoiceMessage(action.isCancelled)
+            is MessageComposerAction.EndRecordingVoiceMessage       -> handleEndRecordingVoiceMessage(action.isCancelled, action.rootThreadEventId)
             is MessageComposerAction.PlayOrPauseVoicePlayback       -> handlePlayOrPauseVoicePlayback(action)
             MessageComposerAction.PauseRecordingVoiceMessage        -> handlePauseRecordingVoiceMessage()
             MessageComposerAction.PlayOrPauseRecordingPlayback      -> handlePlayOrPauseRecordingPlayback()
@@ -188,7 +188,7 @@ class MessageComposerViewModel @AssistedInject constructor(
                             _viewEvents.post(MessageComposerViewEvents.SlashCommandUnknown(slashCommandResult.slashCommand))
                         }
                         is ParsedCommand.ErrorCommandNotSupportedInThreads -> {
-                            _viewEvents.post(TextComposerViewEvents.SlashCommandNotSupportedInThreads(slashCommandResult.slashCommand))
+                            _viewEvents.post(MessageComposerViewEvents.SlashCommandNotSupportedInThreads(slashCommandResult.slashCommand))
                         }
                         is ParsedCommand.SendPlainText                     -> {
                             // Send the text message to the room, without markdown
@@ -491,7 +491,7 @@ class MessageComposerViewModel @AssistedInject constructor(
                                 eventReplied = timelineEvent)
                     } ?: room.replyToMessage(timelineEvent, action.text.toString(), action.autoMarkdown)
 
-                    _viewEvents.post(TextComposerViewEvents.MessageSent)
+                    _viewEvents.post(MessageComposerViewEvents.MessageSent)
                     popDraft()
                 }
                 is SendMode.Voice   -> {
@@ -774,14 +774,18 @@ class MessageComposerViewModel @AssistedInject constructor(
         }
     }
 
-    private fun handleEndRecordingVoiceMessage(isCancelled: Boolean) {
+    private fun handleEndRecordingVoiceMessage(isCancelled: Boolean, rootThreadEventId: String? = null) {
         voiceMessageHelper.stopPlayback()
         if (isCancelled) {
             voiceMessageHelper.deleteRecording()
         } else {
             voiceMessageHelper.stopRecording(convertForSending = true)?.let { audioType ->
                 if (audioType.duration > 1000) {
-                    room.sendMedia(audioType.toContentAttachmentData(isVoiceMessage = true), false, emptySet())
+                    room.sendMedia(
+                            attachment = audioType.toContentAttachmentData(isVoiceMessage = true),
+                            compressBeforeSending = false,
+                            roomIds = emptySet(),
+                            rootThreadEventId = rootThreadEventId)
                 } else {
                     voiceMessageHelper.deleteRecording()
                 }
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 1368adc3c0c..fe615d8c01f 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
@@ -60,6 +60,8 @@ 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.isSticker
 import org.matrix.android.sdk.flow.flow
 import org.matrix.android.sdk.flow.unwrap
 
@@ -442,14 +444,14 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
      * Determine whether or not the Reply In Thread bottom sheet setting will be visible
      * to the user
      */
-    // TODO handle reply in thread for images etc
     private fun canReplyInThread(event: TimelineEvent,
                                  messageContent: MessageContent?,
                                  actionPermissions: ActionPermissions): Boolean {
         // Only event of type EventType.MESSAGE are supported for the moment
         if (!BuildConfig.THREADING_ENABLED) return false
         if (initialState.isFromThreadTimeline) return false
-        if (event.root.getClearType() != EventType.MESSAGE) return false
+        if (event.root.getClearType() != EventType.MESSAGE &&
+                !event.isSticker() && !event.isPoll()) return false
         if (!actionPermissions.canSendMessage) return false
         return when (messageContent?.msgType) {
             MessageType.MSGTYPE_TEXT,
@@ -458,8 +460,10 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
             MessageType.MSGTYPE_IMAGE,
             MessageType.MSGTYPE_VIDEO,
             MessageType.MSGTYPE_AUDIO,
-            MessageType.MSGTYPE_FILE -> true
-            else                     -> false
+            MessageType.MSGTYPE_FILE,
+            MessageType.MSGTYPE_POLL_START,
+            MessageType.MSGTYPE_STICKER_LOCAL -> true
+            else                              -> false
         }
     }
 
@@ -468,12 +472,13 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
      * a thread timeline
      */
     private fun canViewInRoom(event: TimelineEvent,
-                                 messageContent: MessageContent?,
-                                 actionPermissions: ActionPermissions): Boolean {
+                              messageContent: MessageContent?,
+                              actionPermissions: ActionPermissions): Boolean {
         // Only event of type EventType.MESSAGE are supported for the moment
         if (!BuildConfig.THREADING_ENABLED) return false
-        if (!initialState.isFromThreadTimeline) return  false
-        if (event.root.getClearType() != EventType.MESSAGE) return false
+        if (!initialState.isFromThreadTimeline) return false
+        if (event.root.getClearType() != EventType.MESSAGE &&
+                !event.isSticker() && !event.isPoll()) return false
         if (!actionPermissions.canSendMessage) return false
 
         return when (messageContent?.msgType) {
@@ -483,12 +488,13 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
             MessageType.MSGTYPE_IMAGE,
             MessageType.MSGTYPE_VIDEO,
             MessageType.MSGTYPE_AUDIO,
-            MessageType.MSGTYPE_FILE -> event.root.threadDetails?.isRootThread ?: false
-            else                     -> false
+            MessageType.MSGTYPE_FILE,
+            MessageType.MSGTYPE_POLL_START,
+            MessageType.MSGTYPE_STICKER_LOCAL -> event.root.threadDetails?.isRootThread ?: false
+            else                              -> false
         }
     }
 
-
     private fun canQuote(event: TimelineEvent, messageContent: MessageContent?, actionPermissions: ActionPermissions): Boolean {
         // Only event of type EventType.MESSAGE are supported for the moment
         if (event.root.getClearType() != EventType.MESSAGE) return false
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
index a2c38030eb4..82aa7094de1 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
@@ -226,6 +226,18 @@ class MessageItemFactory @Inject constructor(
             )
         }
 
+        return PollItem_()
+                .attributes(attributes)
+                .eventId(informationData.eventId)
+                .pollQuestion(pollContent.pollCreationInfo?.question?.question ?: "")
+                .pollSent(isPollSent)
+                .totalVotesText(totalVotesText)
+                .optionViewStates(optionViewStates)
+                .highlighted(highlight)
+                .leftGuideline(avatarSizeProvider.leftGuideline)
+                .callback(callback)
+    }
+
     private fun buildAudioMessageItem(messageContent: MessageAudioContent,
                                       @Suppress("UNUSED_PARAMETER")
                                       informationData: MessageInformationData,
diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt
index fb1a6006c45..ecbea4cdaf6 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt
@@ -19,15 +19,10 @@ package im.vector.app.features.home.room.threads
 import android.content.Context
 import android.content.Intent
 import android.os.Bundle
-import android.view.View
-import android.view.ViewGroup
-import androidx.core.view.ViewCompat
-import androidx.core.view.children
 import androidx.fragment.app.FragmentTransaction
 import com.google.android.material.appbar.MaterialToolbar
+import dagger.hilt.android.AndroidEntryPoint
 import im.vector.app.R
-import im.vector.app.core.di.ScreenComponent
-import im.vector.app.core.extensions.addFragment
 import im.vector.app.core.extensions.addFragmentToBackstack
 import im.vector.app.core.extensions.replaceFragment
 import im.vector.app.core.platform.ToolbarConfigurable
@@ -42,6 +37,7 @@ import im.vector.app.features.home.room.threads.list.views.ThreadListFragment
 import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
 import javax.inject.Inject
 
+@AndroidEntryPoint
 class ThreadsActivity : VectorBaseActivity(), ToolbarConfigurable {
 
     @Inject
@@ -56,10 +52,6 @@ class ThreadsActivity : VectorBaseActivity(), ToolbarCon
 
     override fun getCoordinatorLayout() = views.coordinatorLayout
 
-    override fun injectWith(injector: ScreenComponent) {
-        injector.inject(this)
-    }
-
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         initFragment()
@@ -83,14 +75,14 @@ class ThreadsActivity : VectorBaseActivity(), ToolbarCon
 
     private fun initThreadListFragment(threadListArgs: ThreadListArgs) {
         replaceFragment(
-                R.id.threadsActivityFragmentContainer,
+                views.threadsActivityFragmentContainer,
                 ThreadListFragment::class.java,
                 threadListArgs)
     }
 
     private fun initThreadTimelineFragment(threadTimelineArgs: ThreadTimelineArgs) =
             replaceFragment(
-                    R.id.threadsActivityFragmentContainer,
+                    views.threadsActivityFragmentContainer,
                     TimelineFragment::class.java,
                     TimelineArgs(
                             roomId = threadTimelineArgs.roomId,
@@ -113,7 +105,7 @@ class ThreadsActivity : VectorBaseActivity(), ToolbarCon
             it.setCustomAnimations(R.anim.animation_slide_in_right, R.anim.animation_slide_out_left, R.anim.animation_slide_in_left, R.anim.animation_slide_out_right)
         }
         addFragmentToBackstack(
-                frameId = R.id.threadsActivityFragmentContainer,
+                container = views.threadsActivityFragmentContainer,
                 fragmentClass = TimelineFragment::class.java,
                 params = TimelineArgs(
                         roomId = timelineEvent.roomId,
diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListBottomSheet.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListBottomSheet.kt
index a4f40a820ad..bd62f658970 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListBottomSheet.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListBottomSheet.kt
@@ -39,7 +39,7 @@ class ThreadListBottomSheet : VectorBaseBottomSheetDialogFragment
                     val room = roomId?.let { session?.getRoom(it) }
-                    // Root thread will be opened in timeline
-//                    if(room?.getTimeLineEvent(eventId)?.root?.threadDetails?.isRootThread == true){
-//                        room.getTimeLineEvent(eventId)?.root?.eventId
-//                    }else{
                     room?.getTimeLineEvent(eventId)?.root?.getRootThreadEventId()
-//                    }
-
-                }
-                // MERGE FROM DEVELOP CONFLICT A.K.
-//                openRoom(
-//                        navigationInterceptor,
-//                        context = context,
-//                        roomId = roomId,
-//                        permalinkData = permalinkData,
-//                        rawLink = rawLink,
-//                        buildTask = buildTask
-//                )
-                if (navigationInterceptor?.navToRoom(roomId, permalinkData.eventId, rawLink,rootThreadEventId) != true) {
-                    openRoom(
-                            context = context,
-                            roomId = roomId,
-                            permalinkData = permalinkData,
-                            rawLink = rawLink,
-                            buildTask = buildTask,
-                            rootThreadEventId = rootThreadEventId
-                    )
                 }
+                openRoom(
+                        navigationInterceptor,
+                        context = context,
+                        roomId = roomId,
+                        permalinkData = permalinkData,
+                        rawLink = rawLink,
+                        buildTask = buildTask,
+                        rootThreadEventId = rootThreadEventId
+                )
                 true
             }
             is PermalinkData.GroupLink           -> {
@@ -170,14 +155,13 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti
      * Open room either joined, or not
      */
     private fun openRoom(
-            // A.K. conflict
-//            navigationInterceptor: NavigationInterceptor?,
+            navigationInterceptor: NavigationInterceptor?,
             context: Context,
             roomId: String?,
             permalinkData: PermalinkData.RoomLink,
             rawLink: Uri,
             buildTask: Boolean,
-            rootThreadEventId: String? =null
+            rootThreadEventId: String? = null
     ) {
         val session = activeSessionHolder.getSafeActiveSession() ?: return
         if (roomId == null) {
@@ -194,13 +178,7 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti
             membership?.isActive().orFalse() -> {
                 if (!isSpace && membership == Membership.JOIN) {
                     // If it's a room you're in, let's just open it, you can tap back if needed
-                    // A.K. Conflict
-//                    navigationInterceptor.openJoinedRoomScreen(buildTask, roomId, eventId, rawLink, context)
-                    rootThreadEventId?.let {
-                        val threadTimelineArgs = ThreadTimelineArgs(roomId, displayName = roomSummary.displayName, roomSummary.avatarUrl, it)
-                        navigator.openThread(context, threadTimelineArgs, eventId)
-                    } ?: navigator.openRoom(context, roomId, eventId, buildTask)
-
+                    navigationInterceptor.openJoinedRoomScreen(buildTask, roomId, eventId, rawLink, context, rootThreadEventId, roomSummary)
                 } else {
                     // maybe open space preview navigator.openSpacePreview(context, roomId)? if already joined?
                     navigator.openMatrixToBottomSheet(context, rawLink.toString())
@@ -213,9 +191,19 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti
         }
     }
 
-    private fun NavigationInterceptor?.openJoinedRoomScreen(buildTask: Boolean, roomId: String, eventId: String?, rawLink: Uri, context: Context) {
-        if (this?.navToRoom(roomId, eventId, rawLink) != true) {
-            navigator.openRoom(context, roomId, eventId, buildTask)
+    private fun NavigationInterceptor?.openJoinedRoomScreen(buildTask: Boolean,
+                                                            roomId: String,
+                                                            eventId: String?,
+                                                            rawLink: Uri,
+                                                            context: Context,
+                                                            rootThreadEventId: String?,
+                                                            roomSummary: RoomSummary
+    ) {
+        if (this?.navToRoom(roomId, eventId, rawLink, rootThreadEventId) != true) {
+            rootThreadEventId?.let {
+                val threadTimelineArgs = ThreadTimelineArgs(roomId, displayName = roomSummary.displayName, roomSummary.avatarUrl, it)
+                navigator.openThread(context, threadTimelineArgs, eventId)
+            } ?: navigator.openRoom(context, roomId, eventId, buildTask)
         }
     }
 
diff --git a/vector/src/main/java/im/vector/app/features/room/RequireActiveMembershipViewState.kt b/vector/src/main/java/im/vector/app/features/room/RequireActiveMembershipViewState.kt
index 7a5363100fc..7e4af1b7d56 100644
--- a/vector/src/main/java/im/vector/app/features/room/RequireActiveMembershipViewState.kt
+++ b/vector/src/main/java/im/vector/app/features/room/RequireActiveMembershipViewState.kt
@@ -17,7 +17,7 @@
 package im.vector.app.features.room
 
 import com.airbnb.mvrx.MavericksState
-import im.vector.app.features.home.room.detail.RoomDetailArgs
+import im.vector.app.features.home.room.detail.arguments.TimelineArgs
 import im.vector.app.features.roommemberprofile.RoomMemberProfileArgs
 import im.vector.app.features.roomprofile.RoomProfileArgs
 
@@ -25,7 +25,7 @@ data class RequireActiveMembershipViewState(
         val roomId: String? = null
 ) : MavericksState {
 
-    constructor(args: RoomDetailArgs) : this(roomId = args.roomId)
+    constructor(args: TimelineArgs) : this(roomId = args.roomId)
 
     constructor(args: RoomProfileArgs) : this(roomId = args.roomId)
 

From 3acdccb339e62e800ab995522a99527ac5417a9e Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Wed, 15 Dec 2021 16:31:58 +0200
Subject: [PATCH 030/581] Disable polls from within threads but allow users to
 vote if the poll is a root thread message

---
 .../org/matrix/android/sdk/api/session/events/model/Event.kt  | 3 ++-
 .../sdk/internal/database/RealmSessionStoreMigration.kt       | 2 +-
 .../vector/app/features/home/room/detail/TimelineFragment.kt  | 4 +++-
 3 files changed, 6 insertions(+), 3 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
index 3e0aed87381..5f9a15de02e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
@@ -205,6 +205,7 @@ data class Event(
             isAudioMessage()       -> "sent an audio file."
             isImageMessage()       -> "sent an image."
             isVideoMessage()       -> "sent a video."
+            isPoll()               -> "created a poll."
             else                   -> text
         }
     }
@@ -337,7 +338,7 @@ fun Event.isAttachmentMessage(): Boolean {
             }
 }
 
-fun Event.isPoll(): Boolean = getClearType() == EventType.POLL_START ||  getClearType() == EventType.POLL_END
+fun Event.isPoll(): Boolean = getClearType() == EventType.POLL_START || getClearType() == EventType.POLL_END
 
 fun Event.isSticker(): Boolean = getClearType() == EventType.STICKER
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
index 5b9a74fe9ed..e7968b87865 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
@@ -55,7 +55,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
 ) : RealmMigration {
 
     companion object {
-        const val SESSION_STORE_SCHEMA_VERSION = 19L
+        const val SESSION_STORE_SCHEMA_VERSION = 20L
     }
 
     /**
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 69189131fc9..c89e4cb9803 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
@@ -1448,7 +1448,9 @@ class TimelineFragment @Inject constructor(
             override fun onAddAttachment() {
                 if (!::attachmentTypeSelector.isInitialized) {
                     attachmentTypeSelector = AttachmentTypeSelectorView(vectorBaseActivity, vectorBaseActivity.layoutInflater, this@TimelineFragment)
-                    attachmentTypeSelector.setAttachmentVisibility(AttachmentTypeSelectorView.Type.POLL, vectorPreferences.labsEnablePolls())
+                    attachmentTypeSelector.setAttachmentVisibility(
+                            AttachmentTypeSelectorView.Type.POLL,
+                            vectorPreferences.labsEnablePolls() && !isThreadTimeLine())
                 }
                 attachmentTypeSelector.show(views.composerLayout.views.attachmentButton, keyboardStateUtils.isKeyboardShowing)
             }

From bc6e89b5030a6084126757580dfc204a58596f53 Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Wed, 15 Dec 2021 18:49:22 +0200
Subject: [PATCH 031/581] Disable user typing from thread timeline

---
 .../im/vector/app/features/home/room/detail/TimelineFragment.kt  | 1 +
 1 file changed, 1 insertion(+)

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 c89e4cb9803..0bff2da0826 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
@@ -1488,6 +1488,7 @@ class TimelineFragment @Inject constructor(
     }
 
     private fun observerUserTyping() {
+        if(isThreadTimeLine()) return
         views.composerLayout.views.composerEditText.textChanges()
                 .skipInitialValue()
                 .debounce(300)

From 638d56c7075bc8da6ebcbb8851feb1bf10d74107 Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Thu, 16 Dec 2021 17:10:29 +0200
Subject: [PATCH 032/581] Fix update from develop/prod to threads

---
 .../sdk/internal/database/RealmSessionStoreMigration.kt  | 4 ++++
 .../internal/session/room/timeline/DefaultTimeline.kt    | 9 +++++----
 2 files changed, 9 insertions(+), 4 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
index e7968b87865..04c48a18894 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
@@ -25,6 +25,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent
 import org.matrix.android.sdk.api.session.room.model.VersioningState
 import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
 import org.matrix.android.sdk.api.session.room.model.tag.RoomTag
+import org.matrix.android.sdk.api.session.threads.ThreadNotificationState
 import org.matrix.android.sdk.internal.database.model.ChunkEntityFields
 import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntityFields
 import org.matrix.android.sdk.internal.database.model.EditAggregatedSummaryEntityFields
@@ -403,6 +404,9 @@ internal class RealmSessionStoreMigration @Inject constructor(
                 ?.addField(EventEntityFields.ROOT_THREAD_EVENT_ID, String::class.java, FieldAttribute.INDEXED)
                 ?.addField(EventEntityFields.NUMBER_OF_THREADS, Int::class.java)
                 ?.addField(EventEntityFields.THREAD_NOTIFICATION_STATE_STR, String::class.java)
+                ?.transform {
+                    it.setString(EventEntityFields.THREAD_NOTIFICATION_STATE_STR, ThreadNotificationState.NO_NEW_MESSAGE.name)
+                }
                 ?.addRealmObjectField(EventEntityFields.THREAD_SUMMARY_LATEST_MESSAGE.`$`, eventEntity)
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
index 00388dc5fa8..78ebe821294 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
@@ -602,8 +602,11 @@ internal class DefaultTimeline(
             nextDisplayIndex = offsetIndex + 1
         }
 
-        // Prerequisite to in order for the ThreadsAwarenessHandler to work properly
-        fetchRootThreadEventsIfNeeded(offsetResults)
+
+        if(!BuildConfig.THREADING_ENABLED) {
+            // Prerequisite to in order for the ThreadsAwarenessHandler to work properly
+            fetchRootThreadEventsIfNeeded(offsetResults)
+        }
 
         offsetResults.forEach { eventEntity ->
 
@@ -640,9 +643,7 @@ internal class DefaultTimeline(
                 }.map {
                     EventMapper.map(it)
                 }
-        if(!BuildConfig.THREADING_ENABLED) {
             threadsAwarenessHandler.fetchRootThreadEventsIfNeeded(eventEntityList)
-        }
     }
 
     private fun buildTimelineEvent(eventEntity: TimelineEventEntity): TimelineEvent {

From 9a5934dd33ce66353e63d34206d16ec9a6cdba7f Mon Sep 17 00:00:00 2001
From: ganfra 
Date: Thu, 16 Dec 2021 20:57:05 +0100
Subject: [PATCH 033/581] Bubbles: R&D try to find the best way to provide
 dynamic layout

---
 .../res/values/stylable_message_bubble.xml    |   8 +
 .../timeline/factory/MessageItemFactory.kt    |   5 +
 .../timeline/helper/AvatarSizeProvider.kt     |   5 +-
 .../helper/MessageInformationDataFactory.kt   |  13 +-
 .../detail/timeline/item/AbsMessageItem.kt    |  40 ++---
 .../timeline/item/MessageInformationData.kt   |   5 +-
 .../detail/timeline/item/MessageTextItem.kt   |   2 +-
 .../detail/timeline/view/MessageBubbleView.kt |  58 ++++++
 .../drawable/bg_timeline_incoming_message.xml |   5 +
 .../drawable/bg_timeline_outgoing_message.xml |   5 +
 .../res/layout/item_timeline_event_base.xml   |  60 +------
 ...em_timeline_event_bubble_incoming_base.xml |   8 +
 ...em_timeline_event_bubble_outgoing_base.xml |   8 +
 .../item_timeline_event_text_message_stub.xml |   2 +-
 ...em_timeline_event_view_stubs_container.xml |  61 +++++++
 .../main/res/layout/view_message_bubble.xml   | 170 ++++++++++++++++++
 16 files changed, 366 insertions(+), 89 deletions(-)
 create mode 100644 library/ui-styles/src/main/res/values/stylable_message_bubble.xml
 create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
 create mode 100644 vector/src/main/res/drawable/bg_timeline_incoming_message.xml
 create mode 100644 vector/src/main/res/drawable/bg_timeline_outgoing_message.xml
 create mode 100644 vector/src/main/res/layout/item_timeline_event_bubble_incoming_base.xml
 create mode 100644 vector/src/main/res/layout/item_timeline_event_bubble_outgoing_base.xml
 create mode 100644 vector/src/main/res/layout/item_timeline_event_view_stubs_container.xml
 create mode 100644 vector/src/main/res/layout/view_message_bubble.xml

diff --git a/library/ui-styles/src/main/res/values/stylable_message_bubble.xml b/library/ui-styles/src/main/res/values/stylable_message_bubble.xml
new file mode 100644
index 00000000000..8da2df33e7e
--- /dev/null
+++ b/library/ui-styles/src/main/res/values/stylable_message_bubble.xml
@@ -0,0 +1,8 @@
+
+
+
+    
+        
+    
+
+
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 98deaaf9c37..05973be8bef 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
@@ -482,6 +482,11 @@ class MessageItemFactory @Inject constructor(
             } else {
                 message(linkifiedBody)
             }
+            if (informationData.sentByMe) {
+                layout(R.layout.item_timeline_event_bubble_outgoing_base)
+            } else {
+                layout(R.layout.item_timeline_event_bubble_incoming_base)
+            }
         }
                 .useBigFont(linkifiedBody.length <= MAX_NUMBER_OF_EMOJI_FOR_BIG_FONT * 2 && containsOnlyEmojis(linkifiedBody.toString()))
                 .canUseTextFuture(canUseTextFuture)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AvatarSizeProvider.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AvatarSizeProvider.kt
index 5fc5deb4071..c2662e7ae6b 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AvatarSizeProvider.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AvatarSizeProvider.kt
@@ -21,10 +21,10 @@ import javax.inject.Inject
 
 class AvatarSizeProvider @Inject constructor(private val dimensionConverter: DimensionConverter) {
 
-    private val avatarStyle = AvatarStyle.SMALL
+    private val avatarStyle = AvatarStyle.X_SMALL
 
     val leftGuideline: Int by lazy {
-        dimensionConverter.dpToPx(avatarStyle.avatarSizeDP + 8)
+        dimensionConverter.dpToPx(avatarStyle.avatarSizeDP)
     }
 
     val avatarSize: Int by lazy {
@@ -37,6 +37,7 @@ class AvatarSizeProvider @Inject constructor(private val dimensionConverter: Dim
             BIG(50),
             MEDIUM(40),
             SMALL(30),
+            X_SMALL(24),
             NONE(0)
         }
     }
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 6385494fe1b..eef174c0f38 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
@@ -57,6 +57,8 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
         val event = params.event
         val nextDisplayableEvent = params.nextDisplayableEvent
         val eventId = event.eventId
+        val isSentByMe = event.root.senderId == session.myUserId
+        val roomSummary = params.partialState.roomSummary
 
         val date = event.root.localDateTime()
         val nextDate = nextDisplayableEvent?.root?.localDateTime()
@@ -65,20 +67,18 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
                 ?: false
 
         val showInformation =
-                addDaySeparator ||
+                (addDaySeparator ||
                         event.senderInfo.avatarUrl != nextDisplayableEvent?.senderInfo?.avatarUrl ||
                         event.senderInfo.disambiguatedDisplayName != nextDisplayableEvent?.senderInfo?.disambiguatedDisplayName ||
                         nextDisplayableEvent.root.getClearType() !in listOf(EventType.MESSAGE, EventType.STICKER, EventType.ENCRYPTED) ||
                         isNextMessageReceivedMoreThanOneHourAgo ||
                         isTileTypeMessage(nextDisplayableEvent) ||
-                        nextDisplayableEvent.isEdition()
+                        nextDisplayableEvent.isEdition() ) && !isSentByMe
 
         val time = dateFormatter.format(event.root.originServerTs, DateFormatKind.MESSAGE_SIMPLE)
-        val roomSummary = params.partialState.roomSummary
         val e2eDecoration = getE2EDecoration(roomSummary, event)
 
         // SendState Decoration
-        val isSentByMe = event.root.senderId == session.myUserId
         val sendStateDecoration = if (isSentByMe) {
             getSendStateDecoration(
                     event = event,
@@ -97,8 +97,9 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
                 ageLocalTS = event.root.ageLocalTs,
                 avatarUrl = event.senderInfo.avatarUrl,
                 memberName = event.senderInfo.disambiguatedDisplayName,
-                showInformation = showInformation,
-                forceShowTimestamp = vectorPreferences.alwaysShowTimeStamps(),
+                showAvatar = showInformation,
+                showDisplayName = showInformation,
+                showTimestamp = true,
                 orderedReactionList = event.annotations?.reactionsSummary
                         // ?.filter { isSingleEmoji(it.key) }
                         ?.map {
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 b53495fdaf7..a964af6f730 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
@@ -22,7 +22,6 @@ import android.widget.ImageView
 import android.widget.ProgressBar
 import android.widget.TextView
 import androidx.annotation.IdRes
-import androidx.core.view.isInvisible
 import androidx.core.view.isVisible
 import com.airbnb.epoxy.EpoxyAttribute
 import im.vector.app.R
@@ -63,38 +62,37 @@ abstract class AbsMessageItem : AbsBaseMessageItem
 
     override fun bind(holder: H) {
         super.bind(holder)
-        if (attributes.informationData.showInformation) {
+        if (attributes.informationData.showAvatar) {
             holder.avatarImageView.layoutParams = holder.avatarImageView.layoutParams?.apply {
                 height = attributes.avatarSize
                 width = attributes.avatarSize
             }
-            holder.avatarImageView.visibility = View.VISIBLE
+            attributes.avatarRenderer.render(attributes.informationData.matrixItem, holder.avatarImageView)
+            holder.avatarImageView.setOnLongClickListener(attributes.itemLongClickListener)
+            holder.avatarImageView.isVisible = true
             holder.avatarImageView.onClick(_avatarClickListener)
-            holder.memberNameView.visibility = View.VISIBLE
-            holder.memberNameView.onClick(_memberNameClickListener)
-            holder.timeView.visibility = View.VISIBLE
-            holder.timeView.text = attributes.informationData.time
+        } else {
+            holder.avatarImageView.setOnClickListener(null)
+            holder.avatarImageView.setOnLongClickListener(null)
+            holder.avatarImageView.isVisible = false
+        }
+        if (attributes.informationData.showDisplayName) {
+            holder.memberNameView.isVisible = true
             holder.memberNameView.text = attributes.informationData.memberName
             holder.memberNameView.setTextColor(attributes.getMemberNameColor())
-            attributes.avatarRenderer.render(attributes.informationData.matrixItem, holder.avatarImageView)
-            holder.avatarImageView.setOnLongClickListener(attributes.itemLongClickListener)
+            holder.memberNameView.onClick(_memberNameClickListener)
             holder.memberNameView.setOnLongClickListener(attributes.itemLongClickListener)
         } else {
-            holder.avatarImageView.setOnClickListener(null)
             holder.memberNameView.setOnClickListener(null)
-            holder.avatarImageView.visibility = View.GONE
-            if (attributes.informationData.forceShowTimestamp) {
-                holder.memberNameView.isInvisible = true
-                holder.timeView.isVisible = true
-                holder.timeView.text = attributes.informationData.time
-            } else {
-                holder.memberNameView.isVisible = false
-                holder.timeView.isVisible = false
-            }
-            holder.avatarImageView.setOnLongClickListener(null)
             holder.memberNameView.setOnLongClickListener(null)
+            holder.memberNameView.isVisible = false
+        }
+        if (attributes.informationData.showTimestamp) {
+            holder.timeView.isVisible = true
+            holder.timeView.text = attributes.informationData.time
+        } else {
+            holder.timeView.isVisible = false
         }
-
         // Render send state indicator
         holder.sendStateImageView.render(attributes.informationData.sendStateDecoration)
         holder.eventSendingIndicator.isVisible = attributes.informationData.sendStateDecoration == SendStateDecoration.SENDING_MEDIA
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt
index 08aa3015380..94c6b32b0aa 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt
@@ -31,8 +31,9 @@ data class MessageInformationData(
         val ageLocalTS: Long?,
         val avatarUrl: String?,
         val memberName: CharSequence? = null,
-        val showInformation: Boolean = true,
-        val forceShowTimestamp: Boolean = false,
+        val showAvatar: Boolean,
+        val showDisplayName: Boolean,
+        val showTimestamp: Boolean,
         /*List of reactions (emoji,count,isSelected)*/
         val orderedReactionList: List? = null,
         val pollResponseAggregatedSummary: PollResponseData? = null,
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt
index 747183bce67..d950b7226a9 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt
@@ -113,7 +113,7 @@ abstract class MessageTextItem : AbsMessageItem() {
         previewUrlRetriever?.removeListener(attributes.informationData.eventId, previewUrlViewUpdater)
     }
 
-    override fun getViewType() = STUB_ID
+    override fun getViewType() = STUB_ID + layout
 
     class Holder : AbsMessageItem.Holder(STUB_ID) {
         val messageView by bind(R.id.messageTextView)
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
new file mode 100644
index 00000000000..66fb5cd9981
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageBubbleView.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.home.room.detail.timeline.view
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import android.widget.RelativeLayout
+import androidx.core.content.withStyledAttributes
+import im.vector.app.R
+
+class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null,
+                                                  defStyleAttr: Int = 0) : RelativeLayout(context, attrs, defStyleAttr) {
+
+    var incoming: Boolean = false
+
+    init {
+        inflate(context, R.layout.view_message_bubble, this)
+        context.withStyledAttributes(attrs, R.styleable.MessageBubble) {
+            incoming = getBoolean(R.styleable.MessageBubble_incoming_style, false)
+        }
+    }
+
+    override fun onFinishInflate() {
+        super.onFinishInflate()
+        val currentLayoutDirection = layoutDirection
+        if (incoming) {
+            findViewById(R.id.informationBottom).layoutDirection = currentLayoutDirection
+            findViewById(R.id.bubbleWrapper).layoutDirection = currentLayoutDirection
+            findViewById(R.id.bubbleView).layoutDirection = currentLayoutDirection
+            findViewById(R.id.bubbleView).setBackgroundResource(R.drawable.bg_timeline_incoming_message)
+        } else {
+            val oppositeLayoutDirection = if (currentLayoutDirection == View.LAYOUT_DIRECTION_LTR) {
+                View.LAYOUT_DIRECTION_RTL
+            } else {
+                View.LAYOUT_DIRECTION_LTR
+            }
+            findViewById(R.id.informationBottom).layoutDirection = oppositeLayoutDirection
+            findViewById(R.id.bubbleWrapper).layoutDirection = oppositeLayoutDirection
+            findViewById(R.id.bubbleView).layoutDirection = currentLayoutDirection
+            findViewById(R.id.bubbleView).setBackgroundResource(R.drawable.bg_timeline_outgoing_message)
+        }
+    }
+}
diff --git a/vector/src/main/res/drawable/bg_timeline_incoming_message.xml b/vector/src/main/res/drawable/bg_timeline_incoming_message.xml
new file mode 100644
index 00000000000..ad4a28a7706
--- /dev/null
+++ b/vector/src/main/res/drawable/bg_timeline_incoming_message.xml
@@ -0,0 +1,5 @@
+
+
+    
+    
+
\ No newline at end of file
diff --git a/vector/src/main/res/drawable/bg_timeline_outgoing_message.xml b/vector/src/main/res/drawable/bg_timeline_outgoing_message.xml
new file mode 100644
index 00000000000..1ab85d23528
--- /dev/null
+++ b/vector/src/main/res/drawable/bg_timeline_outgoing_message.xml
@@ -0,0 +1,5 @@
+
+
+    
+    
+
\ No newline at end of file
diff --git a/vector/src/main/res/layout/item_timeline_event_base.xml b/vector/src/main/res/layout/item_timeline_event_base.xml
index 3316a651d73..debf578519c 100644
--- a/vector/src/main/res/layout/item_timeline_event_base.xml
+++ b/vector/src/main/res/layout/item_timeline_event_base.xml
@@ -76,68 +76,16 @@
         android:visibility="gone"
         tools:visibility="visible" />
 
-    
-
-        
-
-        
-
-        
-
-        
-
-        
-
-        
-
-        
-
-        
-
-    
+        android:addStatesFromChildren="true"/>
 
     
+
diff --git a/vector/src/main/res/layout/item_timeline_event_bubble_outgoing_base.xml b/vector/src/main/res/layout/item_timeline_event_bubble_outgoing_base.xml
new file mode 100644
index 00000000000..42bf1b5f7a1
--- /dev/null
+++ b/vector/src/main/res/layout/item_timeline_event_bubble_outgoing_base.xml
@@ -0,0 +1,8 @@
+
+
diff --git a/vector/src/main/res/layout/item_timeline_event_text_message_stub.xml b/vector/src/main/res/layout/item_timeline_event_text_message_stub.xml
index 3f9feb93afd..4bb612fedfe 100644
--- a/vector/src/main/res/layout/item_timeline_event_text_message_stub.xml
+++ b/vector/src/main/res/layout/item_timeline_event_text_message_stub.xml
@@ -23,6 +23,6 @@
         android:layout_marginBottom="4dp"
         android:foreground="?attr/selectableItemBackground"
         android:visibility="gone"
-        tools:visibility="visible" />
+        tools:visibility="gone" />
 
 
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
new file mode 100644
index 00000000000..04613c665f4
--- /dev/null
+++ b/vector/src/main/res/layout/item_timeline_event_view_stubs_container.xml
@@ -0,0 +1,61 @@
+
+
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+
+
+
diff --git a/vector/src/main/res/layout/view_message_bubble.xml b/vector/src/main/res/layout/view_message_bubble.xml
new file mode 100644
index 00000000000..36c5592f071
--- /dev/null
+++ b/vector/src/main/res/layout/view_message_bubble.xml
@@ -0,0 +1,170 @@
+
+
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+        
+
+            
+
+            
+
+        
+    
+
+    
+
+    
+
+    
+
+        
+
+            
+            
+
+        
+    
+
+
\ No newline at end of file

From a187e0ec33d1d461e76db977900a8920be967759 Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Thu, 16 Dec 2021 22:03:42 +0200
Subject: [PATCH 034/581] Enhance thread awareness to recognise the type of
 messages that are not able to be send as a reply such as images, videos,
 audios, stickers

---
 build.gradle                                  |  2 +-
 .../handler/room/ThreadsAwarenessHandler.kt   | 26 ++++++++++++++++---
 2 files changed, 24 insertions(+), 4 deletions(-)

diff --git a/build.gradle b/build.gradle
index f057d234e51..433ca8bc9d9 100644
--- a/build.gradle
+++ b/build.gradle
@@ -154,7 +154,7 @@ project(":diff-match-patch") {
 
 // Global configurations across all modules
 ext {
-    isThreadingEnabled = true
+    isThreadingEnabled = false
 }
 
 //project(":matrix-sdk-android") {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt
index 767a967522e..30876c21da1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt
@@ -27,6 +27,7 @@ import org.matrix.android.sdk.api.session.events.model.toModel
 import org.matrix.android.sdk.api.session.room.model.message.MessageFormat
 import org.matrix.android.sdk.api.session.room.model.message.MessageRelationContent
 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.send.SendState
 import org.matrix.android.sdk.api.session.sync.model.SyncResponse
 import org.matrix.android.sdk.api.util.JsonDict
@@ -43,6 +44,7 @@ import org.matrix.android.sdk.internal.session.permalinks.PermalinkFactory
 import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
 import org.matrix.android.sdk.internal.session.room.timeline.GetEventTask
 import org.matrix.android.sdk.internal.util.awaitTransaction
+import timber.log.Timber
 import javax.inject.Inject
 
 /**
@@ -84,7 +86,7 @@ internal class ThreadsAwarenessHandler @Inject constructor(
         if (eventList.isNullOrEmpty()) return
 
         val threadsToFetch = emptyMap().toMutableMap()
-        Realm.getInstance(monarchy.realmConfiguration).use {  realm ->
+        Realm.getInstance(monarchy.realmConfiguration).use { realm ->
             eventList.asSequence()
                     .filter {
                         isThreadEvent(it) && it.roomId != null
@@ -176,11 +178,29 @@ internal class ThreadsAwarenessHandler @Inject constructor(
         if (!isThreadEvent(event)) return null
         val rootThreadEventId = getRootThreadEventId(event) ?: return null
         val payload = decryptedResult?.toMutableMap() ?: return null
-        val body = getValueFromPayload(payload, "body") ?: return null
+        var body = getValueFromPayload(payload, "body") ?: return null
         val msgType = getValueFromPayload(payload, "msgtype") ?: return null
         val rootThreadEvent = getEventFromDB(realm, rootThreadEventId) ?: return null
         val rootThreadEventSenderId = rootThreadEvent.senderId ?: return null
 
+        // Check the event type
+        when (msgType) {
+            MessageType.MSGTYPE_STICKER_LOCAL -> {
+                body = "sent a sticker from within a thread"
+            }
+            MessageType.MSGTYPE_FILE     -> {
+                body = "sent a file from within a thread"
+            }
+            MessageType.MSGTYPE_VIDEO    -> {
+                body = "Sent a video from within a thread"
+            }
+            MessageType.MSGTYPE_IMAGE    -> {
+                body = "sent an image from within a thread"
+            }
+            MessageType.MSGTYPE_AUDIO    -> {
+                body = "sent an audio file from within a thread"
+            }
+        }
         decryptIfNeeded(rootThreadEvent, roomId)
 
         val rootThreadEventBody = getValueFromPayload(rootThreadEvent.mxDecryptionResult?.payload?.toMutableMap(), "body")
@@ -197,7 +217,7 @@ internal class ThreadsAwarenessHandler @Inject constructor(
                 body)
 
         val messageTextContent = MessageTextContent(
-                msgType = msgType,
+                msgType = "m.text",
                 format = MessageFormat.FORMAT_MATRIX_HTML,
                 body = body,
                 formattedBody = replyFormatted

From a60f6e996a7b0991ea171df1995b4e2402b4dcdc Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Fri, 17 Dec 2021 00:46:47 +0200
Subject: [PATCH 035/581] Enhance thread awareness to support stickers

---
 .../handler/room/ThreadsAwarenessHandler.kt     | 17 ++++++++++++-----
 1 file changed, 12 insertions(+), 5 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt
index 30876c21da1..eb03875cb1c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt
@@ -21,6 +21,7 @@ import io.realm.Realm
 import org.matrix.android.sdk.api.session.crypto.CryptoService
 import org.matrix.android.sdk.api.session.crypto.MXCryptoError
 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.events.model.toModel
@@ -179,7 +180,13 @@ internal class ThreadsAwarenessHandler @Inject constructor(
         val rootThreadEventId = getRootThreadEventId(event) ?: return null
         val payload = decryptedResult?.toMutableMap() ?: return null
         var body = getValueFromPayload(payload, "body") ?: return null
-        val msgType = getValueFromPayload(payload, "msgtype") ?: return null
+        val msgType = getValueFromPayload(payload, "msgtype") ?: run {
+            if (payload["type"]?.toString() == EventType.STICKER) {
+                MessageType.MSGTYPE_STICKER_LOCAL
+            } else {
+                return null
+            }
+        }
         val rootThreadEvent = getEventFromDB(realm, rootThreadEventId) ?: return null
         val rootThreadEventSenderId = rootThreadEvent.senderId ?: return null
 
@@ -188,16 +195,16 @@ internal class ThreadsAwarenessHandler @Inject constructor(
             MessageType.MSGTYPE_STICKER_LOCAL -> {
                 body = "sent a sticker from within a thread"
             }
-            MessageType.MSGTYPE_FILE     -> {
+            MessageType.MSGTYPE_FILE          -> {
                 body = "sent a file from within a thread"
             }
-            MessageType.MSGTYPE_VIDEO    -> {
+            MessageType.MSGTYPE_VIDEO         -> {
                 body = "Sent a video from within a thread"
             }
-            MessageType.MSGTYPE_IMAGE    -> {
+            MessageType.MSGTYPE_IMAGE         -> {
                 body = "sent an image from within a thread"
             }
-            MessageType.MSGTYPE_AUDIO    -> {
+            MessageType.MSGTYPE_AUDIO         -> {
                 body = "sent an audio file from within a thread"
             }
         }

From 57234651067ef54e775ca23b395511f9b3cc7b4e Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Fri, 17 Dec 2021 01:23:09 +0200
Subject: [PATCH 036/581] Fix local notification badge number

---
 build.gradle                                                    | 2 +-
 .../android/sdk/internal/database/helper/ThreadEventsHelper.kt  | 2 ++
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/build.gradle b/build.gradle
index 433ca8bc9d9..f057d234e51 100644
--- a/build.gradle
+++ b/build.gradle
@@ -154,7 +154,7 @@ project(":diff-match-patch") {
 
 // Global configurations across all modules
 ext {
-    isThreadingEnabled = false
+    isThreadingEnabled = true
 }
 
 //project(":matrix-sdk-android") {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt
index 43f6c54c7ec..3610d5871b0 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt
@@ -113,9 +113,11 @@ internal fun TimelineEventEntity.Companion.findAllLocalThreadNotificationsForRoo
         TimelineEventEntity
                 .whereRoomId(realm, roomId = roomId)
                 .equalTo(TimelineEventEntityFields.ROOT.IS_ROOT_THREAD, true)
+                .beginGroup()
                 .equalTo(TimelineEventEntityFields.ROOT.THREAD_NOTIFICATION_STATE_STR, ThreadNotificationState.NEW_MESSAGE.name)
                 .or()
                 .equalTo(TimelineEventEntityFields.ROOT.THREAD_NOTIFICATION_STATE_STR, ThreadNotificationState.NEW_HIGHLIGHTED_MESSAGE.name)
+                .endGroup()
 
 /**
  * Returns whether or not the given user is participating in a current thread

From cc7e3ea78cfe68d9676519374e96072e2741e8d8 Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Fri, 17 Dec 2021 01:25:50 +0200
Subject: [PATCH 037/581] Improve init thread query

---
 .../sdk/internal/session/room/timeline/DefaultTimeline.kt       | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
index 78ebe821294..1fe05ced68c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
@@ -173,9 +173,11 @@ internal class DefaultTimeline(
                     TimelineEventEntity
                             .whereRoomId(realm, roomId = roomId)
                             .equalTo(TimelineEventEntityFields.CHUNK.IS_LAST_FORWARD, true)
+                            .beginGroup()
                             .equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, it)
                             .or()
                             .equalTo(TimelineEventEntityFields.ROOT.EVENT_ID, it)
+                            .endGroup()
                             .sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)
                             .findAll()
 

From ed48eb38c9dbda84a9929ef67be4127e7a38b9f5 Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Tue, 21 Dec 2021 13:23:17 +0200
Subject: [PATCH 038/581] Apply ktlinFormat

---
 .../java/org/matrix/android/sdk/flow/FlowRoom.kt   |  1 -
 .../api/session/room/timeline/TimelineService.kt   |  1 -
 .../api/session/threads/ThreadNotificationState.kt |  1 -
 .../database/RealmSessionStoreMigration.kt         |  2 --
 .../internal/database/helper/ThreadEventsHelper.kt |  7 +------
 .../sdk/internal/database/model/ChunkEntity.kt     |  1 -
 .../sdk/internal/database/model/EventEntity.kt     |  1 -
 .../internal/database/query/ChunkEntityQueries.kt  |  4 ----
 .../internal/database/query/EventEntityQueries.kt  |  2 --
 .../database/query/TimelineEventEntityQueries.kt   |  2 --
 .../room/relation/DefaultRelationService.kt        |  1 -
 .../session/room/send/DefaultSendService.kt        |  1 -
 .../session/room/send/LocalEchoEventFactory.kt     |  1 -
 .../session/room/timeline/DefaultTimeline.kt       |  7 +------
 .../session/room/timeline/PaginationTask.kt        |  1 -
 .../room/timeline/TimelineEventDecryptor.kt        |  1 -
 .../room/timeline/TokenChunkEventPersistor.kt      |  1 -
 .../internal/session/sync/SyncResponseHandler.kt   |  2 +-
 .../session/sync/handler/room/RoomSyncHandler.kt   |  2 +-
 .../sync/handler/room/ThreadsAwarenessHandler.kt   |  1 -
 .../command/AutocompleteCommandPresenter.kt        |  6 ++----
 .../vector/app/features/command/CommandParser.kt   |  4 ++--
 .../home/room/detail/RoomDetailActivity.kt         |  2 +-
 .../home/room/detail/RoomDetailViewState.kt        |  1 -
 .../features/home/room/detail/TimelineFragment.kt  | 12 ++----------
 .../room/detail/composer/MessageComposerAction.kt  |  2 +-
 .../detail/composer/MessageComposerViewModel.kt    | 14 ++++++--------
 .../detail/timeline/TimelineEventController.kt     |  4 ++--
 .../helper/TimelineEventVisibilityHelper.kt        |  2 +-
 .../detail/timeline/item/AbsBaseMessageItem.kt     |  1 +
 .../room/detail/timeline/item/AbsMessageItem.kt    |  4 ++--
 .../features/home/room/threads/ThreadsActivity.kt  |  3 +--
 .../room/threads/list/model/ThreadListModel.kt     |  5 ++---
 .../threads/list/viewmodel/ThreadListViewState.kt  |  2 +-
 .../im/vector/app/features/navigation/Navigator.kt |  1 -
 35 files changed, 28 insertions(+), 75 deletions(-)

diff --git a/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowRoom.kt b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowRoom.kt
index cdb3bdf9c2d..46acdc123b5 100644
--- a/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowRoom.kt
+++ b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowRoom.kt
@@ -21,7 +21,6 @@ import kotlinx.coroutines.flow.Flow
 import org.matrix.android.sdk.api.query.QueryStringValue
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.room.Room
-import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
 import org.matrix.android.sdk.api.session.room.members.RoomMemberQueryParams
 import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary
 import org.matrix.android.sdk.api.session.room.model.ReadReceipt
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineService.kt
index 4ac4aab4e6f..bf483539186 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineService.kt
@@ -91,5 +91,4 @@ interface TimelineService {
      * @param rootThreadEventId the eventId of the current thread
      */
     suspend fun markThreadAsRead(rootThreadEventId: String)
-
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadNotificationState.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadNotificationState.kt
index 093e4a7627e..58cc3a0706d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadNotificationState.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadNotificationState.kt
@@ -30,5 +30,4 @@ enum class ThreadNotificationState {
     // The is at least one new message that should bi highlighted
     // ex. "Hello @aris.kotsomitopoulos"
     NEW_HIGHLIGHTED_MESSAGE;
-
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
index 04c48a18894..88a7b7abb3d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
@@ -26,7 +26,6 @@ import org.matrix.android.sdk.api.session.room.model.VersioningState
 import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
 import org.matrix.android.sdk.api.session.room.model.tag.RoomTag
 import org.matrix.android.sdk.api.session.threads.ThreadNotificationState
-import org.matrix.android.sdk.internal.database.model.ChunkEntityFields
 import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntityFields
 import org.matrix.android.sdk.internal.database.model.EditAggregatedSummaryEntityFields
 import org.matrix.android.sdk.internal.database.model.EditionOfEventFields
@@ -89,7 +88,6 @@ internal class RealmSessionStoreMigration @Inject constructor(
         if (oldVersion <= 17) migrateTo18(realm)
         if (oldVersion <= 18) migrateTo19(realm)
         if (oldVersion <= 19) migrateTo20(realm)
-
     }
 
     private fun migrateTo1(realm: DynamicRealm) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt
index 3610d5871b0..557bb4bdf1f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt
@@ -37,13 +37,10 @@ import org.matrix.android.sdk.internal.database.query.whereRoomId
  * of threads included. If there is no root thread event no action is done
  */
 internal fun Map.updateThreadSummaryIfNeeded(roomId: String, realm: Realm, currentUserId: String) {
-
     if (!BuildConfig.THREADING_ENABLED) return
 
     for ((rootThreadEventId, eventEntity) in this) {
-
         eventEntity.findAllThreadsForRootEventId(eventEntity.realm, rootThreadEventId).let {
-
             if (it.isNullOrEmpty()) return@let
 
             val latestMessage = it.firstOrNull()
@@ -55,7 +52,6 @@ internal fun Map.updateThreadSummaryIfNeeded(roomId: String
                     threadsCounted = it.size,
                     latestMessageTimelineEventEntity = latestMessage
             )
-
         }
     }
 
@@ -175,7 +171,6 @@ internal fun isUserMentioned(currentUserId: String, timelineEventEntity: Timelin
  * immediately so we should not display wrong notifications
  */
 internal fun updateNotificationsNew(roomId: String, realm: Realm, currentUserId: String) {
-
     val readReceipt = findMyReadReceipt(realm, roomId, currentUserId) ?: return
 
     val readReceiptChunk = ChunkEntity
@@ -190,7 +185,7 @@ internal fun updateNotificationsNew(roomId: String, realm: Realm, currentUserId:
 
     val readReceiptChunkPosition = readReceiptChunkTimelineEvents.indexOfFirst { it.eventId == readReceipt }
 
-    if(readReceiptChunkPosition == -1) return
+    if (readReceiptChunkPosition == -1) return
 
     if (readReceiptChunkPosition < readReceiptChunkTimelineEvents.lastIndex) {
         // If the read receipt is found inside the chunk
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt
index 0b9a1ee8cce..68533a3c199 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt
@@ -44,7 +44,6 @@ internal open class ChunkEntity(@Index var prevToken: String? = null,
     val room: RealmResults? = null
 
     companion object
-
 }
 
 internal fun ChunkEntity.deleteOnCascade(deleteStateEvents: Boolean, canDeleteRoot: Boolean) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt
index 3a7611fc36f..f4e12bf3edc 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt
@@ -95,5 +95,4 @@ internal open class EventEntity(@Index var eventId: String = "",
     }
 
     fun isThread(): Boolean = rootThreadEventId != null
-
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ChunkEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ChunkEntityQueries.kt
index 2261d9786ac..156a8dd767c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ChunkEntityQueries.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ChunkEntityQueries.kt
@@ -46,9 +46,6 @@ internal fun ChunkEntity.Companion.findLastForwardChunkOfRoom(realm: Realm, room
             .findFirst()
 }
 
-
-
-
 internal fun ChunkEntity.Companion.findAllIncludingEvents(realm: Realm, eventIds: List): RealmResults {
     return realm.where()
             .`in`(ChunkEntityFields.TIMELINE_EVENTS.EVENT_ID, eventIds.toTypedArray())
@@ -59,7 +56,6 @@ internal fun ChunkEntity.Companion.findIncludingEvent(realm: Realm, eventId: Str
     return findAllIncludingEvents(realm, listOf(eventId)).firstOrNull()
 }
 
-
 internal fun ChunkEntity.Companion.create(
         realm: Realm,
         prevToken: String?,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt
index a439d6aae72..f7fa1037ba5 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt
@@ -49,13 +49,11 @@ internal fun EventEntity.Companion.where(realm: Realm, eventId: String): RealmQu
             .equalTo(EventEntityFields.EVENT_ID, eventId)
 }
 
-
 internal fun EventEntity.Companion.whereRoomId(realm: Realm, roomId: String): RealmQuery {
     return realm.where()
             .equalTo(EventEntityFields.ROOM_ID, roomId)
 }
 
-
 internal fun EventEntity.Companion.where(realm: Realm, eventIds: List): RealmQuery {
     return realm.where()
             .`in`(EventEntityFields.EVENT_ID, eventIds.toTypedArray())
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt
index 9ce59904b4d..63f41ebf2cf 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt
@@ -25,11 +25,9 @@ import io.realm.kotlin.where
 import org.matrix.android.sdk.api.session.room.send.SendState
 import org.matrix.android.sdk.api.session.room.timeline.TimelineEventFilters
 import org.matrix.android.sdk.internal.database.model.ChunkEntity
-import org.matrix.android.sdk.internal.database.model.ChunkEntityFields
 import org.matrix.android.sdk.internal.database.model.RoomEntity
 import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
 import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
-import timber.log.Timber
 
 internal fun TimelineEventEntity.Companion.where(realm: Realm, roomId: String, eventId: String): RealmQuery {
     return realm.where()
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 a82d6e8d190..d459e79a4a8 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
@@ -169,7 +169,6 @@ internal class DefaultRelationService @AssistedInject constructor(
             autoMarkdown: Boolean,
             formattedText: String?,
             eventReplied: TimelineEvent?): Cancelable? {
-
         val event = if (eventReplied != null) {
             eventFactory.createReplyTextEvent(
                     roomId = roomId,
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 e89ea778352..8fe799b1a1e 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
@@ -258,7 +258,6 @@ internal class DefaultSendService @AssistedInject constructor(
                            roomIds: Set,
                            rootThreadEventId: String?
     ): Cancelable {
-
         // Ensure that the event will not be send in a thread if we are a different flow.
         // Like sending files to multiple rooms
         val rootThreadId = if (roomIds.isNotEmpty()) null else 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 27e50e5e93f..1046bcee495 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
@@ -397,7 +397,6 @@ internal class LocalEchoEventFactory @Inject constructor(
             msgType: String,
             autoMarkdown: Boolean,
             formattedText: String?): Event {
-
         val content = formattedText?.let { TextContent(text, it) } ?: createTextContent(text, autoMarkdown)
         return createEvent(
                 roomId,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
index 1fe05ced68c..69e56a85d07 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
@@ -180,7 +180,6 @@ internal class DefaultTimeline(
                             .endGroup()
                             .sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)
                             .findAll()
-
                 } ?: buildEventQuery(realm).sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING).findAll()
 
                 timelineEvents.addChangeListener(eventsChangeListener)
@@ -332,7 +331,6 @@ internal class DefaultTimeline(
         val firstCacheEvent = results.firstOrNull()
         val chunkEntity = getLiveChunk()
 
-
         updateState(Timeline.Direction.FORWARDS) {
             it.copy(
                     hasMoreInCache = !builtEventsIdMap.containsKey(firstCacheEvent?.eventId),   // what is in DB
@@ -340,7 +338,6 @@ internal class DefaultTimeline(
             )
         }
         updateState(Timeline.Direction.BACKWARDS) {
-
             it.copy(
                     hasMoreInCache = !builtEventsIdMap.containsKey(lastCacheEvent?.eventId),
                     hasReachedEnd = if (isFromThreadTimeline) true else chunkEntity?.isLastBackward ?: false || lastCacheEvent?.root?.type == EventType.STATE_ROOM_CREATE
@@ -497,7 +494,6 @@ internal class DefaultTimeline(
      * This has to be called on TimelineThread as it accesses realm live results
      */
     private fun executePaginationTask(direction: Timeline.Direction, limit: Int) {
-
         val currentChunk = getLiveChunk()
         val token = if (direction == Timeline.Direction.BACKWARDS) currentChunk?.prevToken else currentChunk?.nextToken
         if (token == null) {
@@ -604,8 +600,7 @@ internal class DefaultTimeline(
             nextDisplayIndex = offsetIndex + 1
         }
 
-
-        if(!BuildConfig.THREADING_ENABLED) {
+        if (!BuildConfig.THREADING_ENABLED) {
             // Prerequisite to in order for the ThreadsAwarenessHandler to work properly
             fetchRootThreadEventsIfNeeded(offsetResults)
         }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/PaginationTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/PaginationTask.kt
index cb23061edad..8aeccb66c89 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/PaginationTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/PaginationTask.kt
@@ -21,7 +21,6 @@ import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.filter.FilterRepository
 import org.matrix.android.sdk.internal.session.room.RoomAPI
 import org.matrix.android.sdk.internal.task.Task
-import timber.log.Timber
 import javax.inject.Inject
 
 internal interface PaginationTask : Task {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt
index aa792f6b9b9..a4d48903adc 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt
@@ -115,7 +115,6 @@ internal class TimelineEventDecryptor @Inject constructor(
                         .findFirst()
 
                 eventEntity?.apply {
-
                     val decryptedPayload =
                             if (!BuildConfig.THREADING_ENABLED) {
                                 threadsAwarenessHandler.handleIfNeededDuringDecryption(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt
index 8331d3f5f46..b909b2feef0 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt
@@ -238,7 +238,6 @@ internal class TokenChunkEventPersistor @Inject constructor(
                 // This is a normal event or a root thread one
                 optimizedThreadSummaryMap[eventEntity.eventId] = eventEntity
             }
-
         }
 
         // Find all the chunks which contain at least one event from the list of eventIds
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt
index 5ac3eadb757..a3cfddd4728 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt
@@ -102,7 +102,7 @@ internal class SyncResponseHandler @Inject constructor(
         val aggregator = SyncResponsePostTreatmentAggregator()
 
         // Prerequisite for thread events handling in RoomSyncHandler
-        if(!BuildConfig.THREADING_ENABLED) {
+        if (!BuildConfig.THREADING_ENABLED) {
             threadsAwarenessHandler.fetchRootThreadEventsIfNeeded(syncResponse)
         }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt
index f354a98f808..83860717556 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt
@@ -376,7 +376,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
                 decryptIfNeeded(event, roomId)
             }
 
-            if(!BuildConfig.THREADING_ENABLED) {
+            if (!BuildConfig.THREADING_ENABLED) {
                 threadsAwarenessHandler.handleIfNeeded(
                         realm = realm,
                         roomId = roomId,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt
index eb03875cb1c..a4ebfabc5cd 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt
@@ -45,7 +45,6 @@ import org.matrix.android.sdk.internal.session.permalinks.PermalinkFactory
 import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
 import org.matrix.android.sdk.internal.session.room.timeline.GetEventTask
 import org.matrix.android.sdk.internal.util.awaitTransaction
-import timber.log.Timber
 import javax.inject.Inject
 
 /**
diff --git a/vector/src/main/java/im/vector/app/features/autocomplete/command/AutocompleteCommandPresenter.kt b/vector/src/main/java/im/vector/app/features/autocomplete/command/AutocompleteCommandPresenter.kt
index 7846ebab372..7afe3eaebc7 100644
--- a/vector/src/main/java/im/vector/app/features/autocomplete/command/AutocompleteCommandPresenter.kt
+++ b/vector/src/main/java/im/vector/app/features/autocomplete/command/AutocompleteCommandPresenter.kt
@@ -25,10 +25,7 @@ import im.vector.app.BuildConfig
 import im.vector.app.features.autocomplete.AutocompleteClickListener
 import im.vector.app.features.autocomplete.RecyclerViewPresenter
 import im.vector.app.features.command.Command
-import im.vector.app.features.home.room.detail.AutoCompleter
 import im.vector.app.features.settings.VectorPreferences
-import timber.log.Timber
-import javax.inject.Inject
 
 class AutocompleteCommandPresenter @AssistedInject constructor(
         @Assisted val isInThreadTimeline: Boolean,
@@ -62,8 +59,9 @@ class AutocompleteCommandPresenter @AssistedInject constructor(
                 .filter {
                     if (BuildConfig.THREADING_ENABLED && isInThreadTimeline) {
                         it.isThreadCommand
-                    } else
+                    } else {
                         true
+                    }
                 }
                 .filter {
                     if (query.isNullOrEmpty()) {
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 3eb01758f80..7ff22236827 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
@@ -64,14 +64,14 @@ object CommandParser {
 
             // If the command is not supported by threads return error
 
-            if(BuildConfig.THREADING_ENABLED && isInThreadTimeline){
+            if (BuildConfig.THREADING_ENABLED && isInThreadTimeline) {
                 val slashCommand = messageParts.first()
                 val notSupportedCommandsInThreads = Command.values().filter {
                     !it.isThreadCommand
                 }.map {
                     it.command
                 }
-                if(notSupportedCommandsInThreads.contains(slashCommand)){
+                if (notSupportedCommandsInThreads.contains(slashCommand)) {
                     return ParsedCommand.ErrorCommandNotSupportedInThreads(slashCommand)
                 }
             }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt
index 40af675e660..bc1e17f984e 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt
@@ -37,8 +37,8 @@ import im.vector.app.core.platform.ToolbarConfigurable
 import im.vector.app.core.platform.VectorBaseActivity
 import im.vector.app.databinding.ActivityRoomDetailBinding
 import im.vector.app.features.home.room.breadcrumbs.BreadcrumbsFragment
-import im.vector.app.features.home.room.detail.timeline.helper.VoiceMessagePlaybackTracker
 import im.vector.app.features.home.room.detail.arguments.TimelineArgs
+import im.vector.app.features.home.room.detail.timeline.helper.VoiceMessagePlaybackTracker
 import im.vector.app.features.matrixto.MatrixToBottomSheet
 import im.vector.app.features.navigation.Navigator
 import im.vector.app.features.room.RequireActiveMembershipAction
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 051c9b65001..f41ac504fcb 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
@@ -89,5 +89,4 @@ data class RoomDetailViewState(
     fun isDm() = asyncRoomSummary()?.isDirect == true
 
     fun isThreadTimeline() = rootThreadEventId != null
-
 }
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 0bff2da0826..7778294aa3d 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
@@ -63,8 +63,6 @@ import com.airbnb.epoxy.EpoxyModel
 import com.airbnb.epoxy.OnModelBuildFinishedListener
 import com.airbnb.epoxy.addGlidePreloader
 import com.airbnb.epoxy.glidePreloader
-import com.airbnb.mvrx.Mavericks
-import com.airbnb.mvrx.activityViewModel
 import com.airbnb.mvrx.args
 import com.airbnb.mvrx.fragmentViewModel
 import com.airbnb.mvrx.withState
@@ -136,7 +134,6 @@ import im.vector.app.features.command.Command
 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.UnreadMessagesSharedViewModel
 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.MessageComposerView
@@ -975,7 +972,6 @@ class TimelineFragment @Inject constructor(
     }
 
     override fun onPrepareOptionsMenu(menu: Menu) {
-
         menu.forEach {
             it.isVisible = roomDetailViewModel.isMenuItemVisible(it.itemId)
         }
@@ -1014,7 +1010,6 @@ class TimelineFragment @Inject constructor(
 
             // Handle custom threads badge notification
             updateMenuThreadNotificationBadge(menu, state)
-
         }
     }
 
@@ -1057,7 +1052,6 @@ class TimelineFragment @Inject constructor(
                     val permalink = session.permalinkService().createPermalink(timelineArgs.roomId, it)
                     copyToClipboard(requireContext(), permalink, false)
                     showSnackWithMessage(getString(R.string.copied_to_clipboard))
-
                 }
                 true
             }
@@ -1109,7 +1103,6 @@ class TimelineFragment @Inject constructor(
                 val int = RoomDetailActivity.newIntent(con, newRoom)
                 int.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
                 con.startActivity(int)
-
             }
         }
     }
@@ -1388,7 +1381,7 @@ class TimelineFragment @Inject constructor(
     }
 
     private fun updateJumpToReadMarkerViewVisibility() {
-        if(isThreadTimeLine()) return
+        if (isThreadTimeLine()) return
         viewLifecycleOwner.lifecycleScope.launchWhenResumed {
             withState(roomDetailViewModel) {
                 val showJumpToUnreadBanner = when (it.unreadState) {
@@ -1488,7 +1481,7 @@ class TimelineFragment @Inject constructor(
     }
 
     private fun observerUserTyping() {
-        if(isThreadTimeLine()) return
+        if (isThreadTimeLine()) return
         views.composerLayout.views.composerEditText.textChanges()
                 .skipInitialValue()
                 .debounce(300)
@@ -1774,7 +1767,6 @@ class TimelineFragment @Inject constructor(
                             if (roomId != timelineArgs.roomId) return false
                             // Navigation to same room
                             if (!isThreadTimeLine()) {
-
                                 if (rootThreadEventId != null) {
                                     // Thread link, so PermalinkHandler will handle the navigation
                                     return false
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 25c8b172069..10cef399426 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
@@ -35,7 +35,7 @@ sealed class MessageComposerAction : VectorViewModelAction {
     data class InitializeVoiceRecorder(val attachmentData: ContentAttachmentData) : MessageComposerAction()
     data class OnVoiceRecordingUiStateChanged(val uiState: VoiceMessageRecorderView.RecordingUiState) : MessageComposerAction()
     object StartRecordingVoiceMessage : MessageComposerAction()
-    data class EndRecordingVoiceMessage(val isCancelled: Boolean,val rootThreadEventId: String?) : MessageComposerAction()
+    data class EndRecordingVoiceMessage(val isCancelled: Boolean, val rootThreadEventId: String?) : MessageComposerAction()
     object PauseRecordingVoiceMessage : MessageComposerAction()
     data class PlayOrPauseVoicePlayback(val eventId: String, val messageAudioContent: MessageAudioContent) : MessageComposerAction()
     object PlayOrPauseRecordingPlayback : MessageComposerAction()
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 4755bddffe3..83c2938b452 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
@@ -30,7 +30,6 @@ import im.vector.app.features.attachments.toContentAttachmentData
 import im.vector.app.features.command.CommandParser
 import im.vector.app.features.command.ParsedCommand
 import im.vector.app.features.home.room.detail.ChatEffect
-import im.vector.app.features.home.room.detail.TimelineFragment
 import im.vector.app.features.home.room.detail.composer.rainbow.RainbowGenerator
 import im.vector.app.features.home.room.detail.composer.voice.VoiceMessageRecorderView
 import im.vector.app.features.home.room.detail.toMessageType
@@ -167,13 +166,14 @@ class MessageComposerViewModel @AssistedInject constructor(
                     when (val slashCommandResult = CommandParser.parseSplashCommand(action.text, state.isInThreadTimeline())) {
                         is ParsedCommand.ErrorNotACommand                  -> {
                             // Send the text message to the room
-                            if (state.rootThreadEventId != null)
+                            if (state.rootThreadEventId != null) {
                                 room.replyInThread(
                                         rootThreadEventId = state.rootThreadEventId,
                                         replyInThreadText = action.text.toString(),
                                         autoMarkdown = action.autoMarkdown)
-                            else
+                            } else {
                                 room.sendTextMessage(action.text, autoMarkdown = action.autoMarkdown)
+                            }
 
                             _viewEvents.post(MessageComposerViewEvents.MessageSent)
                             popDraft()
@@ -192,13 +192,14 @@ class MessageComposerViewModel @AssistedInject constructor(
                         }
                         is ParsedCommand.SendPlainText                     -> {
                             // Send the text message to the room, without markdown
-                            if (state.rootThreadEventId != null)
+                            if (state.rootThreadEventId != null) {
                                 room.replyInThread(
                                         rootThreadEventId = state.rootThreadEventId,
                                         replyInThreadText = action.text.toString(),
                                         autoMarkdown = false)
-                            else
+                            } else {
                                 room.sendTextMessage(slashCommandResult.message, autoMarkdown = false)
+                            }
                             _viewEvents.post(MessageComposerViewEvents.MessageSent)
                             popDraft()
                         }
@@ -258,7 +259,6 @@ class MessageComposerViewModel @AssistedInject constructor(
                             popDraft()
                         }
                         is ParsedCommand.SendRainbow                       -> {
-
                             val message = slashCommandResult.message.toString()
                             state.rootThreadEventId?.let {
                                 room.replyInThread(
@@ -283,7 +283,6 @@ class MessageComposerViewModel @AssistedInject constructor(
                             popDraft()
                         }
                         is ParsedCommand.SendSpoiler                       -> {
-
                             val text = "[${stringProvider.getString(R.string.spoiler)}](${slashCommandResult.message})"
                             val formattedText = "${slashCommandResult.message}"
                             state.rootThreadEventId?.let {
@@ -299,7 +298,6 @@ class MessageComposerViewModel @AssistedInject constructor(
                             popDraft()
                         }
                         is ParsedCommand.SendShrug                         -> {
-
                             sendPrefixedMessage("¯\\_(ツ)_/¯", slashCommandResult.message, state.rootThreadEventId)
                             _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk())
                             popDraft()
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt
index b091ea2fb70..20a3f34338f 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt
@@ -105,7 +105,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
                 rootThreadEventId = state.rootThreadEventId
         )
 
-        fun isFromThreadTimeline():Boolean = rootThreadEventId != null
+        fun isFromThreadTimeline(): Boolean = rootThreadEventId != null
     }
 
     interface Callback :
@@ -200,7 +200,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
                 // it's sent by the same user so we are sure we have up to date information.
                 val invalidatedSenderId: String? = currentSnapshot.getOrNull(position)?.senderInfo?.userId
                 val prevDisplayableEventIndex = currentSnapshot.subList(0, position).indexOfLast {
-                    timelineEventVisibilityHelper.shouldShowEvent(it, partialState.highlightedEventId, partialState.isFromThreadTimeline()  )
+                    timelineEventVisibilityHelper.shouldShowEvent(it, partialState.highlightedEventId, partialState.isFromThreadTimeline())
                 }
                 if (prevDisplayableEventIndex != -1 && currentSnapshot[prevDisplayableEventIndex].senderInfo.userId == invalidatedSenderId) {
                     modelCache[prevDisplayableEventIndex] = null
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt
index 7206fa22807..7efefc52096 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt
@@ -128,7 +128,7 @@ class TimelineEventVisibilityHelper @Inject constructor(private val userPreferen
             return true
         }
 
-        if(BuildConfig.THREADING_ENABLED && !isFromThreadTimeline && root.isThread() && root.getRootThreadEventId() != null){
+        if (BuildConfig.THREADING_ENABLED && !isFromThreadTimeline && root.isThread() && root.getRootThreadEventId() != null) {
             return true
         }
 
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt
index a3e808c7bb5..080b7662580 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt
@@ -127,6 +127,7 @@ abstract class AbsBaseMessageItem : BaseEventItem
         val messageColorProvider: MessageColorProvider
         val itemLongClickListener: View.OnLongClickListener?
         val itemClickListener: ClickListener?
+
         //        val memberClickListener: ClickListener?
         val reactionPillCallback: TimelineEventController.ReactionPillCallback?
 
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 a9455a0e3c6..f75df309161 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
@@ -124,10 +124,10 @@ abstract class AbsMessageItem : AbsBaseMessageItem
                 val displayName = threadDetails.threadSummarySenderInfo?.displayName
                 val avatarUrl = threadDetails.threadSummarySenderInfo?.avatarUrl
                 attributes.avatarRenderer.render(MatrixItem.UserItem(userId, displayName, avatarUrl), holder.threadSummaryAvatarImageView)
-                updateHighlightedMessageHeight(holder,true)
+                updateHighlightedMessageHeight(holder, true)
             } ?: run {
                 holder.threadSummaryConstraintLayout.isVisible = false
-                updateHighlightedMessageHeight(holder,false)
+                updateHighlightedMessageHeight(holder, false)
             }
         }
     }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt
index ecbea4cdaf6..84d270a2c22 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt
@@ -29,8 +29,8 @@ import im.vector.app.core.platform.ToolbarConfigurable
 import im.vector.app.core.platform.VectorBaseActivity
 import im.vector.app.databinding.ActivityThreadsBinding
 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.TimelineFragment
+import im.vector.app.features.home.room.detail.arguments.TimelineArgs
 import im.vector.app.features.home.room.threads.arguments.ThreadListArgs
 import im.vector.app.features.home.room.threads.arguments.ThreadTimelineArgs
 import im.vector.app.features.home.room.threads.list.views.ThreadListFragment
@@ -152,7 +152,6 @@ class ThreadsActivity : VectorBaseActivity(), ToolbarCon
                 putExtra(THREAD_TIMELINE_ARGS, threadTimelineArgs)
                 putExtra(THREAD_EVENT_ID_TO_NAVIGATE, eventIdToNavigate)
                 putExtra(THREAD_LIST_ARGS, threadListArgs)
-
             }
         }
     }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadListModel.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadListModel.kt
index 286f0279150..b8909527197 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadListModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadListModel.kt
@@ -77,15 +77,14 @@ abstract class ThreadListModel : VectorEpoxyModel() {
     }
 
     private fun renderNotificationState(holder: Holder) {
-
         when (threadNotificationState) {
             ThreadNotificationState.NEW_MESSAGE             -> {
                 holder.unreadImageView.isVisible = true
-                holder.unreadImageView.setColorFilter(ContextCompat.getColor(holder.view.context, R.color.palette_gray_200));
+                holder.unreadImageView.setColorFilter(ContextCompat.getColor(holder.view.context, R.color.palette_gray_200))
             }
             ThreadNotificationState.NEW_HIGHLIGHTED_MESSAGE -> {
                 holder.unreadImageView.isVisible = true
-                holder.unreadImageView.setColorFilter(ContextCompat.getColor(holder.view.context, R.color.palette_vermilion));
+                holder.unreadImageView.setColorFilter(ContextCompat.getColor(holder.view.context, R.color.palette_vermilion))
             }
             else                                            -> {
                 holder.unreadImageView.isVisible = false
diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewState.kt
index 53d2a45344c..2a70a5be1e6 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewState.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListViewState.kt
@@ -26,7 +26,7 @@ data class ThreadListViewState(
         val rootThreadEventList: Async> = Uninitialized,
         val shouldFilterThreads: Boolean = false,
         val roomId: String
-) : MavericksState{
+) : MavericksState {
 
     constructor(args: ThreadListArgs) : this(roomId = args.roomId)
 }
diff --git a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt
index b6da13ca33f..5254bf4838a 100644
--- a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt
+++ b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt
@@ -154,5 +154,4 @@ interface Navigator {
     fun openThread(context: Context, threadTimelineArgs: ThreadTimelineArgs, eventIdToNavigate: String? = null)
 
     fun openThreadList(context: Context, threadTimelineArgs: ThreadTimelineArgs)
-
 }

From 5a7d12a9a5896a7aa18d6c81dc1e865aad37faf5 Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Tue, 21 Dec 2021 20:04:50 +0200
Subject: [PATCH 039/581] Enhance RoomEventFilter with MSC3440

---
 .../android/sdk/api/session/events/model/Event.kt     |  4 ++--
 .../sdk/api/session/events/model/RelationType.kt      |  4 ++--
 .../sdk/internal/session/filter/RoomEventFilter.kt    | 10 ++++++++++
 .../android/sdk/internal/session/room/RoomAPI.kt      | 11 +++++++++++
 .../session/room/send/LocalEchoEventFactory.kt        | 10 +++++-----
 .../sdk/internal/session/room/send/TextContent.kt     |  2 +-
 .../sync/handler/room/ThreadsAwarenessHandler.kt      |  2 +-
 .../features/home/room/detail/RoomDetailViewModel.kt  |  2 +-
 8 files changed, 33 insertions(+), 12 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
index 5f9a15de02e..7372a83873c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
@@ -367,9 +367,9 @@ fun Event.isReply(): Boolean {
     return getRelationContent()?.inReplyTo?.eventId != null
 }
 
-fun Event.isThread(): Boolean = getRelationContentForType(RelationType.THREAD)?.eventId != null
+fun Event.isThread(): Boolean = getRelationContentForType(RelationType.IO_THREAD)?.eventId != null
 
-fun Event.getRootThreadEventId(): String? = getRelationContentForType(RelationType.THREAD)?.eventId
+fun Event.getRootThreadEventId(): String? = getRelationContentForType(RelationType.IO_THREAD)?.eventId
 
 fun Event.isEdition(): Boolean {
     return getRelationContentForType(RelationType.REPLACE)?.eventId != null
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/RelationType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/RelationType.kt
index 65462587666..18bb946462a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/RelationType.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/RelationType.kt
@@ -29,8 +29,8 @@ object RelationType {
     const val REFERENCE = "m.reference"
 
     /** Lets you define an event which is a reply to an existing event.*/
-//    const val THREAD = "m.thread"
-    const val THREAD = "io.element.thread"
+    const val THREAD = "m.thread"
+    const val IO_THREAD = "io.element.thread"
 
     /** Lets you define an event which adds a response to an existing event.*/
     const val RESPONSE = "org.matrix.response"
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/RoomEventFilter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/RoomEventFilter.kt
index 7047d38260e..f4983229670 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/RoomEventFilter.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/RoomEventFilter.kt
@@ -48,6 +48,16 @@ data class RoomEventFilter(
          * a wildcard to match any sequence of characters.
          */
         @Json(name = "types") val types: List? = null,
+        /**
+         * A list of relation types which must be exist pointing to the event being filtered.
+         * If this list is absent then no filtering is done on relation types.
+         */
+        @Json(name = "relation_types") val relationTypes: List? = null,
+        /**
+         *  A list of senders of relations which must exist pointing to the event being filtered.
+         *  If this list is absent then no filtering is done on relation types.
+         */
+        @Json(name = "relation_senders") val relationSenders: List? = null,
         /**
          * A list of room IDs to include. If this list is absent then all rooms are included.
          */
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt
index efc5166a0cf..0017cdd9176 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt
@@ -376,4 +376,15 @@ internal interface RoomAPI {
     @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "im.nheko.summary/rooms/{roomIdOrAlias}/summary")
     suspend fun getRoomSummary(@Path("roomIdOrAlias") roomidOrAlias: String,
                                @Query("via") viaServers: List?): RoomStrippedState
+
+    // TODO add doc
+    /**
+     */
+    @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "rooms/{roomId}/messages")
+    suspend fun getRoomThreadMessages(@Path("roomId") roomId: String,
+                                      @Query("from") from: String,
+                                      @Query("dir") dir: String,
+                                      @Query("limit") limit: Int,
+                                      @Query("filter") filter: String?
+    ): PaginationResponse
 }
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 1046bcee495..aad1d422a6a 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
@@ -290,7 +290,7 @@ internal class LocalEchoEventFactory @Inject constructor(
                         size = attachment.size
                 ),
                 url = attachment.queryUri.toString(),
-                relatesTo = rootThreadEventId?.let { RelationDefaultContent(RelationType.THREAD, it) }
+                relatesTo = rootThreadEventId?.let { RelationDefaultContent(RelationType.IO_THREAD, it) }
         )
         return createMessageEvent(roomId, content)
     }
@@ -327,7 +327,7 @@ internal class LocalEchoEventFactory @Inject constructor(
                         thumbnailInfo = thumbnailInfo
                 ),
                 url = attachment.queryUri.toString(),
-                relatesTo = rootThreadEventId?.let { RelationDefaultContent(RelationType.THREAD, it) }
+                relatesTo = rootThreadEventId?.let { RelationDefaultContent(RelationType.IO_THREAD, it) }
         )
         return createMessageEvent(roomId, content)
     }
@@ -351,7 +351,7 @@ internal class LocalEchoEventFactory @Inject constructor(
                         waveform = waveformSanitizer.sanitize(attachment.waveform)
                 ),
                 voiceMessageIndicator = if (!isVoiceMessage) null else emptyMap(),
-                relatesTo = rootThreadEventId?.let { RelationDefaultContent(RelationType.THREAD, it) }
+                relatesTo = rootThreadEventId?.let { RelationDefaultContent(RelationType.IO_THREAD, it) }
         )
         return createMessageEvent(roomId, content)
     }
@@ -365,7 +365,7 @@ internal class LocalEchoEventFactory @Inject constructor(
                         size = attachment.size
                 ),
                 url = attachment.queryUri.toString(),
-                relatesTo = rootThreadEventId?.let { RelationDefaultContent(RelationType.THREAD, it) }
+                relatesTo = rootThreadEventId?.let { RelationDefaultContent(RelationType.IO_THREAD, it) }
         )
         return createMessageEvent(roomId, content)
     }
@@ -454,7 +454,7 @@ internal class LocalEchoEventFactory @Inject constructor(
     private fun generateReplyRelationContent(eventId: String, rootThreadEventId: String? = null): RelationDefaultContent =
             rootThreadEventId?.let {
                 RelationDefaultContent(
-                        type = RelationType.THREAD,
+                        type = RelationType.IO_THREAD,
                         eventId = it,
                         inReplyTo = ReplyToContent(eventId))
             } ?: RelationDefaultContent(null, null, ReplyToContent(eventId))
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/TextContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/TextContent.kt
index d3e0189f4b9..0bf05615999 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/TextContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/TextContent.kt
@@ -48,7 +48,7 @@ fun TextContent.toThreadTextContent(rootThreadEventId: String, msgType: String =
             msgType = msgType,
             format = MessageFormat.FORMAT_MATRIX_HTML.takeIf { formattedText != null },
             body = text,
-            relatesTo = RelationDefaultContent(RelationType.THREAD, rootThreadEventId),
+            relatesTo = RelationDefaultContent(RelationType.IO_THREAD, rootThreadEventId),
             formattedBody = formattedText
     )
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt
index a4ebfabc5cd..24854b601fc 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt
@@ -272,7 +272,7 @@ internal class ThreadsAwarenessHandler @Inject constructor(
      * @param event
      */
     private fun isThreadEvent(event: Event): Boolean =
-            event.content.toModel()?.relatesTo?.type == RelationType.THREAD
+            event.content.toModel()?.relatesTo?.type == RelationType.IO_THREAD
 
     /**
      * Returns the root thread eventId or null otherwise
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
index 3a191063127..62bcccb67a3 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
@@ -436,7 +436,7 @@ class RoomDetailViewModel @AssistedInject constructor(
 
     private fun handleSendSticker(action: RoomDetailAction.SendSticker) {
         val content = initialState.rootThreadEventId?.let {
-            action.stickerContent.copy(relatesTo = RelationDefaultContent(RelationType.THREAD, it))
+            action.stickerContent.copy(relatesTo = RelationDefaultContent(RelationType.IO_THREAD, it))
         } ?: action.stickerContent
         room.sendEvent(EventType.STICKER, content.toContent())
     }

From d7546db26f917a630c09d4d992e1abd801ce5a25 Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Tue, 21 Dec 2021 20:09:25 +0200
Subject: [PATCH 040/581] Fix code quality issues

---
 .../home/room/threads/ThreadsActivity.kt       |  6 +++++-
 .../res/layout/view_room_detail_toolbar.xml    | 18 +++++++++---------
 2 files changed, 14 insertions(+), 10 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt
index 84d270a2c22..fe3c32ae651 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt
@@ -102,7 +102,11 @@ class ThreadsActivity : VectorBaseActivity(), ToolbarCon
                 avatarUrl = timelineEvent.senderInfo.avatarUrl,
                 rootThreadEventId = timelineEvent.eventId)
         val commonOption: (FragmentTransaction) -> Unit = {
-            it.setCustomAnimations(R.anim.animation_slide_in_right, R.anim.animation_slide_out_left, R.anim.animation_slide_in_left, R.anim.animation_slide_out_right)
+            it.setCustomAnimations(
+                    R.anim.animation_slide_in_right,
+                    R.anim.animation_slide_out_left,
+                    R.anim.animation_slide_in_left,
+                    R.anim.animation_slide_out_right)
         }
         addFragmentToBackstack(
                 container = views.threadsActivityFragmentContainer,
diff --git a/vector/src/main/res/layout/view_room_detail_toolbar.xml b/vector/src/main/res/layout/view_room_detail_toolbar.xml
index fdc3f6819e4..ab78f45243c 100644
--- a/vector/src/main/res/layout/view_room_detail_toolbar.xml
+++ b/vector/src/main/res/layout/view_room_detail_toolbar.xml
@@ -25,9 +25,9 @@
         android:layout_height="17dp"
         android:layout_marginStart="5dp"
         android:layout_marginTop="2dp"
-        app:layout_constraintBottom_toBottomOf="@+id/roomToolbarTitleView"
-        app:layout_constraintStart_toEndOf="@+id/roomToolbarAvatarImageView"
-        app:layout_constraintTop_toTopOf="@+id/roomToolbarTitleView" />
+        app:layout_constraintBottom_toBottomOf="@id/roomToolbarTitleView"
+        app:layout_constraintStart_toEndOf="@id/roomToolbarAvatarImageView"
+        app:layout_constraintTop_toTopOf="@id/roomToolbarTitleView" />
 
     
 

From f06397023a8b7e5d984270a97567820bc1608ab7 Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Thu, 23 Dec 2021 17:19:36 +0200
Subject: [PATCH 041/581] Add support when there no threads messages to init
 timeline. Init as the normal one and hide them on the app side. That is also
 helpful to work to load all the threads when there is no server support

---
 .../room/model/relation/RelationService.kt    | 12 +++
 .../database/helper/ChunkEntityHelper.kt      |  4 +-
 .../sdk/internal/session/room/RoomAPI.kt      | 13 +--
 .../sdk/internal/session/room/RoomModule.kt   |  5 +
 .../room/relation/DefaultRelationService.kt   | 97 ++++++++++++++++++-
 .../threads/FetchThreadTimelineTask.kt        | 55 +++++++++++
 .../session/room/timeline/DefaultTimeline.kt  | 29 ++++--
 .../home/room/detail/RoomDetailViewModel.kt   | 46 ++++++---
 .../timeline/TimelineEventController.kt       | 26 ++++-
 .../factory/MergedHeaderItemFactory.kt        |  2 +-
 .../timeline/factory/TimelineItemFactory.kt   | 32 +++++-
 .../helper/TimelineEventVisibilityHelper.kt   | 44 +++++++--
 12 files changed, 313 insertions(+), 52 deletions(-)
 create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt

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 a5ecfaf6e44..4f28f7dce12 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
@@ -145,4 +145,16 @@ interface RelationService {
                       autoMarkdown: Boolean = false,
                       formattedText: String? = null,
                       eventReplied: TimelineEvent? = null): Cancelable?
+
+
+
+
+    /**
+     * Get all the thread replies for the specified rootThreadEventId
+     * The return list will contain the original root thread event and all the thread replies to that event
+     * Note: We will use a large limit value in order to avoid using pagination until it would be 100% ready
+     * from the backend
+     * @param rootThreadEventId the root thread eventId
+     */
+    suspend fun fetchThreadTimeline(rootThreadEventId: String): List
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt
index b0d15ce8dac..0b8c42c8cda 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt
@@ -82,7 +82,7 @@ internal fun ChunkEntity.addStateEvent(roomId: String, stateEvent: EventEntity,
 internal fun ChunkEntity.addTimelineEvent(roomId: String,
                                           eventEntity: EventEntity,
                                           direction: PaginationDirection,
-                                          roomMemberContentsByUser: Map) {
+                                          roomMemberContentsByUser: Map? = null) {
     val eventId = eventEntity.eventId
     if (timelineEvents.find(eventId) != null) {
         return
@@ -102,7 +102,7 @@ internal fun ChunkEntity.addTimelineEvent(roomId: String,
                 ?.also { it.cleanUp(eventEntity.sender) }
         this.readReceipts = readReceiptsSummaryEntity
         this.displayIndex = displayIndex
-        val roomMemberContent = roomMemberContentsByUser[senderId]
+        val roomMemberContent = roomMemberContentsByUser?.get(senderId)
         this.senderAvatar = roomMemberContent?.avatarUrl
         this.senderName = roomMemberContent?.displayName
         isUniqueDisplayName = if (roomMemberContent?.displayName != null) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt
index 0017cdd9176..2dd1871ac04 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt
@@ -226,7 +226,8 @@ internal interface RoomAPI {
     suspend fun getRelations(@Path("roomId") roomId: String,
                              @Path("eventId") eventId: String,
                              @Path("relationType") relationType: String,
-                             @Path("eventType") eventType: String
+                             @Path("eventType") eventType: String,
+                             @Query("limit") limit: Int?= null
     ): RelationsResponse
 
     /**
@@ -377,14 +378,4 @@ internal interface RoomAPI {
     suspend fun getRoomSummary(@Path("roomIdOrAlias") roomidOrAlias: String,
                                @Query("via") viaServers: List?): RoomStrippedState
 
-    // TODO add doc
-    /**
-     */
-    @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "rooms/{roomId}/messages")
-    suspend fun getRoomThreadMessages(@Path("roomId") roomId: String,
-                                      @Query("from") from: String,
-                                      @Query("dir") dir: String,
-                                      @Query("limit") limit: Int,
-                                      @Query("filter") filter: String?
-    ): PaginationResponse
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt
index dbd0ae6f060..7939c74dcec 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt
@@ -74,6 +74,8 @@ import org.matrix.android.sdk.internal.session.room.relation.DefaultUpdateQuickR
 import org.matrix.android.sdk.internal.session.room.relation.FetchEditHistoryTask
 import org.matrix.android.sdk.internal.session.room.relation.FindReactionEventForUndoTask
 import org.matrix.android.sdk.internal.session.room.relation.UpdateQuickReactionTask
+import org.matrix.android.sdk.internal.session.room.relation.threads.DefaultFetchThreadTimelineTask
+import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask
 import org.matrix.android.sdk.internal.session.room.reporting.DefaultReportContentTask
 import org.matrix.android.sdk.internal.session.room.reporting.ReportContentTask
 import org.matrix.android.sdk.internal.session.room.state.DefaultSendStateTask
@@ -256,4 +258,7 @@ internal abstract class RoomModule {
 
     @Binds
     abstract fun bindGetRoomSummaryTask(task: DefaultGetRoomSummaryTask): GetRoomSummaryTask
+
+    @Binds
+    abstract fun bindFetchThreadTimelineTask(task: DefaultFetchThreadTimelineTask): FetchThreadTimelineTask
 }
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 d459e79a4a8..02af20de233 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
@@ -21,26 +21,48 @@ import com.zhuinden.monarchy.Monarchy
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
+import io.realm.Realm
 import org.matrix.android.sdk.api.MatrixCallback
+import org.matrix.android.sdk.api.session.crypto.MXCryptoError
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary
 import org.matrix.android.sdk.api.session.room.model.relation.RelationService
+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.Optional
 import org.matrix.android.sdk.api.util.toOptional
 import org.matrix.android.sdk.internal.crypto.CryptoSessionInfoProvider
+import org.matrix.android.sdk.internal.crypto.DefaultCryptoService
+import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
+import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult
+import org.matrix.android.sdk.internal.database.helper.addTimelineEvent
+import org.matrix.android.sdk.internal.database.helper.updateThreadSummaryIfNeeded
 import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
 import org.matrix.android.sdk.internal.database.mapper.asDomain
+import org.matrix.android.sdk.internal.database.mapper.toEntity
+import org.matrix.android.sdk.internal.database.model.ChunkEntity
+import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity
 import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity
+import org.matrix.android.sdk.internal.database.model.EventEntity
+import org.matrix.android.sdk.internal.database.model.EventInsertType
 import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
+import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore
+import org.matrix.android.sdk.internal.database.query.findIncludingEvent
+import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfRoom
+import org.matrix.android.sdk.internal.database.query.getOrCreate
 import org.matrix.android.sdk.internal.database.query.where
+import org.matrix.android.sdk.internal.di.MoshiProvider
 import org.matrix.android.sdk.internal.di.SessionDatabase
+import org.matrix.android.sdk.internal.di.UserId
+import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask
 import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
 import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
+import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection
 import org.matrix.android.sdk.internal.task.TaskExecutor
 import org.matrix.android.sdk.internal.task.configureWith
+import org.matrix.android.sdk.internal.util.awaitTransaction
 import org.matrix.android.sdk.internal.util.fetchCopyMap
 import timber.log.Timber
 
@@ -50,9 +72,12 @@ internal class DefaultRelationService @AssistedInject constructor(
         private val eventSenderProcessor: EventSenderProcessor,
         private val eventFactory: LocalEchoEventFactory,
         private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
+        private val cryptoService: DefaultCryptoService,
         private val findReactionEventForUndoTask: FindReactionEventForUndoTask,
         private val fetchEditHistoryTask: FetchEditHistoryTask,
+        private val fetchThreadTimelineTask: FetchThreadTimelineTask,
         private val timelineEventMapper: TimelineEventMapper,
+        @UserId private val userId: String,
         @SessionDatabase private val monarchy: Monarchy,
         private val taskExecutor: TaskExecutor) :
         RelationService {
@@ -192,7 +217,77 @@ internal class DefaultRelationService @AssistedInject constructor(
                         saveLocalEcho(it)
                     }
         }
-        return  eventSenderProcessor.postEvent(event, cryptoSessionInfoProvider.isRoomEncrypted(roomId))
+        return eventSenderProcessor.postEvent(event, cryptoSessionInfoProvider.isRoomEncrypted(roomId))
+    }
+
+    private fun decryptIfNeeded(event: Event, roomId: String) {
+        try {
+            // Event from sync does not have roomId, so add it to the event first
+            val result = cryptoService.decryptEvent(event.copy(roomId = roomId), "")
+            event.mxDecryptionResult = OlmDecryptionResult(
+                    payload = result.clearEvent,
+                    senderKey = result.senderCurve25519Key,
+                    keysClaimed = result.claimedEd25519Key?.let { k -> mapOf("ed25519" to k) },
+                    forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
+            )
+        } catch (e: MXCryptoError) {
+            if (e is MXCryptoError.Base) {
+                event.mCryptoError = e.errorType
+                event.mCryptoErrorReason = e.technicalMessage.takeIf { it.isNotEmpty() } ?: e.detailedErrorDescription
+            }
+        }
+    }
+
+    override suspend fun fetchThreadTimeline(rootThreadEventId: String): List {
+        val results = fetchThreadTimelineTask.execute(FetchThreadTimelineTask.Params(roomId, rootThreadEventId))
+        var counter = 0
+//
+//        monarchy
+//                .awaitTransaction { realm ->
+//                    val chunk = ChunkEntity.findLastForwardChunkOfRoom(realm, roomId)
+//
+//                    val optimizedThreadSummaryMap = hashMapOf()
+//                    for (event in results.reversed()) {
+//                        if (event.eventId == null || event.senderId == null || event.type == null) {
+//                            continue
+//                        }
+//
+//                        // skip if event already exists
+//                        if (EventEntity.where(realm, event.eventId).findFirst() != null) {
+//                            counter++
+//                            continue
+//                        }
+//
+//                        if (event.isEncrypted()) {
+//                            decryptIfNeeded(event, roomId)
+//                        }
+//
+//                        val ageLocalTs = event.unsignedData?.age?.let { System.currentTimeMillis() - it }
+//                        val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.INCREMENTAL_SYNC)
+//                        if (event.stateKey != null) {
+//                            CurrentStateEventEntity.getOrCreate(realm, roomId, event.stateKey, event.type).apply {
+//                                eventId = event.eventId
+//                                root = eventEntity
+//                            }
+//                        }
+//                        chunk?.addTimelineEvent(roomId, eventEntity, PaginationDirection.FORWARDS)
+//                        eventEntity.rootThreadEventId?.let {
+//                            // This is a thread event
+//                            optimizedThreadSummaryMap[it] = eventEntity
+//                        } ?: run {
+//                            // This is a normal event or a root thread one
+//                            optimizedThreadSummaryMap[eventEntity.eventId] = eventEntity
+//                        }
+//                    }
+//
+//                    optimizedThreadSummaryMap.updateThreadSummaryIfNeeded(
+//                            roomId = roomId,
+//                            realm = realm,
+//                            currentUserId = userId)
+//                }
+        Timber.i("----> size: ${results.size} | skipped: $counter | threads: ${results.map{ it.eventId}}")
+
+        return results
     }
 
     /**
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt
new file mode 100644
index 00000000000..d62ce4158f2
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright 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.room.relation.threads
+
+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.internal.crypto.CryptoSessionInfoProvider
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
+import org.matrix.android.sdk.internal.network.executeRequest
+import org.matrix.android.sdk.internal.session.room.RoomAPI
+import org.matrix.android.sdk.internal.task.Task
+import javax.inject.Inject
+
+internal interface FetchThreadTimelineTask : Task> {
+    data class Params(
+            val roomId: String,
+            val rootThreadEventId: String
+    )
+}
+
+internal class DefaultFetchThreadTimelineTask @Inject constructor(
+        private val roomAPI: RoomAPI,
+        private val globalErrorReceiver: GlobalErrorReceiver,
+        private val cryptoSessionInfoProvider: CryptoSessionInfoProvider
+) : FetchThreadTimelineTask {
+
+    override suspend fun execute(params: FetchThreadTimelineTask.Params): List {
+        val isRoomEncrypted = cryptoSessionInfoProvider.isRoomEncrypted(params.roomId)
+        val response = executeRequest(globalErrorReceiver) {
+            roomAPI.getRelations(
+                    roomId = params.roomId,
+                    eventId = params.rootThreadEventId,
+                    relationType = RelationType.IO_THREAD,
+                    eventType = if (isRoomEncrypted) EventType.ENCRYPTED else EventType.MESSAGE,
+                    limit = 2000
+            )
+        }
+
+        return response.chunks + listOfNotNull(response.originalEvent)
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
index 69e56a85d07..a100c4635a3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
@@ -53,6 +53,7 @@ import org.matrix.android.sdk.internal.util.Debouncer
 import org.matrix.android.sdk.internal.util.createBackgroundHandler
 import org.matrix.android.sdk.internal.util.createUIHandler
 import timber.log.Timber
+import java.lang.Thread.sleep
 import java.util.Collections
 import java.util.UUID
 import java.util.concurrent.CopyOnWriteArrayList
@@ -107,6 +108,7 @@ internal class DefaultTimeline(
     private val backwardsState = AtomicReference(TimelineState())
     private val forwardsState = AtomicReference(TimelineState())
     private var isFromThreadTimeline = false
+    private var rootThreadEventId: String? = null
     override val timelineID = UUID.randomUUID().toString()
 
     override val isLive
@@ -151,9 +153,11 @@ internal class DefaultTimeline(
     override fun start(rootThreadEventId: String?) {
         if (isStarted.compareAndSet(false, true)) {
             isFromThreadTimeline = rootThreadEventId != null
+            this@DefaultTimeline.rootThreadEventId = rootThreadEventId
             Timber.v("Start timeline for roomId: $roomId and eventId: $initialEventId")
             timelineInput.listeners.add(this)
             BACKGROUND_HANDLER.post {
+
                 eventDecryptor.start()
                 val realm = Realm.getInstance(realmConfiguration)
                 backgroundRealm.set(realm)
@@ -170,9 +174,10 @@ internal class DefaultTimeline(
                 }
 
                 timelineEvents = rootThreadEventId?.let {
-                    TimelineEventEntity
+                    val threadTimelineEvents = TimelineEventEntity
                             .whereRoomId(realm, roomId = roomId)
                             .equalTo(TimelineEventEntityFields.CHUNK.IS_LAST_FORWARD, true)
+//                            .`in`("${TimelineEventEntityFields.CHUNK.TIMELINE_EVENTS}.${TimelineEventEntityFields.EVENT_ID}", arrayOf(it))
                             .beginGroup()
                             .equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, it)
                             .or()
@@ -180,7 +185,15 @@ internal class DefaultTimeline(
                             .endGroup()
                             .sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)
                             .findAll()
+                    if (threadTimelineEvents.isNullOrEmpty()) {
+                        // When there no threads in the last forward chunk get all events and hide them
+                        buildEventQuery(realm).sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING).findAll()
+                    } else {
+                        threadTimelineEvents
+                    }
                 } ?: buildEventQuery(realm).sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING).findAll()
+                if (isFromThreadTimeline)
+                    Timber.i("----> timelineEvents.size: ${timelineEvents.size}")
 
                 timelineEvents.addChangeListener(eventsChangeListener)
                 handleInitialLoad()
@@ -330,17 +343,19 @@ internal class DefaultTimeline(
         val lastCacheEvent = results.lastOrNull()
         val firstCacheEvent = results.firstOrNull()
         val chunkEntity = getLiveChunk()
+        if (isFromThreadTimeline)
+            Timber.i("----> results.size: ${results.size} | contains root thread ${results.map { it.eventId }.contains(rootThreadEventId)}")
 
-        updateState(Timeline.Direction.FORWARDS) {
-            it.copy(
+        updateState(Timeline.Direction.FORWARDS) { state ->
+            state.copy(
                     hasMoreInCache = !builtEventsIdMap.containsKey(firstCacheEvent?.eventId),   // what is in DB
                     hasReachedEnd = if (isFromThreadTimeline) true else chunkEntity?.isLastForward ?: false // if you neeed fetch more
             )
         }
-        updateState(Timeline.Direction.BACKWARDS) {
-            it.copy(
+        updateState(Timeline.Direction.BACKWARDS) { state ->
+            state.copy(
                     hasMoreInCache = !builtEventsIdMap.containsKey(lastCacheEvent?.eventId),
-                    hasReachedEnd = if (isFromThreadTimeline) true else chunkEntity?.isLastBackward ?: false || lastCacheEvent?.root?.type == EventType.STATE_ROOM_CREATE
+                    hasReachedEnd = if (isFromThreadTimeline && results.map { it.eventId }.contains(rootThreadEventId)) true else (chunkEntity?.isLastBackward ?: false || lastCacheEvent?.root?.type == EventType.STATE_ROOM_CREATE)
             )
         }
     }
@@ -640,7 +655,7 @@ internal class DefaultTimeline(
                 }.map {
                     EventMapper.map(it)
                 }
-            threadsAwarenessHandler.fetchRootThreadEventsIfNeeded(eventEntityList)
+        threadsAwarenessHandler.fetchRootThreadEventsIfNeeded(eventEntityList)
     }
 
     private fun buildTimelineEvent(eventEntity: TimelineEventEntity): TimelineEvent {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
index 62bcccb67a3..552a7e63f69 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
@@ -180,6 +180,15 @@ class RoomDetailViewModel @AssistedInject constructor(
         if (OutboundSessionKeySharingStrategy.WhenEnteringRoom == BuildConfig.outboundSessionKeySharingStrategy && room.isEncrypted()) {
             prepareForEncryption()
         }
+
+        // Threads
+        initThreads()
+    }
+
+    /**
+     * Threads specific initialization
+     */
+    private fun initThreads() {
         markThreadTimelineAsReadLocal()
         observeLocalThreadNotifications()
     }
@@ -269,6 +278,18 @@ class RoomDetailViewModel @AssistedInject constructor(
                 }
     }
 
+    /**
+     * Mark the thread as read, while the user navigated within the thread
+     * This is a local implementation has nothing to do with APIs
+     */
+    private fun markThreadTimelineAsReadLocal() {
+        initialState.rootThreadEventId?.let {
+            session.coroutineScope.launch {
+                room.markThreadAsRead(it)
+            }
+        }
+    }
+
     /**
      * Observe local unread threads
      */
@@ -287,6 +308,17 @@ class RoomDetailViewModel @AssistedInject constructor(
                 }
     }
 
+//    /**
+//     * Fetch all the thread replies for the current thread
+//     */
+//    private fun fetchThreadTimeline() {
+//        initialState.rootThreadEventId?.let {
+//            viewModelScope.launch(Dispatchers.IO) {
+//                room.fetchThreadTimeline(it)
+//            }
+//        }
+//    }
+
     fun getOtherUserIds() = room.roomSummary()?.otherMemberIds
 
     fun getRoomSummary() = room.roomSummary()
@@ -1076,18 +1108,6 @@ class RoomDetailViewModel @AssistedInject constructor(
         }
     }
 
-    /**
-     * Mark the thread as read, while the user navigated within the thread
-     * This is a local implementation has nothing to do with APIs
-     */
-    private fun markThreadTimelineAsReadLocal() {
-        initialState.rootThreadEventId?.let {
-            session.coroutineScope.launch {
-                room.markThreadAsRead(it)
-            }
-        }
-    }
-
     override fun onTimelineUpdated(snapshot: List) {
         viewModelScope.launch {
             // tryEmit doesn't work with SharedFlow without cache
@@ -1125,6 +1145,8 @@ class RoomDetailViewModel @AssistedInject constructor(
         chatEffectManager.delegate = null
         chatEffectManager.dispose()
         callManager.removeProtocolsCheckerListener(this)
+        // we should also mark it as read here, for the scenario that the user
+        // is already in the thread timeline
         markThreadTimelineAsReadLocal()
         super.onCleared()
     }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt
index 20a3f34338f..b29cf141d4c 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt
@@ -200,7 +200,12 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
                 // it's sent by the same user so we are sure we have up to date information.
                 val invalidatedSenderId: String? = currentSnapshot.getOrNull(position)?.senderInfo?.userId
                 val prevDisplayableEventIndex = currentSnapshot.subList(0, position).indexOfLast {
-                    timelineEventVisibilityHelper.shouldShowEvent(it, partialState.highlightedEventId, partialState.isFromThreadTimeline())
+                    timelineEventVisibilityHelper.shouldShowEvent(
+                            timelineEvent = it,
+                            highlightedEventId = partialState.highlightedEventId,
+                            isFromThreadTimeline = partialState.isFromThreadTimeline(),
+                            rootThreadEventId = partialState.rootThreadEventId
+                    )
                 }
                 if (prevDisplayableEventIndex != -1 && currentSnapshot[prevDisplayableEventIndex].senderInfo.userId == invalidatedSenderId) {
                     modelCache[prevDisplayableEventIndex] = null
@@ -377,7 +382,11 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
             val nextEvent = currentSnapshot.nextOrNull(position)
             val prevEvent = currentSnapshot.prevOrNull(position)
             val nextDisplayableEvent = currentSnapshot.subList(position + 1, currentSnapshot.size).firstOrNull {
-                timelineEventVisibilityHelper.shouldShowEvent(it, partialState.highlightedEventId, partialState.isFromThreadTimeline())
+                timelineEventVisibilityHelper.shouldShowEvent(
+                        timelineEvent = it,
+                        highlightedEventId = partialState.highlightedEventId,
+                        isFromThreadTimeline = partialState.isFromThreadTimeline(),
+                        rootThreadEventId = partialState.rootThreadEventId)
             }
             // Should be build if not cached or if model should be refreshed
             if (modelCache[position] == null || modelCache[position]?.isCacheable == false) {
@@ -459,7 +468,12 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
                 return null
             }
             // If the event is not shown, we go to the next one
-            if (!timelineEventVisibilityHelper.shouldShowEvent(event, partialState.highlightedEventId, partialState.isFromThreadTimeline())) {
+            if (!timelineEventVisibilityHelper.shouldShowEvent(
+                            timelineEvent = event,
+                            highlightedEventId = partialState.highlightedEventId,
+                            isFromThreadTimeline = partialState.isFromThreadTimeline(),
+                            rootThreadEventId = partialState.rootThreadEventId
+                    )) {
                 continue
             }
             // If the event is sent by us, we update the holder with the eventId and stop the search
@@ -481,7 +495,11 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
             val currentReadReceipts = ArrayList(event.readReceipts).filter {
                 it.user.userId != session.myUserId
             }
-            if (timelineEventVisibilityHelper.shouldShowEvent(event, partialState.highlightedEventId, partialState.isFromThreadTimeline())) {
+            if (timelineEventVisibilityHelper.shouldShowEvent(
+                            timelineEvent = event,
+                            highlightedEventId = partialState.highlightedEventId,
+                            isFromThreadTimeline = partialState.isFromThreadTimeline(),
+                            rootThreadEventId = partialState.rootThreadEventId)) {
                 lastShownEventId = event.eventId
             }
             if (lastShownEventId == null) {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt
index 1c25f923cfa..874d8f0b1eb 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt
@@ -83,7 +83,7 @@ class MergedHeaderItemFactory @Inject constructor(private val activeSessionHolde
                                                    eventIdToHighlight: String?,
                                                    requestModelBuild: () -> Unit,
                                                    callback: TimelineEventController.Callback?): MergedMembershipEventsItem_? {
-        val mergedEvents = timelineEventVisibilityHelper.prevSameTypeEvents(items, currentPosition, 2, eventIdToHighlight, partialState.isFromThreadTimeline())
+        val mergedEvents = timelineEventVisibilityHelper.prevSameTypeEvents(items, currentPosition, 2, eventIdToHighlight,partialState.rootThreadEventId, partialState.isFromThreadTimeline())
         return if (mergedEvents.isEmpty()) {
             null
         } else {
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 2f7c7fdc0fc..1e915d2b29c 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
@@ -42,8 +42,17 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
     fun create(params: TimelineItemFactoryParams): VectorEpoxyModel<*> {
         val event = params.event
         val computedModel = try {
-            if (!timelineEventVisibilityHelper.shouldShowEvent(event, params.highlightedEventId, params.isFromThreadTimeline())) {
-                return buildEmptyItem(event, params.prevEvent, params.highlightedEventId, params.isFromThreadTimeline())
+            if (!timelineEventVisibilityHelper.shouldShowEvent(
+                            timelineEvent = event,
+                            highlightedEventId = params.highlightedEventId,
+                            isFromThreadTimeline = params.isFromThreadTimeline(),
+                            rootThreadEventId = params.rootThreadEventId)) {
+                return buildEmptyItem(
+                        event,
+                        params.prevEvent,
+                        params.highlightedEventId,
+                        params.rootThreadEventId,
+                        params.isFromThreadTimeline())
             }
             when (event.root.getClearType()) {
                 // Message itemsX
@@ -112,11 +121,24 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
             Timber.e(throwable, "failed to create message item")
             defaultItemFactory.create(params, throwable)
         }
-        return computedModel ?: buildEmptyItem(event, params.prevEvent, params.highlightedEventId, params.isFromThreadTimeline())
+        return computedModel ?: buildEmptyItem(
+                event,
+                params.prevEvent,
+                params.highlightedEventId,
+                params.rootThreadEventId,
+                params.isFromThreadTimeline())
     }
 
-    private fun buildEmptyItem(timelineEvent: TimelineEvent, prevEvent: TimelineEvent?, highlightedEventId: String?, isFromThreadTimeline: Boolean): TimelineEmptyItem {
-        val isNotBlank = prevEvent == null || timelineEventVisibilityHelper.shouldShowEvent(prevEvent, highlightedEventId, isFromThreadTimeline)
+    private fun buildEmptyItem(timelineEvent: TimelineEvent,
+                               prevEvent: TimelineEvent?,
+                               highlightedEventId: String?,
+                               rootThreadEventId: String?,
+                               isFromThreadTimeline: Boolean): TimelineEmptyItem {
+        val isNotBlank = prevEvent == null || timelineEventVisibilityHelper.shouldShowEvent(
+                timelineEvent = prevEvent,
+                highlightedEventId = highlightedEventId,
+                isFromThreadTimeline = isFromThreadTimeline,
+                rootThreadEventId = rootThreadEventId)
         return TimelineEmptyItem_()
                 .id(timelineEvent.localId)
                 .eventId(timelineEvent.eventId)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt
index 7efefc52096..e91f28cea62 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt
@@ -40,7 +40,13 @@ class TimelineEventVisibilityHelper @Inject constructor(private val userPreferen
      *
      * @return a list of timeline events which have sequentially the same type following the next direction.
      */
-    private fun nextSameTypeEvents(timelineEvents: List, index: Int, minSize: Int, eventIdToHighlight: String?, isFromThreadTimeline: Boolean): List {
+    private fun nextSameTypeEvents(
+            timelineEvents: List,
+            index: Int,
+            minSize: Int,
+            eventIdToHighlight: String?,
+            rootThreadEventId: String?,
+            isFromThreadTimeline: Boolean): List {
         if (index >= timelineEvents.size - 1) {
             return emptyList()
         }
@@ -62,11 +68,18 @@ class TimelineEventVisibilityHelper @Inject constructor(private val userPreferen
         } else {
             nextSameDayEvents.subList(0, indexOfFirstDifferentEventType)
         }
-        val filteredSameTypeEvents = sameTypeEvents.filter { shouldShowEvent(it, eventIdToHighlight, isFromThreadTimeline) }
+        val filteredSameTypeEvents = sameTypeEvents.filter {
+            shouldShowEvent(
+                    timelineEvent = it,
+                    highlightedEventId = eventIdToHighlight,
+                    isFromThreadTimeline = isFromThreadTimeline,
+                    rootThreadEventId = rootThreadEventId
+            )
+        }
         if (filteredSameTypeEvents.size < minSize) {
             return emptyList()
         }
-        return  filteredSameTypeEvents
+        return filteredSameTypeEvents
     }
 
     /**
@@ -77,12 +90,12 @@ class TimelineEventVisibilityHelper @Inject constructor(private val userPreferen
      *
      * @return a list of timeline events which have sequentially the same type following the prev direction.
      */
-    fun prevSameTypeEvents(timelineEvents: List, index: Int, minSize: Int, eventIdToHighlight: String?, isFromThreadTimeline: Boolean): List {
+    fun prevSameTypeEvents(timelineEvents: List, index: Int, minSize: Int, eventIdToHighlight: String?, rootThreadEventId: String?, isFromThreadTimeline: Boolean): List {
         val prevSub = timelineEvents.subList(0, index + 1)
         return prevSub
                 .reversed()
                 .let {
-                    nextSameTypeEvents(it, 0, minSize, eventIdToHighlight, isFromThreadTimeline)
+                    nextSameTypeEvents(it, 0, minSize, eventIdToHighlight, rootThreadEventId, isFromThreadTimeline)
                 }
     }
 
@@ -92,7 +105,12 @@ class TimelineEventVisibilityHelper @Inject constructor(private val userPreferen
      * @param rootThreadEventId if this param is null it means we are in the original timeline
      * @return true if the event should be shown in the timeline.
      */
-    fun shouldShowEvent(timelineEvent: TimelineEvent, highlightedEventId: String?, isFromThreadTimeline: Boolean): Boolean {
+    fun shouldShowEvent(
+            timelineEvent: TimelineEvent,
+            highlightedEventId: String?,
+            isFromThreadTimeline: Boolean,
+            rootThreadEventId: String?
+    ): Boolean {
         // If show hidden events is true we should always display something
         if (userPreferencesProvider.shouldShowHiddenEvents()) {
             return true
@@ -106,14 +124,14 @@ class TimelineEventVisibilityHelper @Inject constructor(private val userPreferen
         }
 
         // Check for special case where we should hide the event, like redacted, relation, memberships... according to user preferences.
-        return !timelineEvent.shouldBeHidden(isFromThreadTimeline)
+        return !timelineEvent.shouldBeHidden(rootThreadEventId, isFromThreadTimeline)
     }
 
     private fun TimelineEvent.isDisplayable(): Boolean {
         return TimelineDisplayableEvents.DISPLAYABLE_TYPES.contains(root.getClearType())
     }
 
-    private fun TimelineEvent.shouldBeHidden(isFromThreadTimeline: Boolean): Boolean {
+    private fun TimelineEvent.shouldBeHidden(rootThreadEventId: String?, isFromThreadTimeline: Boolean): Boolean {
         if (root.isRedacted() && !userPreferencesProvider.shouldShowRedactedMessages()) {
             return true
         }
@@ -128,10 +146,18 @@ class TimelineEventVisibilityHelper @Inject constructor(private val userPreferen
             return true
         }
 
-        if (BuildConfig.THREADING_ENABLED && !isFromThreadTimeline && root.isThread() && root.getRootThreadEventId() != null) {
+        if (BuildConfig.THREADING_ENABLED && !isFromThreadTimeline && root.isThread()) {
             return true
         }
 
+        if (BuildConfig.THREADING_ENABLED && isFromThreadTimeline) {
+
+            ////
+            return if (root.getRootThreadEventId() == rootThreadEventId) {
+                false
+            } else root.eventId != rootThreadEventId
+        }
+
         return false
     }
 

From 581f71e89d53b9ddedd31a27b9bee02de9c0fbc8 Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Thu, 23 Dec 2021 17:22:27 +0200
Subject: [PATCH 042/581] Remove unused code

---
 .../room/relation/DefaultRelationService.kt   | 68 +------------------
 .../session/room/timeline/DefaultTimeline.kt  |  5 --
 .../home/room/detail/RoomDetailViewModel.kt   | 11 ---
 3 files changed, 1 insertion(+), 83 deletions(-)

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 02af20de233..2a6950d742a 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
@@ -220,74 +220,8 @@ internal class DefaultRelationService @AssistedInject constructor(
         return eventSenderProcessor.postEvent(event, cryptoSessionInfoProvider.isRoomEncrypted(roomId))
     }
 
-    private fun decryptIfNeeded(event: Event, roomId: String) {
-        try {
-            // Event from sync does not have roomId, so add it to the event first
-            val result = cryptoService.decryptEvent(event.copy(roomId = roomId), "")
-            event.mxDecryptionResult = OlmDecryptionResult(
-                    payload = result.clearEvent,
-                    senderKey = result.senderCurve25519Key,
-                    keysClaimed = result.claimedEd25519Key?.let { k -> mapOf("ed25519" to k) },
-                    forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
-            )
-        } catch (e: MXCryptoError) {
-            if (e is MXCryptoError.Base) {
-                event.mCryptoError = e.errorType
-                event.mCryptoErrorReason = e.technicalMessage.takeIf { it.isNotEmpty() } ?: e.detailedErrorDescription
-            }
-        }
-    }
-
     override suspend fun fetchThreadTimeline(rootThreadEventId: String): List {
-        val results = fetchThreadTimelineTask.execute(FetchThreadTimelineTask.Params(roomId, rootThreadEventId))
-        var counter = 0
-//
-//        monarchy
-//                .awaitTransaction { realm ->
-//                    val chunk = ChunkEntity.findLastForwardChunkOfRoom(realm, roomId)
-//
-//                    val optimizedThreadSummaryMap = hashMapOf()
-//                    for (event in results.reversed()) {
-//                        if (event.eventId == null || event.senderId == null || event.type == null) {
-//                            continue
-//                        }
-//
-//                        // skip if event already exists
-//                        if (EventEntity.where(realm, event.eventId).findFirst() != null) {
-//                            counter++
-//                            continue
-//                        }
-//
-//                        if (event.isEncrypted()) {
-//                            decryptIfNeeded(event, roomId)
-//                        }
-//
-//                        val ageLocalTs = event.unsignedData?.age?.let { System.currentTimeMillis() - it }
-//                        val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.INCREMENTAL_SYNC)
-//                        if (event.stateKey != null) {
-//                            CurrentStateEventEntity.getOrCreate(realm, roomId, event.stateKey, event.type).apply {
-//                                eventId = event.eventId
-//                                root = eventEntity
-//                            }
-//                        }
-//                        chunk?.addTimelineEvent(roomId, eventEntity, PaginationDirection.FORWARDS)
-//                        eventEntity.rootThreadEventId?.let {
-//                            // This is a thread event
-//                            optimizedThreadSummaryMap[it] = eventEntity
-//                        } ?: run {
-//                            // This is a normal event or a root thread one
-//                            optimizedThreadSummaryMap[eventEntity.eventId] = eventEntity
-//                        }
-//                    }
-//
-//                    optimizedThreadSummaryMap.updateThreadSummaryIfNeeded(
-//                            roomId = roomId,
-//                            realm = realm,
-//                            currentUserId = userId)
-//                }
-        Timber.i("----> size: ${results.size} | skipped: $counter | threads: ${results.map{ it.eventId}}")
-
-        return results
+        return fetchThreadTimelineTask.execute(FetchThreadTimelineTask.Params(roomId, rootThreadEventId))
     }
 
     /**
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
index a100c4635a3..72a922dc885 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
@@ -177,7 +177,6 @@ internal class DefaultTimeline(
                     val threadTimelineEvents = TimelineEventEntity
                             .whereRoomId(realm, roomId = roomId)
                             .equalTo(TimelineEventEntityFields.CHUNK.IS_LAST_FORWARD, true)
-//                            .`in`("${TimelineEventEntityFields.CHUNK.TIMELINE_EVENTS}.${TimelineEventEntityFields.EVENT_ID}", arrayOf(it))
                             .beginGroup()
                             .equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, it)
                             .or()
@@ -192,8 +191,6 @@ internal class DefaultTimeline(
                         threadTimelineEvents
                     }
                 } ?: buildEventQuery(realm).sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING).findAll()
-                if (isFromThreadTimeline)
-                    Timber.i("----> timelineEvents.size: ${timelineEvents.size}")
 
                 timelineEvents.addChangeListener(eventsChangeListener)
                 handleInitialLoad()
@@ -343,8 +340,6 @@ internal class DefaultTimeline(
         val lastCacheEvent = results.lastOrNull()
         val firstCacheEvent = results.firstOrNull()
         val chunkEntity = getLiveChunk()
-        if (isFromThreadTimeline)
-            Timber.i("----> results.size: ${results.size} | contains root thread ${results.map { it.eventId }.contains(rootThreadEventId)}")
 
         updateState(Timeline.Direction.FORWARDS) { state ->
             state.copy(
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
index 552a7e63f69..45389fefc8a 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
@@ -308,17 +308,6 @@ class RoomDetailViewModel @AssistedInject constructor(
                 }
     }
 
-//    /**
-//     * Fetch all the thread replies for the current thread
-//     */
-//    private fun fetchThreadTimeline() {
-//        initialState.rootThreadEventId?.let {
-//            viewModelScope.launch(Dispatchers.IO) {
-//                room.fetchThreadTimeline(it)
-//            }
-//        }
-//    }
-
     fun getOtherUserIds() = room.roomSummary()?.otherMemberIds
 
     fun getRoomSummary() = room.roomSummary()

From d3e9e197798a26a1beae4b1cfac27f5d162199ea Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Sat, 25 Dec 2021 13:11:42 +0200
Subject: [PATCH 043/581] Fix code quality issues

---
 .../helper/TimelineEventVisibilityHelper.kt   |  8 ++-
 .../view_thread_notification_badge_old.xml    | 53 -------------------
 2 files changed, 7 insertions(+), 54 deletions(-)
 delete mode 100644 vector/src/main/res/layout/view_thread_notification_badge_old.xml

diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt
index e91f28cea62..1ffd66c5725 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt
@@ -90,7 +90,13 @@ class TimelineEventVisibilityHelper @Inject constructor(private val userPreferen
      *
      * @return a list of timeline events which have sequentially the same type following the prev direction.
      */
-    fun prevSameTypeEvents(timelineEvents: List, index: Int, minSize: Int, eventIdToHighlight: String?, rootThreadEventId: String?, isFromThreadTimeline: Boolean): List {
+    fun prevSameTypeEvents(
+            timelineEvents: List,
+            index: Int,
+            minSize: Int,
+            eventIdToHighlight: String?,
+            rootThreadEventId: String?,
+            isFromThreadTimeline: Boolean): List {
         val prevSub = timelineEvents.subList(0, index + 1)
         return prevSub
                 .reversed()
diff --git a/vector/src/main/res/layout/view_thread_notification_badge_old.xml b/vector/src/main/res/layout/view_thread_notification_badge_old.xml
deleted file mode 100644
index 70efceda516..00000000000
--- a/vector/src/main/res/layout/view_thread_notification_badge_old.xml
+++ /dev/null
@@ -1,53 +0,0 @@
-
-
-
-    
-
-    
-
-    
-
-
-

From 9ef4e1e83f4dd296797d46a15ab30f06e89f8497 Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Sat, 25 Dec 2021 13:42:53 +0200
Subject: [PATCH 044/581] Fix code quality issues

---
 .../room/model/relation/RelationService.kt    |  3 ---
 .../sdk/internal/session/room/RoomAPI.kt      |  3 +--
 .../room/relation/DefaultRelationService.kt   | 19 -------------------
 .../session/room/timeline/DefaultTimeline.kt  |  2 --
 .../factory/MergedHeaderItemFactory.kt        |  2 +-
 .../helper/TimelineEventVisibilityHelper.kt   |  3 +--
 6 files changed, 3 insertions(+), 29 deletions(-)

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 4f28f7dce12..183cd481d27 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
@@ -146,9 +146,6 @@ interface RelationService {
                       formattedText: String? = null,
                       eventReplied: TimelineEvent? = null): Cancelable?
 
-
-
-
     /**
      * Get all the thread replies for the specified rootThreadEventId
      * The return list will contain the original root thread event and all the thread replies to that event
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt
index 2dd1871ac04..399bfbd0e45 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt
@@ -227,7 +227,7 @@ internal interface RoomAPI {
                              @Path("eventId") eventId: String,
                              @Path("relationType") relationType: String,
                              @Path("eventType") eventType: String,
-                             @Query("limit") limit: Int?= null
+                             @Query("limit") limit: Int? = null
     ): RelationsResponse
 
     /**
@@ -377,5 +377,4 @@ internal interface RoomAPI {
     @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "im.nheko.summary/rooms/{roomIdOrAlias}/summary")
     suspend fun getRoomSummary(@Path("roomIdOrAlias") roomidOrAlias: String,
                                @Query("via") viaServers: List?): RoomStrippedState
-
 }
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 2a6950d742a..47794e424f6 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
@@ -21,13 +21,10 @@ import com.zhuinden.monarchy.Monarchy
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
-import io.realm.Realm
 import org.matrix.android.sdk.api.MatrixCallback
-import org.matrix.android.sdk.api.session.crypto.MXCryptoError
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary
 import org.matrix.android.sdk.api.session.room.model.relation.RelationService
-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
@@ -35,34 +32,18 @@ import org.matrix.android.sdk.api.util.Optional
 import org.matrix.android.sdk.api.util.toOptional
 import org.matrix.android.sdk.internal.crypto.CryptoSessionInfoProvider
 import org.matrix.android.sdk.internal.crypto.DefaultCryptoService
-import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
-import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult
-import org.matrix.android.sdk.internal.database.helper.addTimelineEvent
-import org.matrix.android.sdk.internal.database.helper.updateThreadSummaryIfNeeded
 import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
 import org.matrix.android.sdk.internal.database.mapper.asDomain
-import org.matrix.android.sdk.internal.database.mapper.toEntity
-import org.matrix.android.sdk.internal.database.model.ChunkEntity
-import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity
 import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity
-import org.matrix.android.sdk.internal.database.model.EventEntity
-import org.matrix.android.sdk.internal.database.model.EventInsertType
 import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
-import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore
-import org.matrix.android.sdk.internal.database.query.findIncludingEvent
-import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfRoom
-import org.matrix.android.sdk.internal.database.query.getOrCreate
 import org.matrix.android.sdk.internal.database.query.where
-import org.matrix.android.sdk.internal.di.MoshiProvider
 import org.matrix.android.sdk.internal.di.SessionDatabase
 import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask
 import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
 import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
-import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection
 import org.matrix.android.sdk.internal.task.TaskExecutor
 import org.matrix.android.sdk.internal.task.configureWith
-import org.matrix.android.sdk.internal.util.awaitTransaction
 import org.matrix.android.sdk.internal.util.fetchCopyMap
 import timber.log.Timber
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
index 72a922dc885..00e7510b009 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
@@ -53,7 +53,6 @@ import org.matrix.android.sdk.internal.util.Debouncer
 import org.matrix.android.sdk.internal.util.createBackgroundHandler
 import org.matrix.android.sdk.internal.util.createUIHandler
 import timber.log.Timber
-import java.lang.Thread.sleep
 import java.util.Collections
 import java.util.UUID
 import java.util.concurrent.CopyOnWriteArrayList
@@ -157,7 +156,6 @@ internal class DefaultTimeline(
             Timber.v("Start timeline for roomId: $roomId and eventId: $initialEventId")
             timelineInput.listeners.add(this)
             BACKGROUND_HANDLER.post {
-
                 eventDecryptor.start()
                 val realm = Realm.getInstance(realmConfiguration)
                 backgroundRealm.set(realm)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt
index 874d8f0b1eb..6ef8ba5a0fc 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt
@@ -83,7 +83,7 @@ class MergedHeaderItemFactory @Inject constructor(private val activeSessionHolde
                                                    eventIdToHighlight: String?,
                                                    requestModelBuild: () -> Unit,
                                                    callback: TimelineEventController.Callback?): MergedMembershipEventsItem_? {
-        val mergedEvents = timelineEventVisibilityHelper.prevSameTypeEvents(items, currentPosition, 2, eventIdToHighlight,partialState.rootThreadEventId, partialState.isFromThreadTimeline())
+        val mergedEvents = timelineEventVisibilityHelper.prevSameTypeEvents(items, currentPosition, 2, eventIdToHighlight, partialState.rootThreadEventId, partialState.isFromThreadTimeline())
         return if (mergedEvents.isEmpty()) {
             null
         } else {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt
index 1ffd66c5725..6d23d22ff03 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt
@@ -157,8 +157,7 @@ class TimelineEventVisibilityHelper @Inject constructor(private val userPreferen
         }
 
         if (BuildConfig.THREADING_ENABLED && isFromThreadTimeline) {
-
-            ////
+            // //
             return if (root.getRootThreadEventId() == rootThreadEventId) {
                 false
             } else root.eventId != rootThreadEventId

From 0e30f4e817db7e5c329cceab3970e7a04105a4af Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Sat, 25 Dec 2021 23:35:40 +0200
Subject: [PATCH 045/581] Fix code quality issues

---
 .../session/room/timeline/DefaultTimeline.kt      |  8 ++++++--
 .../features/home/room/detail/TimelineFragment.kt | 15 ++++++++++-----
 .../timeline/factory/MergedHeaderItemFactory.kt   |  8 +++++++-
 3 files changed, 23 insertions(+), 8 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
index 00e7510b009..3807ffe5079 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
@@ -342,13 +342,17 @@ internal class DefaultTimeline(
         updateState(Timeline.Direction.FORWARDS) { state ->
             state.copy(
                     hasMoreInCache = !builtEventsIdMap.containsKey(firstCacheEvent?.eventId),   // what is in DB
-                    hasReachedEnd = if (isFromThreadTimeline) true else chunkEntity?.isLastForward ?: false // if you neeed fetch more
+                    hasReachedEnd = if (isFromThreadTimeline) true else chunkEntity?.isLastForward ?: false // if you need fetch more
             )
         }
         updateState(Timeline.Direction.BACKWARDS) { state ->
             state.copy(
                     hasMoreInCache = !builtEventsIdMap.containsKey(lastCacheEvent?.eventId),
-                    hasReachedEnd = if (isFromThreadTimeline && results.map { it.eventId }.contains(rootThreadEventId)) true else (chunkEntity?.isLastBackward ?: false || lastCacheEvent?.root?.type == EventType.STATE_ROOM_CREATE)
+                    hasReachedEnd = if (isFromThreadTimeline && results.map { it.eventId }.contains(rootThreadEventId)) {
+                        true
+                    } else {
+                        (chunkEntity?.isLastBackward ?: false || lastCacheEvent?.root?.type == EventType.STATE_ROOM_CREATE)
+                    }
             )
         }
     }
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 7778294aa3d..652451c1ff5 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
@@ -734,27 +734,32 @@ class TimelineFragment @Inject constructor(
             }
 
             override fun onSendVoiceMessage() {
-                messageComposerViewModel.handle(MessageComposerAction.EndRecordingVoiceMessage(isCancelled = false, rootThreadEventId = getRootThreadEventId()))
+                messageComposerViewModel.handle(
+                        MessageComposerAction.EndRecordingVoiceMessage(isCancelled = false, rootThreadEventId = getRootThreadEventId()))
                 updateRecordingUiState(RecordingUiState.Idle)
             }
 
             override fun onDeleteVoiceMessage() {
-                messageComposerViewModel.handle(MessageComposerAction.EndRecordingVoiceMessage(isCancelled = true, rootThreadEventId = getRootThreadEventId()))
+                messageComposerViewModel.handle(
+                        MessageComposerAction.EndRecordingVoiceMessage(isCancelled = true, rootThreadEventId = getRootThreadEventId()))
                 updateRecordingUiState(RecordingUiState.Idle)
             }
 
             override fun onRecordingLimitReached() {
-                messageComposerViewModel.handle(MessageComposerAction.PauseRecordingVoiceMessage)
+                messageComposerViewModel.handle(
+                        MessageComposerAction.PauseRecordingVoiceMessage)
                 updateRecordingUiState(RecordingUiState.Draft)
             }
 
             override fun onRecordingWaveformClicked() {
-                messageComposerViewModel.handle(MessageComposerAction.PauseRecordingVoiceMessage)
+                messageComposerViewModel.handle(
+                        MessageComposerAction.PauseRecordingVoiceMessage)
                 updateRecordingUiState(RecordingUiState.Draft)
             }
 
             private fun updateRecordingUiState(state: RecordingUiState) {
-                messageComposerViewModel.handle(MessageComposerAction.OnVoiceRecordingUiStateChanged(state))
+                messageComposerViewModel.handle(
+                        MessageComposerAction.OnVoiceRecordingUiStateChanged(state))
             }
         }
     }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt
index 6ef8ba5a0fc..99a026a0cf9 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt
@@ -83,7 +83,13 @@ class MergedHeaderItemFactory @Inject constructor(private val activeSessionHolde
                                                    eventIdToHighlight: String?,
                                                    requestModelBuild: () -> Unit,
                                                    callback: TimelineEventController.Callback?): MergedMembershipEventsItem_? {
-        val mergedEvents = timelineEventVisibilityHelper.prevSameTypeEvents(items, currentPosition, 2, eventIdToHighlight, partialState.rootThreadEventId, partialState.isFromThreadTimeline())
+        val mergedEvents = timelineEventVisibilityHelper.prevSameTypeEvents(
+                items,
+                currentPosition,
+                2,
+                eventIdToHighlight,
+                partialState.rootThreadEventId,
+                partialState.isFromThreadTimeline())
         return if (mergedEvents.isEmpty()) {
             null
         } else {

From 0d9bc188d7700657c74786125ad769dae8ba7f24 Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Sun, 26 Dec 2021 00:48:11 +0200
Subject: [PATCH 046/581] Fix code quality issues

---
 tools/check/forbidden_strings_in_code.txt   | 2 +-
 tools/check/forbidden_strings_in_layout.txt | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)
 mode change 100644 => 100755 tools/check/forbidden_strings_in_code.txt
 mode change 100644 => 100755 tools/check/forbidden_strings_in_layout.txt

diff --git a/tools/check/forbidden_strings_in_code.txt b/tools/check/forbidden_strings_in_code.txt
old mode 100644
new mode 100755
index 6ca86be0950..7b869e8cd25
--- a/tools/check/forbidden_strings_in_code.txt
+++ b/tools/check/forbidden_strings_in_code.txt
@@ -160,7 +160,7 @@ Formatter\.formatShortFileSize===1
 # android\.text\.TextUtils
 
 ### This is not a rule, but a warning: the number of "enum class" has changed. For Json classes, it is mandatory that they have `@JsonClass(generateAdapter = false)`. If the enum is not used as a Json class, change the value in file forbidden_strings_in_code.txt
-enum class===114
+enum class===117
 
 ### Do not import temporary legacy classes
 import org.matrix.android.sdk.internal.legacy.riot===3
diff --git a/tools/check/forbidden_strings_in_layout.txt b/tools/check/forbidden_strings_in_layout.txt
old mode 100644
new mode 100755
index 545983f8442..e46aa3a0bbd
--- a/tools/check/forbidden_strings_in_layout.txt
+++ b/tools/check/forbidden_strings_in_layout.txt
@@ -24,7 +24,7 @@
 # Extension:xml
 
 ### Use style="@style/Widget.Vector.TextView.*" instead of textSize attribute
-android:textSize===9
+android:textSize===11
 
 ### Use `@id` and not `@+id` when referencing ids in layouts
 layout_(.*)="@\+id

From f9e03aa99ef268c2a958a42984bc92fea33e06d6 Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Mon, 3 Jan 2022 11:33:38 +0200
Subject: [PATCH 047/581] Remove unused code

---
 .../matrix/android/sdk/internal/database/model/EventEntity.kt   | 2 --
 1 file changed, 2 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt
index f4e12bf3edc..2c103652d02 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt
@@ -48,9 +48,7 @@ internal open class EventEntity(@Index var eventId: String = "",
                                 @Index var isRootThread: Boolean = false,
                                 @Index var rootThreadEventId: String? = null,
                                 var numberOfThreads: Int = 0,
-//                                var threadNotificationState: Boolean = false,
                                 var threadSummaryLatestMessage: TimelineEventEntity? = null
-
 ) : RealmObject() {
 
     private var sendStateStr: String = SendState.UNKNOWN.name

From c2183800d3261689b95e83ba848c5c6f165e55f5 Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Mon, 3 Jan 2022 12:14:14 +0200
Subject: [PATCH 048/581] Github actions improvement test

---
 .github/workflows/integration.yml | 15 ++++++++++-----
 matrix-sdk-android/build.gradle   |  2 +-
 vector/build.gradle               |  2 +-
 3 files changed, 12 insertions(+), 7 deletions(-)

diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
index c18ca69fdee..5cc8882e200 100644
--- a/.github/workflows/integration.yml
+++ b/.github/workflows/integration.yml
@@ -66,11 +66,16 @@ jobs:
             ${{ runner.os }}-gradle-
       - name: Start synapse server
         run: |
-          python3 -m venv .synapse
-          source .synapse/bin/activate
-          pip install synapse matrix-synapse
-          curl -sL https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh --no-rate-limit \
-            | sed s/127.0.0.1/0.0.0.0/g | bash
+          git clone -b develop https://github.com/matrix-org/synapse.git
+          cd synapse
+          source env/bin/activate
+          pip install -e .
+          demo/start.sh --no-rate-limit
+#          python3 -m venv .synapse
+#          source .synapse/bin/activate
+#          pip install synapse matrix-synapse
+#          curl -sL https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh --no-rate-limit \
+#            | sed s/127.0.0.1/0.0.0.0/g | bash
       - name: Run integration tests on API ${{ matrix.api-level }}
         uses: reactivecircus/android-emulator-runner@v2
         with:
diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle
index 3fb6b815051..d84f8868961 100644
--- a/matrix-sdk-android/build.gradle
+++ b/matrix-sdk-android/build.gradle
@@ -47,7 +47,7 @@ android {
 
     testOptions {
         // Comment to run on Android 12
-        execution 'ANDROIDX_TEST_ORCHESTRATOR'
+//        execution 'ANDROIDX_TEST_ORCHESTRATOR'
     }
 
     buildTypes {
diff --git a/vector/build.gradle b/vector/build.gradle
index 4a26717782f..a720b5f988f 100644
--- a/vector/build.gradle
+++ b/vector/build.gradle
@@ -205,7 +205,7 @@ android {
         animationsDisabled = true
 
         // Comment to run on Android 12
-        execution 'ANDROIDX_TEST_ORCHESTRATOR'
+//        execution 'ANDROIDX_TEST_ORCHESTRATOR'
     }
 
     signingConfigs {

From 5edc0506cefafd7ca0632fa7eb8b62b881602708 Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Mon, 3 Jan 2022 12:23:00 +0200
Subject: [PATCH 049/581] Github actions improvement test

---
 .github/workflows/integration.yml | 27 ++++++++++++++-------------
 1 file changed, 14 insertions(+), 13 deletions(-)

diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
index 5cc8882e200..0faee518f2c 100644
--- a/.github/workflows/integration.yml
+++ b/.github/workflows/integration.yml
@@ -67,6 +67,7 @@ jobs:
       - name: Start synapse server
         run: |
           git clone -b develop https://github.com/matrix-org/synapse.git
+          ls
           cd synapse
           source env/bin/activate
           pip install -e .
@@ -76,16 +77,16 @@ jobs:
 #          pip install synapse matrix-synapse
 #          curl -sL https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh --no-rate-limit \
 #            | sed s/127.0.0.1/0.0.0.0/g | bash
-      - name: Run integration tests on API ${{ matrix.api-level }}
-        uses: reactivecircus/android-emulator-runner@v2
-        with:
-          api-level: ${{ matrix.api-level }}
-          #arch: x86_64
-          #disable-animations: true
-          # script: ./gradlew -PallWarningsAsErrors=false vector:connectedAndroidTest matrix-sdk-android:connectedAndroidTest
-          arch: x86
-          profile: Nexus 5X
-          force-avd-creation: false
-          emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
-          emulator-build: 7425822
-          script: ./gradlew $CI_GRADLE_ARG_PROPERTIES -PallWarningsAsErrors=false connectedCheck --stacktrace
+#      - name: Run integration tests on API ${{ matrix.api-level }}
+#        uses: reactivecircus/android-emulator-runner@v2
+#        with:
+#          api-level: ${{ matrix.api-level }}
+#          #arch: x86_64
+#          #disable-animations: true
+#          # script: ./gradlew -PallWarningsAsErrors=false vector:connectedAndroidTest matrix-sdk-android:connectedAndroidTest
+#          arch: x86
+#          profile: Nexus 5X
+#          force-avd-creation: false
+#          emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
+#          emulator-build: 7425822
+#          script: ./gradlew $CI_GRADLE_ARG_PROPERTIES -PallWarningsAsErrors=false connectedCheck --stacktrace

From 683fcc7f3ed93e4ab01f0db4b989c7b427d54fad Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Mon, 3 Jan 2022 12:26:58 +0200
Subject: [PATCH 050/581] Github actions improvement test

---
 .github/workflows/integration.yml | 24 +++++++++++-------------
 1 file changed, 11 insertions(+), 13 deletions(-)

diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
index 0faee518f2c..6e7b945336e 100644
--- a/.github/workflows/integration.yml
+++ b/.github/workflows/integration.yml
@@ -56,22 +56,20 @@ jobs:
           restore-keys: |
             ${{ runner.os }}-pip-
             ${{ runner.os }}-
-      - uses: actions/cache@v2
-        with:
-          path: |
-            ~/.gradle/caches
-            ~/.gradle/wrapper
-          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
-          restore-keys: |
-            ${{ runner.os }}-gradle-
+#      - uses: actions/cache@v2
+#        with:
+#          path: |
+#            ~/.gradle/caches
+#            ~/.gradle/wrapper
+#          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
+#          restore-keys: |
+#            ${{ runner.os }}-gradle-
       - name: Start synapse server
         run: |
           git clone -b develop https://github.com/matrix-org/synapse.git
-          ls
-          cd synapse
-          source env/bin/activate
-          pip install -e .
-          demo/start.sh --no-rate-limit
+          source .synapse/env/bin/activate
+          pip install -e .synapse
+          .synapse/demo/start.sh --no-rate-limit
 #          python3 -m venv .synapse
 #          source .synapse/bin/activate
 #          pip install synapse matrix-synapse

From 4ef9d089e7335af1a22fce49e4d995831697227b Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Mon, 3 Jan 2022 12:28:51 +0200
Subject: [PATCH 051/581] Github actions improvement test

---
 .github/workflows/integration.yml | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
index 6e7b945336e..1b6f27bcac0 100644
--- a/.github/workflows/integration.yml
+++ b/.github/workflows/integration.yml
@@ -67,9 +67,9 @@ jobs:
       - name: Start synapse server
         run: |
           git clone -b develop https://github.com/matrix-org/synapse.git
-          source .synapse/env/bin/activate
-          pip install -e .synapse
-          .synapse/demo/start.sh --no-rate-limit
+          source synapse/env/bin/activate
+          pip install -e synapse
+          synapse/demo/start.sh --no-rate-limit
 #          python3 -m venv .synapse
 #          source .synapse/bin/activate
 #          pip install synapse matrix-synapse

From b4d5d1320569e1bfda7ba2f1bbe7194cd3653d3f Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Mon, 3 Jan 2022 12:37:22 +0200
Subject: [PATCH 052/581] Github actions improvement test

---
 .github/workflows/integration.yml | 11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)

diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
index 1b6f27bcac0..7011932fd3f 100644
--- a/.github/workflows/integration.yml
+++ b/.github/workflows/integration.yml
@@ -64,12 +64,15 @@ jobs:
 #          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
 #          restore-keys: |
 #            ${{ runner.os }}-gradle-
+      - uses: actions/checkout@develop
+        with:
+          repository: matrix-org/synapse
       - name: Start synapse server
         run: |
-          git clone -b develop https://github.com/matrix-org/synapse.git
-          source synapse/env/bin/activate
-          pip install -e synapse
-          synapse/demo/start.sh --no-rate-limit
+          cd synapse
+          source env/bin/activate
+          pip install -e .
+          demo/start.sh --no-rate-limit
 #          python3 -m venv .synapse
 #          source .synapse/bin/activate
 #          pip install synapse matrix-synapse

From f7a208800931b7bc4376781480b2970753bb1ecb Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Mon, 3 Jan 2022 12:39:33 +0200
Subject: [PATCH 053/581] Github actions improvement test

---
 .github/workflows/integration.yml | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
index 7011932fd3f..561b96b5423 100644
--- a/.github/workflows/integration.yml
+++ b/.github/workflows/integration.yml
@@ -64,9 +64,10 @@ jobs:
 #          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
 #          restore-keys: |
 #            ${{ runner.os }}-gradle-
-      - uses: actions/checkout@develop
+      - uses: actions/checkout@v2
         with:
           repository: matrix-org/synapse
+          ref: develop
       - name: Start synapse server
         run: |
           cd synapse

From ae2dbb808f6c9608ff37a16dc1cbc32936465196 Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Mon, 3 Jan 2022 12:41:44 +0200
Subject: [PATCH 054/581] Github actions improvement test

---
 .github/workflows/integration.yml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
index 561b96b5423..97b69f94cde 100644
--- a/.github/workflows/integration.yml
+++ b/.github/workflows/integration.yml
@@ -70,7 +70,9 @@ jobs:
           ref: develop
       - name: Start synapse server
         run: |
+          pwd
           cd synapse
+          pwd
           source env/bin/activate
           pip install -e .
           demo/start.sh --no-rate-limit

From 7e3a074f8bd697d8966b67d52ebacf759eb9ffb9 Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Mon, 3 Jan 2022 12:45:05 +0200
Subject: [PATCH 055/581] Github actions improvement test

---
 .github/workflows/integration.yml | 58 ++++++++++++++-----------------
 1 file changed, 26 insertions(+), 32 deletions(-)

diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
index 97b69f94cde..9de1b2f4074 100644
--- a/.github/workflows/integration.yml
+++ b/.github/workflows/integration.yml
@@ -56,41 +56,35 @@ jobs:
           restore-keys: |
             ${{ runner.os }}-pip-
             ${{ runner.os }}-
-#      - uses: actions/cache@v2
-#        with:
-#          path: |
-#            ~/.gradle/caches
-#            ~/.gradle/wrapper
-#          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
-#          restore-keys: |
-#            ${{ runner.os }}-gradle-
+      - uses: actions/cache@v2
+        with:
+          path: |
+            ~/.gradle/caches
+            ~/.gradle/wrapper
+          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
+          restore-keys: |
+            ${{ runner.os }}-gradle-
       - uses: actions/checkout@v2
         with:
           repository: matrix-org/synapse
           ref: develop
       - name: Start synapse server
         run: |
-          pwd
-          cd synapse
-          pwd
-          source env/bin/activate
-          pip install -e .
-          demo/start.sh --no-rate-limit
-#          python3 -m venv .synapse
-#          source .synapse/bin/activate
-#          pip install synapse matrix-synapse
-#          curl -sL https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh --no-rate-limit \
-#            | sed s/127.0.0.1/0.0.0.0/g | bash
-#      - name: Run integration tests on API ${{ matrix.api-level }}
-#        uses: reactivecircus/android-emulator-runner@v2
-#        with:
-#          api-level: ${{ matrix.api-level }}
-#          #arch: x86_64
-#          #disable-animations: true
-#          # script: ./gradlew -PallWarningsAsErrors=false vector:connectedAndroidTest matrix-sdk-android:connectedAndroidTest
-#          arch: x86
-#          profile: Nexus 5X
-#          force-avd-creation: false
-#          emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
-#          emulator-build: 7425822
-#          script: ./gradlew $CI_GRADLE_ARG_PROPERTIES -PallWarningsAsErrors=false connectedCheck --stacktrace
+          python3 -m venv .synapse
+          source .synapse/bin/activate
+          pip install synapse matrix-synapse
+          curl -sL https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh \
+            | sed s/127.0.0.1/0.0.0.0/g | bash --no-rate-limit
+      - name: Run integration tests on API ${{ matrix.api-level }}
+        uses: reactivecircus/android-emulator-runner@v2
+        with:
+          api-level: ${{ matrix.api-level }}
+          #arch: x86_64
+          #disable-animations: true
+          # script: ./gradlew -PallWarningsAsErrors=false vector:connectedAndroidTest matrix-sdk-android:connectedAndroidTest
+          arch: x86
+          profile: Nexus 5X
+          force-avd-creation: false
+          emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
+          emulator-build: 7425822
+          script: ./gradlew $CI_GRADLE_ARG_PROPERTIES -PallWarningsAsErrors=false connectedCheck --stacktrace

From 4d6d9181ab42144fd0edc5523c362af6dfb8fdde Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Mon, 3 Jan 2022 12:52:56 +0200
Subject: [PATCH 056/581] Github actions improvement test

---
 .github/workflows/integration.yml | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
index 9de1b2f4074..d22abc6c923 100644
--- a/.github/workflows/integration.yml
+++ b/.github/workflows/integration.yml
@@ -73,8 +73,9 @@ jobs:
           python3 -m venv .synapse
           source .synapse/bin/activate
           pip install synapse matrix-synapse
-          curl -sL https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh \
-            | sed s/127.0.0.1/0.0.0.0/g | bash --no-rate-limit
+#          curl -sL https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh --no-rate-limit \
+#            | sed s/127.0.0.1/0.0.0.0/g | bash
+          curl -s https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh | bash -s --no-rate-limit
       - name: Run integration tests on API ${{ matrix.api-level }}
         uses: reactivecircus/android-emulator-runner@v2
         with:

From 540687cc4b4be82f22d81e005960b3224f35fd58 Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Mon, 3 Jan 2022 12:54:21 +0200
Subject: [PATCH 057/581] Github actions improvement test

---
 .github/workflows/integration.yml | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
index d22abc6c923..c757721a9cf 100644
--- a/.github/workflows/integration.yml
+++ b/.github/workflows/integration.yml
@@ -64,10 +64,6 @@ jobs:
           key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
           restore-keys: |
             ${{ runner.os }}-gradle-
-      - uses: actions/checkout@v2
-        with:
-          repository: matrix-org/synapse
-          ref: develop
       - name: Start synapse server
         run: |
           python3 -m venv .synapse

From c8e4fad5c88a0bb752a825560f53338102e98e59 Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Mon, 3 Jan 2022 12:57:19 +0200
Subject: [PATCH 058/581] Github actions improvement test

---
 .github/workflows/integration.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
index c757721a9cf..c0b6755e417 100644
--- a/.github/workflows/integration.yml
+++ b/.github/workflows/integration.yml
@@ -71,7 +71,7 @@ jobs:
           pip install synapse matrix-synapse
 #          curl -sL https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh --no-rate-limit \
 #            | sed s/127.0.0.1/0.0.0.0/g | bash
-          curl -s https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh | bash -s --no-rate-limit
+          curl -sL https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh | bash -s --no-rate-limit
       - name: Run integration tests on API ${{ matrix.api-level }}
         uses: reactivecircus/android-emulator-runner@v2
         with:

From bb85e9c0c278aab30d43df543f6079d38d1db885 Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Mon, 3 Jan 2022 12:57:54 +0200
Subject: [PATCH 059/581] Github actions improvement test

---
 .github/workflows/integration.yml | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
index c0b6755e417..9ec4bbf4cf7 100644
--- a/.github/workflows/integration.yml
+++ b/.github/workflows/integration.yml
@@ -69,9 +69,9 @@ jobs:
           python3 -m venv .synapse
           source .synapse/bin/activate
           pip install synapse matrix-synapse
-#          curl -sL https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh --no-rate-limit \
-#            | sed s/127.0.0.1/0.0.0.0/g | bash
-          curl -sL https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh | bash -s --no-rate-limit
+          curl -sL https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh --no-rate-limit \
+            | sed s/127.0.0.1/0.0.0.0/g | bash
+#          curl -sL https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh | bash -s --no-rate-limit
       - name: Run integration tests on API ${{ matrix.api-level }}
         uses: reactivecircus/android-emulator-runner@v2
         with:

From 1127a2692875dad325a5959544b539314798c92b Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Mon, 3 Jan 2022 12:58:52 +0200
Subject: [PATCH 060/581] Github actions improvement test

---
 .github/workflows/integration.yml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
index 9ec4bbf4cf7..47aa740546d 100644
--- a/.github/workflows/integration.yml
+++ b/.github/workflows/integration.yml
@@ -69,8 +69,8 @@ jobs:
           python3 -m venv .synapse
           source .synapse/bin/activate
           pip install synapse matrix-synapse
-          curl -sL https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh --no-rate-limit \
-            | sed s/127.0.0.1/0.0.0.0/g | bash
+          curl -sL https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh \
+            | sed s/127.0.0.1/0.0.0.0/g | bash -s --no-rate-limit
 #          curl -sL https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh | bash -s --no-rate-limit
       - name: Run integration tests on API ${{ matrix.api-level }}
         uses: reactivecircus/android-emulator-runner@v2

From 5e282533b6a483d050ccbd04c89fb47152fe68ca Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Mon, 3 Jan 2022 13:02:05 +0200
Subject: [PATCH 061/581] Github actions improvement test

---
 .github/workflows/integration.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
index 47aa740546d..d5efa8c8a2e 100644
--- a/.github/workflows/integration.yml
+++ b/.github/workflows/integration.yml
@@ -70,7 +70,7 @@ jobs:
           source .synapse/bin/activate
           pip install synapse matrix-synapse
           curl -sL https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh \
-            | sed s/127.0.0.1/0.0.0.0/g | bash -s --no-rate-limit
+            | sed s/127.0.0.1/0.0.0.0/g | bash -sL --no-rate-limit
 #          curl -sL https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh | bash -s --no-rate-limit
       - name: Run integration tests on API ${{ matrix.api-level }}
         uses: reactivecircus/android-emulator-runner@v2

From c14420378bca005c943210257f461e82962890aa Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Mon, 3 Jan 2022 13:06:13 +0200
Subject: [PATCH 062/581] Github actions improvement test

---
 .github/workflows/integration.yml | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
index d5efa8c8a2e..16361c7636b 100644
--- a/.github/workflows/integration.yml
+++ b/.github/workflows/integration.yml
@@ -69,8 +69,7 @@ jobs:
           python3 -m venv .synapse
           source .synapse/bin/activate
           pip install synapse matrix-synapse
-          curl -sL https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh \
-            | sed s/127.0.0.1/0.0.0.0/g | bash -sL --no-rate-limit
+          curl -sL https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh | bash -sL --no-rate-limit
 #          curl -sL https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh | bash -s --no-rate-limit
       - name: Run integration tests on API ${{ matrix.api-level }}
         uses: reactivecircus/android-emulator-runner@v2

From aadbf69f3af24900344b6352e19e79688142681a Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Mon, 3 Jan 2022 13:13:46 +0200
Subject: [PATCH 063/581] Github actions improvement test

---
 .github/workflows/integration.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
index 16361c7636b..1f087e1ff12 100644
--- a/.github/workflows/integration.yml
+++ b/.github/workflows/integration.yml
@@ -69,7 +69,7 @@ jobs:
           python3 -m venv .synapse
           source .synapse/bin/activate
           pip install synapse matrix-synapse
-          curl -sL https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh | bash -sL --no-rate-limit
+          curl -sL https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh | bash -s --no-rate-limit
 #          curl -sL https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh | bash -s --no-rate-limit
       - name: Run integration tests on API ${{ matrix.api-level }}
         uses: reactivecircus/android-emulator-runner@v2

From e482ef4262aef95a9dfa1b9f0ede967032c31e2e Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Mon, 3 Jan 2022 16:51:12 +0200
Subject: [PATCH 064/581] First local thread integration test

---
 .github/workflows/integration.yml             |   4 +-
 .../android/sdk/common/CommonTestHelper.kt    |  59 +++++++++-
 .../threads/GenerateThreadMessageTests.kt     | 107 ++++++++++++++++++
 .../sdk/session/room/threads/RetryTestRule.kt |  58 ++++++++++
 4 files changed, 224 insertions(+), 4 deletions(-)
 create mode 100644 matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/GenerateThreadMessageTests.kt
 create mode 100644 matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/RetryTestRule.kt

diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
index 1f087e1ff12..c18ca69fdee 100644
--- a/.github/workflows/integration.yml
+++ b/.github/workflows/integration.yml
@@ -69,8 +69,8 @@ jobs:
           python3 -m venv .synapse
           source .synapse/bin/activate
           pip install synapse matrix-synapse
-          curl -sL https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh | bash -s --no-rate-limit
-#          curl -sL https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh | bash -s --no-rate-limit
+          curl -sL https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh --no-rate-limit \
+            | sed s/127.0.0.1/0.0.0.0/g | bash
       - name: Run integration tests on API ${{ matrix.api-level }}
         uses: reactivecircus/android-emulator-runner@v2
         with:
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt
index 8e21828562f..0edd8c52dfe 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt
@@ -184,18 +184,73 @@ class CommonTestHelper(context: Context) {
     /**
      * Will send nb of messages provided by count parameter but waits a bit every 10 messages to avoid gap in sync
      */
-    private fun sendTextMessagesBatched(room: Room, message: String, count: Int) {
+    private fun sendTextMessagesBatched(room: Room, message: String, count: Int, rootThreadEventId: String? = null) {
         (1 until count + 1)
                 .map { "$message #$it" }
                 .chunked(10)
                 .forEach { batchedMessages ->
                     batchedMessages.forEach { formattedMessage ->
-                        room.sendTextMessage(formattedMessage)
+                        if (rootThreadEventId != null) {
+                            room.replyInThread(
+                                    rootThreadEventId = rootThreadEventId,
+                                    replyInThreadText = formattedMessage)
+                        } else {
+                            room.sendTextMessage(formattedMessage)
+                        }
                     }
                     Thread.sleep(1_000L)
                 }
     }
 
+    /**
+     * Reply in a thread
+     * @param room         the room where to send the messages
+     * @param message      the message to send
+     * @param numberOfMessages the number of time the message will be sent
+     */
+    fun replyInThreadMessage(
+            room: Room,
+            message: String,
+            numberOfMessages: Int,
+            rootThreadEventId: String,
+            timeout: Long = TestConstants.timeOutMillis): List {
+
+        val sentEvents = ArrayList(numberOfMessages)
+        val timeline = room.createTimeline(null, TimelineSettings(10))
+        timeline.start()
+        waitWithLatch(timeout + 1_000L * numberOfMessages) { latch ->
+            val timelineListener = object : Timeline.Listener {
+                override fun onTimelineFailure(throwable: Throwable) {
+                }
+
+                override fun onNewTimelineEvents(eventIds: List) {
+                    // noop
+                }
+
+                override fun onTimelineUpdated(snapshot: List) {
+                    val newMessages = snapshot
+                            .filter { it.root.sendState == SendState.SYNCED }
+                            .filter { it.root.getClearType() == EventType.MESSAGE }
+                            .filter { it.root.getClearContent().toModel()?.body?.startsWith(message) == true }
+
+                    Timber.v("New synced message size: ${newMessages.size}")
+                    if (newMessages.size == numberOfMessages) {
+                        sentEvents.addAll(newMessages)
+                        // Remove listener now, if not at the next update sendEvents could change
+                        timeline.removeListener(this)
+                        latch.countDown()
+                    }
+                }
+            }
+            timeline.addListener(timelineListener)
+            sendTextMessagesBatched(room, message, numberOfMessages, rootThreadEventId)
+        }
+        timeline.dispose()
+        // Check that all events has been created
+        assertEquals("Message number do not match $sentEvents", numberOfMessages.toLong(), sentEvents.size.toLong())
+        return sentEvents
+    }
+
     // PRIVATE METHODS *****************************************************************************
 
     /**
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/GenerateThreadMessageTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/GenerateThreadMessageTests.kt
new file mode 100644
index 00000000000..79f5e8314d4
--- /dev/null
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/GenerateThreadMessageTests.kt
@@ -0,0 +1,107 @@
+/*
+ * 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.session.room.threads
+
+import org.amshove.kluent.shouldBe
+import org.amshove.kluent.shouldBeEqualTo
+import org.amshove.kluent.shouldBeFalse
+import org.amshove.kluent.shouldBeNull
+import org.amshove.kluent.shouldBeTrue
+import org.junit.Assert
+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.events.model.getRootThreadEventId
+import org.matrix.android.sdk.api.session.events.model.isTextMessage
+import org.matrix.android.sdk.api.session.events.model.isThread
+import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
+import org.matrix.android.sdk.common.CommonTestHelper
+import org.matrix.android.sdk.common.CryptoTestHelper
+import timber.log.Timber
+import java.util.concurrent.CountDownLatch
+
+@RunWith(JUnit4::class)
+@FixMethodOrder(MethodSorters.JVM)
+class GenerateThreadMessageTests : InstrumentedTest {
+
+    private val commonTestHelper = CommonTestHelper(context())
+    private val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
+
+    private val logPrefix = "---Test--> "
+
+//    @Rule
+//    @JvmField
+//    val mRetryTestRule = RetryTestRule()
+
+    @Test
+    fun reply_in_thread_to_normal_timeline_message_should_create_a_thread() {
+        val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceInARoom(false)
+
+        val aliceSession = cryptoTestData.firstSession
+        val aliceRoomId = cryptoTestData.roomId
+
+        val aliceRoom = aliceSession.getRoom(aliceRoomId)!!
+
+        // Let's send a message in the normal timeline
+        val textMessage = "This is a normal timeline message"
+        val sentMessages = commonTestHelper.sendTextMessage(
+                room = aliceRoom,
+                message = textMessage,
+                nbOfMessages = 1)
+
+        val initMessage = sentMessages.first()
+
+        initMessage.root.isThread().shouldBeFalse()
+        initMessage.root.isTextMessage().shouldBeTrue()
+        initMessage.root.getRootThreadEventId().shouldBeNull()
+        initMessage.root.threadDetails?.isRootThread?.shouldBeFalse()
+
+        // Let's reply in timeline to that message
+        val repliesInThread = commonTestHelper.replyInThreadMessage(
+                room = aliceRoom,
+                message = "Reply In the above thread",
+                numberOfMessages = 1,
+                rootThreadEventId = initMessage.root.eventId.orEmpty())
+
+        val replyInThread = repliesInThread.first()
+        replyInThread.root.isThread().shouldBeTrue()
+        replyInThread.root.isTextMessage().shouldBeTrue()
+        replyInThread.root.getRootThreadEventId().shouldBeEqualTo(initMessage.root.eventId)
+
+        // The init normal message should now be a root thread event
+        val timeline = aliceRoom.createTimeline(null, TimelineSettings(30))
+        timeline.start()
+
+        aliceSession.startSync(true)
+        run {
+            val lock = CountDownLatch(1)
+            val eventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
+                val initMessageThreadDetails = snapshot.firstOrNull { it.root.eventId == initMessage.root.eventId }?.root?.threadDetails
+                initMessageThreadDetails?.isRootThread?.shouldBeTrue() ?: assert(false)
+                initMessageThreadDetails?.numberOfThreads?.shouldBe(1)
+                Timber.e("$logPrefix $initMessageThreadDetails")
+                true
+            }
+            timeline.addListener(eventsListener)
+            commonTestHelper.await(lock, 600_000)
+        }
+        aliceSession.stopSync()
+    }
+}
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/RetryTestRule.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/RetryTestRule.kt
new file mode 100644
index 00000000000..099491655fa
--- /dev/null
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/RetryTestRule.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.session.room.threads
+
+import android.util.Log
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * Retry test rule used to retry test that failed.
+ * Retry failed test 3 times
+ */
+class RetryTestRule(val retryCount: Int = 3) : TestRule {
+
+    private val TAG = RetryTestRule::class.java.simpleName
+
+    override fun apply(base: Statement, description: Description): Statement {
+        return statement(base, description)
+    }
+
+    private fun statement(base: Statement, description: Description): Statement {
+        return object : Statement() {
+            @Throws(Throwable::class)
+            override fun evaluate() {
+                var caughtThrowable: Throwable? = null
+
+                // implement retry logic here
+                for (i in 0 until retryCount) {
+                    try {
+                        base.evaluate()
+                        return
+                    } catch (t: Throwable) {
+                        caughtThrowable = t
+                        Log.e(TAG, description.displayName + ": run " + (i + 1) + " failed")
+                    }
+                }
+
+                Log.e(TAG, description.displayName + ": giving up after " + retryCount + " failures")
+                throw caughtThrowable!!
+            }
+        }
+    }
+}

From b67199eb07707e7f22fd40b67c537f06dd418df4 Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Mon, 3 Jan 2022 18:12:16 +0200
Subject: [PATCH 065/581] Github actions test

---
 .github/workflows/integration.yml | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
index c18ca69fdee..5b44bfce32d 100644
--- a/.github/workflows/integration.yml
+++ b/.github/workflows/integration.yml
@@ -69,8 +69,13 @@ jobs:
           python3 -m venv .synapse
           source .synapse/bin/activate
           pip install synapse matrix-synapse
-          curl -sL https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh --no-rate-limit \
-            | sed s/127.0.0.1/0.0.0.0/g | bash
+          pwd
+          cd synapse
+          pwd
+          curl https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh -o start.sh
+          ls
+#          curl -sL https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh --no-rate-limit \
+#            | sed s/127.0.0.1/0.0.0.0/g | bash
       - name: Run integration tests on API ${{ matrix.api-level }}
         uses: reactivecircus/android-emulator-runner@v2
         with:

From e7dfdce057f16d45a662d9edde70be7368957b34 Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Mon, 3 Jan 2022 18:15:41 +0200
Subject: [PATCH 066/581] Github actions test

---
 .github/workflows/integration.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
index 5b44bfce32d..195f7347b03 100644
--- a/.github/workflows/integration.yml
+++ b/.github/workflows/integration.yml
@@ -70,7 +70,7 @@ jobs:
           source .synapse/bin/activate
           pip install synapse matrix-synapse
           pwd
-          cd synapse
+          cd .synapse
           pwd
           curl https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh -o start.sh
           ls

From 70d1c15b89ca162532e9ce2ffdf81da1bb5777d7 Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Mon, 3 Jan 2022 18:21:52 +0200
Subject: [PATCH 067/581] Github actions test

---
 .github/workflows/integration.yml | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
index 195f7347b03..f27f71eee1c 100644
--- a/.github/workflows/integration.yml
+++ b/.github/workflows/integration.yml
@@ -69,11 +69,8 @@ jobs:
           python3 -m venv .synapse
           source .synapse/bin/activate
           pip install synapse matrix-synapse
-          pwd
-          cd .synapse
-          pwd
           curl https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh -o start.sh
-          ls
+          ./start.sh --no-rate-limit
 #          curl -sL https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh --no-rate-limit \
 #            | sed s/127.0.0.1/0.0.0.0/g | bash
       - name: Run integration tests on API ${{ matrix.api-level }}

From 1b2ce33f7aa6600734db05fe74db3e9771b75260 Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Mon, 3 Jan 2022 18:24:43 +0200
Subject: [PATCH 068/581] Github actions test

---
 .github/workflows/integration.yml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
index f27f71eee1c..0ba0ad904fe 100644
--- a/.github/workflows/integration.yml
+++ b/.github/workflows/integration.yml
@@ -70,6 +70,7 @@ jobs:
           source .synapse/bin/activate
           pip install synapse matrix-synapse
           curl https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh -o start.sh
+          chmod 777 start.sh
           ./start.sh --no-rate-limit
 #          curl -sL https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh --no-rate-limit \
 #            | sed s/127.0.0.1/0.0.0.0/g | bash

From 929cc29f778e020305e6bacc2064e4b35db21bb9 Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Mon, 3 Jan 2022 19:18:51 +0200
Subject: [PATCH 069/581] Update copyright

---
 .../matrix/android/sdk/session/room/threads/RetryTestRule.kt    | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/RetryTestRule.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/RetryTestRule.kt
index 099491655fa..c06a18aeb3f 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/RetryTestRule.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/RetryTestRule.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 New Vector Ltd
+ * 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 3ef960c4c38e0dc812eb907538f22d97f2da44b7 Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Mon, 3 Jan 2022 19:45:48 +0200
Subject: [PATCH 070/581] Update copyright

---
 .../sdk/session/room/threads/GenerateThreadMessageTests.kt      | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/GenerateThreadMessageTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/GenerateThreadMessageTests.kt
index 79f5e8314d4..e6980c0304b 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/GenerateThreadMessageTests.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/GenerateThreadMessageTests.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 New Vector Ltd
+ * 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 925c1671a6d47ee3dd9871d25c8dc06d75d677ee Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Mon, 3 Jan 2022 21:09:36 +0200
Subject: [PATCH 071/581] Add more integrations tests for threads

---
 .../threads/GenerateThreadMessageTests.kt     | 107 ------
 .../room/threads/ThreadMessagingTest.kt       | 341 ++++++++++++++++++
 2 files changed, 341 insertions(+), 107 deletions(-)
 delete mode 100644 matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/GenerateThreadMessageTests.kt
 create mode 100644 matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/ThreadMessagingTest.kt

diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/GenerateThreadMessageTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/GenerateThreadMessageTests.kt
deleted file mode 100644
index e6980c0304b..00000000000
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/GenerateThreadMessageTests.kt
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * 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.session.room.threads
-
-import org.amshove.kluent.shouldBe
-import org.amshove.kluent.shouldBeEqualTo
-import org.amshove.kluent.shouldBeFalse
-import org.amshove.kluent.shouldBeNull
-import org.amshove.kluent.shouldBeTrue
-import org.junit.Assert
-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.events.model.getRootThreadEventId
-import org.matrix.android.sdk.api.session.events.model.isTextMessage
-import org.matrix.android.sdk.api.session.events.model.isThread
-import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
-import org.matrix.android.sdk.common.CommonTestHelper
-import org.matrix.android.sdk.common.CryptoTestHelper
-import timber.log.Timber
-import java.util.concurrent.CountDownLatch
-
-@RunWith(JUnit4::class)
-@FixMethodOrder(MethodSorters.JVM)
-class GenerateThreadMessageTests : InstrumentedTest {
-
-    private val commonTestHelper = CommonTestHelper(context())
-    private val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
-
-    private val logPrefix = "---Test--> "
-
-//    @Rule
-//    @JvmField
-//    val mRetryTestRule = RetryTestRule()
-
-    @Test
-    fun reply_in_thread_to_normal_timeline_message_should_create_a_thread() {
-        val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceInARoom(false)
-
-        val aliceSession = cryptoTestData.firstSession
-        val aliceRoomId = cryptoTestData.roomId
-
-        val aliceRoom = aliceSession.getRoom(aliceRoomId)!!
-
-        // Let's send a message in the normal timeline
-        val textMessage = "This is a normal timeline message"
-        val sentMessages = commonTestHelper.sendTextMessage(
-                room = aliceRoom,
-                message = textMessage,
-                nbOfMessages = 1)
-
-        val initMessage = sentMessages.first()
-
-        initMessage.root.isThread().shouldBeFalse()
-        initMessage.root.isTextMessage().shouldBeTrue()
-        initMessage.root.getRootThreadEventId().shouldBeNull()
-        initMessage.root.threadDetails?.isRootThread?.shouldBeFalse()
-
-        // Let's reply in timeline to that message
-        val repliesInThread = commonTestHelper.replyInThreadMessage(
-                room = aliceRoom,
-                message = "Reply In the above thread",
-                numberOfMessages = 1,
-                rootThreadEventId = initMessage.root.eventId.orEmpty())
-
-        val replyInThread = repliesInThread.first()
-        replyInThread.root.isThread().shouldBeTrue()
-        replyInThread.root.isTextMessage().shouldBeTrue()
-        replyInThread.root.getRootThreadEventId().shouldBeEqualTo(initMessage.root.eventId)
-
-        // The init normal message should now be a root thread event
-        val timeline = aliceRoom.createTimeline(null, TimelineSettings(30))
-        timeline.start()
-
-        aliceSession.startSync(true)
-        run {
-            val lock = CountDownLatch(1)
-            val eventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
-                val initMessageThreadDetails = snapshot.firstOrNull { it.root.eventId == initMessage.root.eventId }?.root?.threadDetails
-                initMessageThreadDetails?.isRootThread?.shouldBeTrue() ?: assert(false)
-                initMessageThreadDetails?.numberOfThreads?.shouldBe(1)
-                Timber.e("$logPrefix $initMessageThreadDetails")
-                true
-            }
-            timeline.addListener(eventsListener)
-            commonTestHelper.await(lock, 600_000)
-        }
-        aliceSession.stopSync()
-    }
-}
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/ThreadMessagingTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/ThreadMessagingTest.kt
new file mode 100644
index 00000000000..0fdcfb28702
--- /dev/null
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/ThreadMessagingTest.kt
@@ -0,0 +1,341 @@
+/*
+ * 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.session.room.threads
+
+import org.amshove.kluent.shouldBe
+import org.amshove.kluent.shouldBeEqualTo
+import org.amshove.kluent.shouldBeFalse
+import org.amshove.kluent.shouldBeNull
+import org.amshove.kluent.shouldBeTrue
+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.events.model.getRootThreadEventId
+import org.matrix.android.sdk.api.session.events.model.isTextMessage
+import org.matrix.android.sdk.api.session.events.model.isThread
+import org.matrix.android.sdk.api.session.room.timeline.Timeline
+import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
+import org.matrix.android.sdk.common.CommonTestHelper
+import org.matrix.android.sdk.common.CryptoTestHelper
+import java.util.concurrent.CountDownLatch
+
+@RunWith(JUnit4::class)
+@FixMethodOrder(MethodSorters.JVM)
+class ThreadMessagingTest : InstrumentedTest {
+
+    private val commonTestHelper = CommonTestHelper(context())
+    private val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
+
+//    @Rule
+//    @JvmField
+//    val mRetryTestRule = RetryTestRule()
+
+    @Test
+    fun reply_in_thread_should_create_a_thread() {
+        val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceInARoom(false)
+
+        val aliceSession = cryptoTestData.firstSession
+        val aliceRoomId = cryptoTestData.roomId
+
+        val aliceRoom = aliceSession.getRoom(aliceRoomId)!!
+
+        // Let's send a message in the normal timeline
+        val textMessage = "This is a normal timeline message"
+        val sentMessages = commonTestHelper.sendTextMessage(
+                room = aliceRoom,
+                message = textMessage,
+                nbOfMessages = 1)
+
+        val initMessage = sentMessages.first()
+
+        initMessage.root.isThread().shouldBeFalse()
+        initMessage.root.isTextMessage().shouldBeTrue()
+        initMessage.root.getRootThreadEventId().shouldBeNull()
+        initMessage.root.threadDetails?.isRootThread?.shouldBeFalse()
+
+        // Let's reply in timeline to that message
+        val repliesInThread = commonTestHelper.replyInThreadMessage(
+                room = aliceRoom,
+                message = "Reply In the above thread",
+                numberOfMessages = 1,
+                rootThreadEventId = initMessage.root.eventId.orEmpty())
+
+        val replyInThread = repliesInThread.first()
+        replyInThread.root.isThread().shouldBeTrue()
+        replyInThread.root.isTextMessage().shouldBeTrue()
+        replyInThread.root.getRootThreadEventId().shouldBeEqualTo(initMessage.root.eventId)
+
+        // The init normal message should now be a root thread event
+        val timeline = aliceRoom.createTimeline(null, TimelineSettings(30))
+        timeline.start()
+
+        aliceSession.startSync(true)
+        run {
+            val lock = CountDownLatch(1)
+            val eventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
+                val initMessageThreadDetails = snapshot.firstOrNull {
+                    it.root.eventId == initMessage.root.eventId
+                }?.root?.threadDetails
+                initMessageThreadDetails?.isRootThread?.shouldBeTrue()
+                initMessageThreadDetails?.numberOfThreads?.shouldBe(1)
+                true
+            }
+            timeline.addListener(eventsListener)
+            commonTestHelper.await(lock, 600_000)
+        }
+        aliceSession.stopSync()
+    }
+
+    @Test
+    fun reply_in_thread_should_create_a_thread_from_other_user() {
+        val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(false)
+
+        val aliceSession = cryptoTestData.firstSession
+        val aliceRoomId = cryptoTestData.roomId
+        val aliceRoom = aliceSession.getRoom(aliceRoomId)!!
+
+        // Let's send a message in the normal timeline
+        val textMessage = "This is a normal timeline message"
+        val sentMessages = commonTestHelper.sendTextMessage(
+                room = aliceRoom,
+                message = textMessage,
+                nbOfMessages = 1)
+
+        val initMessage = sentMessages.first()
+
+        initMessage.root.isThread().shouldBeFalse()
+        initMessage.root.isTextMessage().shouldBeTrue()
+        initMessage.root.getRootThreadEventId().shouldBeNull()
+        initMessage.root.threadDetails?.isRootThread?.shouldBeFalse()
+
+        // Let's reply in timeline to that message from another user
+        val bobSession = cryptoTestData.secondSession!!
+        val bobRoomId = cryptoTestData.roomId
+        val bobRoom = bobSession.getRoom(bobRoomId)!!
+
+        val repliesInThread = commonTestHelper.replyInThreadMessage(
+                room = bobRoom,
+                message = "Reply In the above thread",
+                numberOfMessages = 1,
+                rootThreadEventId = initMessage.root.eventId.orEmpty())
+
+        val replyInThread = repliesInThread.first()
+        replyInThread.root.isThread().shouldBeTrue()
+        replyInThread.root.isTextMessage().shouldBeTrue()
+        replyInThread.root.getRootThreadEventId().shouldBeEqualTo(initMessage.root.eventId)
+
+        // The init normal message should now be a root thread event
+        val timeline = aliceRoom.createTimeline(null, TimelineSettings(30))
+        timeline.start()
+
+        aliceSession.startSync(true)
+        run {
+            val lock = CountDownLatch(1)
+            val eventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
+                val initMessageThreadDetails = snapshot.firstOrNull { it.root.eventId == initMessage.root.eventId }?.root?.threadDetails
+                initMessageThreadDetails?.isRootThread?.shouldBeTrue()
+                initMessageThreadDetails?.numberOfThreads?.shouldBe(1)
+                true
+            }
+            timeline.addListener(eventsListener)
+            commonTestHelper.await(lock, 600_000)
+        }
+        aliceSession.stopSync()
+
+        bobSession.startSync(true)
+        run {
+            val lock = CountDownLatch(1)
+            val eventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
+                val initMessageThreadDetails = snapshot.firstOrNull { it.root.eventId == initMessage.root.eventId }?.root?.threadDetails
+                initMessageThreadDetails?.isRootThread?.shouldBeTrue()
+                initMessageThreadDetails?.numberOfThreads?.shouldBe(1)
+                true
+            }
+            timeline.addListener(eventsListener)
+            commonTestHelper.await(lock, 600_000)
+        }
+        bobSession.stopSync()
+    }
+
+    @Test
+    fun reply_in_thread_to_timeline_message_multiple_times() {
+        val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceInARoom(false)
+
+        val aliceSession = cryptoTestData.firstSession
+        val aliceRoomId = cryptoTestData.roomId
+
+        val aliceRoom = aliceSession.getRoom(aliceRoomId)!!
+
+        // Let's send 5 messages in the normal timeline
+        val textMessage = "This is a normal timeline message"
+        val sentMessages = commonTestHelper.sendTextMessage(
+                room = aliceRoom,
+                message = textMessage,
+                nbOfMessages = 5)
+
+        sentMessages.forEach {
+            it.root.isThread().shouldBeFalse()
+            it.root.isTextMessage().shouldBeTrue()
+            it.root.getRootThreadEventId().shouldBeNull()
+            it.root.threadDetails?.isRootThread?.shouldBeFalse()
+        }
+        // let's start the thread from the second message
+        val selectedInitMessage = sentMessages[1]
+
+        // Let's reply 40 times in the timeline to the second message
+        val repliesInThread = commonTestHelper.replyInThreadMessage(
+                room = aliceRoom,
+                message = "Reply In the above thread",
+                numberOfMessages = 40,
+                rootThreadEventId = selectedInitMessage.root.eventId.orEmpty())
+
+        repliesInThread.forEach {
+            it.root.isThread().shouldBeTrue()
+            it.root.isTextMessage().shouldBeTrue()
+            it.root.getRootThreadEventId()?.shouldBeEqualTo(selectedInitMessage.root.eventId.orEmpty()) ?: assert(false)
+        }
+
+        // The init normal message should now be a root thread event
+        val timeline = aliceRoom.createTimeline(null, TimelineSettings(30))
+        timeline.start()
+
+        aliceSession.startSync(true)
+        run {
+            val lock = CountDownLatch(1)
+            val eventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
+                val initMessageThreadDetails = snapshot.firstOrNull { it.root.eventId == selectedInitMessage.root.eventId }?.root?.threadDetails
+                // Selected init message should be the thread root
+                initMessageThreadDetails?.isRootThread?.shouldBeTrue()
+                // All threads should be 40
+                initMessageThreadDetails?.numberOfThreads?.shouldBeEqualTo(40)
+                true
+            }
+            // Because we sent more than 30 messages we should paginate a bit more
+            timeline.paginate(Timeline.Direction.BACKWARDS, 50)
+            timeline.addListener(eventsListener)
+            commonTestHelper.await(lock, 600_000)
+        }
+        aliceSession.stopSync()
+    }
+
+    @Test
+    fun thread_summary_advanced_validation_after_multiple_messages_in_multiple_threads() {
+        val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(false)
+
+        val aliceSession = cryptoTestData.firstSession
+        val aliceRoomId = cryptoTestData.roomId
+
+        val aliceRoom = aliceSession.getRoom(aliceRoomId)!!
+
+        // Let's send 5 messages in the normal timeline
+        val textMessage = "This is a normal timeline message"
+        val sentMessages = commonTestHelper.sendTextMessage(
+                room = aliceRoom,
+                message = textMessage,
+                nbOfMessages = 5)
+
+        sentMessages.forEach {
+            it.root.isThread().shouldBeFalse()
+            it.root.isTextMessage().shouldBeTrue()
+            it.root.getRootThreadEventId().shouldBeNull()
+            it.root.threadDetails?.isRootThread?.shouldBeFalse()
+        }
+        // let's start the thread from the second message
+        val firstMessage = sentMessages[0]
+        val secondMessage = sentMessages[1]
+
+
+        // Alice will reply in thread to the second message 35 times
+        val aliceThreadRepliesInSecondMessage = commonTestHelper.replyInThreadMessage(
+                room = aliceRoom,
+                message = "Alice reply In the above second thread message",
+                numberOfMessages = 35,
+                rootThreadEventId = secondMessage.root.eventId.orEmpty())
+
+        // Let's reply in timeline to that message from another user
+        val bobSession = cryptoTestData.secondSession!!
+        val bobRoomId = cryptoTestData.roomId
+        val bobRoom = bobSession.getRoom(bobRoomId)!!
+
+        // Bob will reply in thread to the first message 35 times
+        val bobThreadRepliesInFirstMessage = commonTestHelper.replyInThreadMessage(
+                room = bobRoom,
+                message = "Bob reply In the above first thread message",
+                numberOfMessages = 42,
+                rootThreadEventId = firstMessage.root.eventId.orEmpty())
+
+        // Bob will also reply in second thread 5 times
+        val bobThreadRepliesInSecondMessage = commonTestHelper.replyInThreadMessage(
+                room = bobRoom,
+                message = "Another Bob reply In the above second thread message",
+                numberOfMessages = 20,
+                rootThreadEventId = secondMessage.root.eventId.orEmpty())
+
+
+        aliceThreadRepliesInSecondMessage.forEach {
+            it.root.isThread().shouldBeTrue()
+            it.root.isTextMessage().shouldBeTrue()
+            it.root.getRootThreadEventId()?.shouldBeEqualTo(secondMessage.root.eventId.orEmpty()) ?: assert(false)
+        }
+
+        bobThreadRepliesInFirstMessage.forEach {
+            it.root.isThread().shouldBeTrue()
+            it.root.isTextMessage().shouldBeTrue()
+            it.root.getRootThreadEventId()?.shouldBeEqualTo(firstMessage.root.eventId.orEmpty()) ?: assert(false)
+        }
+
+        bobThreadRepliesInSecondMessage.forEach {
+            it.root.isThread().shouldBeTrue()
+            it.root.isTextMessage().shouldBeTrue()
+            it.root.getRootThreadEventId()?.shouldBeEqualTo(secondMessage.root.eventId.orEmpty()) ?: assert(false)
+        }
+
+        // The init normal message should now be a root thread event
+        val timeline = aliceRoom.createTimeline(null, TimelineSettings(30))
+        timeline.start()
+
+        aliceSession.startSync(true)
+        run {
+            val lock = CountDownLatch(1)
+            val eventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
+                val firstMessageThreadDetails = snapshot.firstOrNull { it.root.eventId == firstMessage.root.eventId }?.root?.threadDetails
+                val secondMessageThreadDetails = snapshot.firstOrNull { it.root.eventId == secondMessage.root.eventId }?.root?.threadDetails
+
+                // first & second message should be the thread root
+                firstMessageThreadDetails?.isRootThread?.shouldBeTrue()
+                secondMessageThreadDetails?.isRootThread?.shouldBeTrue()
+
+                // First thread message should contain 42
+                firstMessageThreadDetails?.numberOfThreads shouldBeEqualTo 42
+                // Second thread message should contain 35+20
+                secondMessageThreadDetails?.numberOfThreads shouldBeEqualTo 55
+
+                true
+            }
+            // Because we sent more than 30 messages we should paginate a bit more
+            timeline.paginate(Timeline.Direction.BACKWARDS, 50)
+            timeline.paginate(Timeline.Direction.BACKWARDS, 50)
+            timeline.addListener(eventsListener)
+            commonTestHelper.await(lock, 600_000)
+        }
+        aliceSession.stopSync()
+    }
+
+}

From 42002b80edc6200127c553752fec832970565969 Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Tue, 4 Jan 2022 00:23:34 +0200
Subject: [PATCH 072/581] Github actions test

---
 .github/workflows/integration.yml | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
index 0ba0ad904fe..7504d825c5c 100644
--- a/.github/workflows/integration.yml
+++ b/.github/workflows/integration.yml
@@ -86,4 +86,5 @@ jobs:
           force-avd-creation: false
           emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
           emulator-build: 7425822
-          script: ./gradlew $CI_GRADLE_ARG_PROPERTIES -PallWarningsAsErrors=false connectedCheck --stacktrace
+          script: ./gradlew $CI_GRADLE_ARG_PROPERTIES -Pandroid.testInstrumentationRunnerArguments.class=org.matrix.android.sdk.session.room.threads.ThreadMessagingTest matrix-sdk-android:connectedAndroidTest --info
+#          script: ./gradlew $CI_GRADLE_ARG_PROPERTIES -PallWarningsAsErrors=false connectedCheck --stacktrace

From da8ec4debf2cc0d790dcfc5bf176bd0149719f32 Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Tue, 4 Jan 2022 00:25:52 +0200
Subject: [PATCH 073/581] Github actions test

---
 .github/workflows/integration.yml | 2 --
 1 file changed, 2 deletions(-)

diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
index 7504d825c5c..3fac485fee2 100644
--- a/.github/workflows/integration.yml
+++ b/.github/workflows/integration.yml
@@ -72,8 +72,6 @@ jobs:
           curl https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh -o start.sh
           chmod 777 start.sh
           ./start.sh --no-rate-limit
-#          curl -sL https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh --no-rate-limit \
-#            | sed s/127.0.0.1/0.0.0.0/g | bash
       - name: Run integration tests on API ${{ matrix.api-level }}
         uses: reactivecircus/android-emulator-runner@v2
         with:

From 948f75b215dfd2a2900fe4b7994e0282e41fdbf4 Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Tue, 4 Jan 2022 00:30:51 +0200
Subject: [PATCH 074/581] Github actions test

---
 .github/workflows/integration.yml | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
index 3fac485fee2..598ba19bc92 100644
--- a/.github/workflows/integration.yml
+++ b/.github/workflows/integration.yml
@@ -84,5 +84,6 @@ jobs:
           force-avd-creation: false
           emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
           emulator-build: 7425822
-          script: ./gradlew $CI_GRADLE_ARG_PROPERTIES -Pandroid.testInstrumentationRunnerArguments.class=org.matrix.android.sdk.session.room.threads.ThreadMessagingTest matrix-sdk-android:connectedAndroidTest --info
 #          script: ./gradlew $CI_GRADLE_ARG_PROPERTIES -PallWarningsAsErrors=false connectedCheck --stacktrace
+          script: ./gradlew $CI_GRADLE_ARG_PROPERTIES -Pandroid.testInstrumentationRunnerArguments.class=org.matrix.android.sdk.session.room.threads.ThreadMessagingTest matrix-sdk-android:connectedAndroidTest --info
+

From ef2c32e2c92af6bc0be3f9ecf32e7af2256d8082 Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Tue, 4 Jan 2022 00:32:39 +0200
Subject: [PATCH 075/581] Github actions test

---
 .../android/sdk/session/room/threads/ThreadMessagingTest.kt      | 1 +
 1 file changed, 1 insertion(+)

diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/ThreadMessagingTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/ThreadMessagingTest.kt
index 0fdcfb28702..0d2458c6d22 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/ThreadMessagingTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/ThreadMessagingTest.kt
@@ -100,6 +100,7 @@ class ThreadMessagingTest : InstrumentedTest {
             timeline.addListener(eventsListener)
             commonTestHelper.await(lock, 600_000)
         }
+
         aliceSession.stopSync()
     }
 

From 84c537315c883f9ab0f9f13e44f0bb7724d13497 Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Tue, 4 Jan 2022 00:38:50 +0200
Subject: [PATCH 076/581] Github actions test

---
 .../android/sdk/session/room/threads/ThreadMessagingTest.kt      | 1 -
 1 file changed, 1 deletion(-)

diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/ThreadMessagingTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/ThreadMessagingTest.kt
index 0d2458c6d22..0fdcfb28702 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/ThreadMessagingTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/ThreadMessagingTest.kt
@@ -100,7 +100,6 @@ class ThreadMessagingTest : InstrumentedTest {
             timeline.addListener(eventsListener)
             commonTestHelper.await(lock, 600_000)
         }
-
         aliceSession.stopSync()
     }
 

From ddfdf180c29da816252826d5542f6e4e75d176fb Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Tue, 4 Jan 2022 00:45:57 +0200
Subject: [PATCH 077/581] Github actions test

---
 .github/workflows/integration.yml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
index 598ba19bc92..f2559565f1c 100644
--- a/.github/workflows/integration.yml
+++ b/.github/workflows/integration.yml
@@ -84,6 +84,6 @@ jobs:
           force-avd-creation: false
           emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
           emulator-build: 7425822
-#          script: ./gradlew $CI_GRADLE_ARG_PROPERTIES -PallWarningsAsErrors=false connectedCheck --stacktrace
-          script: ./gradlew $CI_GRADLE_ARG_PROPERTIES -Pandroid.testInstrumentationRunnerArguments.class=org.matrix.android.sdk.session.room.threads.ThreadMessagingTest matrix-sdk-android:connectedAndroidTest --info
+          script: ./gradlew $CI_GRADLE_ARG_PROPERTIES -PallWarningsAsErrors=false connectedCheck --stacktrace
+#          script: ./gradlew $CI_GRADLE_ARG_PROPERTIES -Pandroid.testInstrumentationRunnerArguments.class=org.matrix.android.sdk.session.room.threads.ThreadMessagingTest matrix-sdk-android:connectedAndroidTest --info
 

From 91bda140e96594327a0ff955ac2e7dc3b07dd997 Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Tue, 4 Jan 2022 00:46:23 +0200
Subject: [PATCH 078/581] Github actions test

---
 .github/workflows/integration.yml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
index f2559565f1c..598ba19bc92 100644
--- a/.github/workflows/integration.yml
+++ b/.github/workflows/integration.yml
@@ -84,6 +84,6 @@ jobs:
           force-avd-creation: false
           emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
           emulator-build: 7425822
-          script: ./gradlew $CI_GRADLE_ARG_PROPERTIES -PallWarningsAsErrors=false connectedCheck --stacktrace
-#          script: ./gradlew $CI_GRADLE_ARG_PROPERTIES -Pandroid.testInstrumentationRunnerArguments.class=org.matrix.android.sdk.session.room.threads.ThreadMessagingTest matrix-sdk-android:connectedAndroidTest --info
+#          script: ./gradlew $CI_GRADLE_ARG_PROPERTIES -PallWarningsAsErrors=false connectedCheck --stacktrace
+          script: ./gradlew $CI_GRADLE_ARG_PROPERTIES -Pandroid.testInstrumentationRunnerArguments.class=org.matrix.android.sdk.session.room.threads.ThreadMessagingTest matrix-sdk-android:connectedAndroidTest --info
 

From f1f1d59576da01da5d53aa36bb4ea0afe9e889fb Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Tue, 4 Jan 2022 00:49:39 +0200
Subject: [PATCH 079/581] Github actions test

---
 .github/workflows/integration.yml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
index 598ba19bc92..8f208f0fe75 100644
--- a/.github/workflows/integration.yml
+++ b/.github/workflows/integration.yml
@@ -87,3 +87,4 @@ jobs:
 #          script: ./gradlew $CI_GRADLE_ARG_PROPERTIES -PallWarningsAsErrors=false connectedCheck --stacktrace
           script: ./gradlew $CI_GRADLE_ARG_PROPERTIES -Pandroid.testInstrumentationRunnerArguments.class=org.matrix.android.sdk.session.room.threads.ThreadMessagingTest matrix-sdk-android:connectedAndroidTest --info
 
+

From bde1df03224ac8cb5422467cb8347da973b687c2 Mon Sep 17 00:00:00 2001
From: ganfra 
Date: Wed, 5 Jan 2022 11:00:12 +0100
Subject: [PATCH 080/581] Bubbles: continue R&D on UI

---
 .../ui-styles/src/main/res/values/colors.xml  |  4 ++
 .../ui-styles/src/main/res/values/dimens.xml  |  4 ++
 .../res/values/stylable_message_bubble.xml    |  2 +
 .../app/features/home/AvatarRenderer.kt       |  1 +
 .../timeline/helper/AvatarSizeProvider.kt     |  4 +-
 .../detail/timeline/view/MessageBubbleView.kt | 56 ++++++++++++++++++-
 .../main/res/drawable/bg_avatar_border.xml    | 12 ++++
 .../drawable/bg_timeline_incoming_message.xml |  1 -
 .../drawable/bg_timeline_outgoing_message.xml |  1 -
 .../main/res/layout/view_message_bubble.xml   | 43 +++++++++-----
 10 files changed, 109 insertions(+), 19 deletions(-)
 create mode 100644 vector/src/main/res/drawable/bg_avatar_border.xml

diff --git a/library/ui-styles/src/main/res/values/colors.xml b/library/ui-styles/src/main/res/values/colors.xml
index 9df2794a1a4..e3ec542c89f 100644
--- a/library/ui-styles/src/main/res/values/colors.xml
+++ b/library/ui-styles/src/main/res/values/colors.xml
@@ -13,6 +13,9 @@
     #14368BD6
     @color/palette_azure
 
+    #0F0DBD8B
+    @color/element_system_light
+
     
     @color/palette_azure
     @color/palette_melon
@@ -137,4 +140,5 @@
     
     @color/palette_gray_100
     @color/palette_gray_450
+
 
diff --git a/library/ui-styles/src/main/res/values/dimens.xml b/library/ui-styles/src/main/res/values/dimens.xml
index 9fbf8958dab..6eff9295d01 100644
--- a/library/ui-styles/src/main/res/values/dimens.xml
+++ b/library/ui-styles/src/main/res/values/dimens.xml
@@ -47,4 +47,8 @@
     56dp
     52dp
     1dp
+
+    28dp
+    62dp
+
 
\ No newline at end of file
diff --git a/library/ui-styles/src/main/res/values/stylable_message_bubble.xml b/library/ui-styles/src/main/res/values/stylable_message_bubble.xml
index 8da2df33e7e..1f55d074864 100644
--- a/library/ui-styles/src/main/res/values/stylable_message_bubble.xml
+++ b/library/ui-styles/src/main/res/values/stylable_message_bubble.xml
@@ -3,6 +3,8 @@
 
     
         
+        
+        
     
 
 
diff --git a/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt b/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt
index 2ee32336370..be689eb27a3 100644
--- a/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt
+++ b/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt
@@ -145,6 +145,7 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
                         }
                         else                    -> {
                             it.apply(RequestOptions.circleCropTransform())
+
                         }
                     }
                 }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AvatarSizeProvider.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AvatarSizeProvider.kt
index c2662e7ae6b..00b02c2cf06 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AvatarSizeProvider.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AvatarSizeProvider.kt
@@ -24,7 +24,7 @@ class AvatarSizeProvider @Inject constructor(private val dimensionConverter: Dim
     private val avatarStyle = AvatarStyle.X_SMALL
 
     val leftGuideline: Int by lazy {
-        dimensionConverter.dpToPx(avatarStyle.avatarSizeDP)
+        dimensionConverter.dpToPx(avatarStyle.avatarSizeDP + 4)
     }
 
     val avatarSize: Int by lazy {
@@ -37,7 +37,7 @@ class AvatarSizeProvider @Inject constructor(private val dimensionConverter: Dim
             BIG(50),
             MEDIUM(40),
             SMALL(30),
-            X_SMALL(24),
+            X_SMALL(28),
             NONE(0)
         }
     }
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 66fb5cd9981..9ad7eb097cd 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
@@ -17,42 +17,94 @@
 package im.vector.app.features.home.room.detail.timeline.view
 
 import android.content.Context
+import android.graphics.drawable.Drawable
 import android.util.AttributeSet
 import android.view.View
+import android.view.ViewGroup
+import android.view.ViewOutlineProvider
 import android.widget.RelativeLayout
+import androidx.core.content.ContextCompat
 import androidx.core.content.withStyledAttributes
+import androidx.core.view.updateLayoutParams
+import com.google.android.material.shape.CornerFamily
+import com.google.android.material.shape.MaterialShapeDrawable
+import com.google.android.material.shape.ShapeAppearanceModel
 import im.vector.app.R
+import im.vector.app.core.utils.DimensionConverter
 
 class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null,
                                                   defStyleAttr: Int = 0) : RelativeLayout(context, attrs, defStyleAttr) {
 
     var incoming: Boolean = false
+    var isFirst: Boolean = false
+    var isLast: Boolean = false
+    var cornerRadius = DimensionConverter(resources).dpToPx(12).toFloat()
 
     init {
         inflate(context, R.layout.view_message_bubble, this)
         context.withStyledAttributes(attrs, R.styleable.MessageBubble) {
             incoming = getBoolean(R.styleable.MessageBubble_incoming_style, false)
+            isFirst = getBoolean(R.styleable.MessageBubble_is_first, false)
+            isLast = getBoolean(R.styleable.MessageBubble_is_last, false)
         }
     }
 
     override fun onFinishInflate() {
         super.onFinishInflate()
         val currentLayoutDirection = layoutDirection
+        findViewById(R.id.bubbleView).apply {
+            background = createBackgroundDrawable()
+            outlineProvider = ViewOutlineProvider.BACKGROUND
+            clipToOutline = true
+        }
         if (incoming) {
             findViewById(R.id.informationBottom).layoutDirection = currentLayoutDirection
             findViewById(R.id.bubbleWrapper).layoutDirection = currentLayoutDirection
             findViewById(R.id.bubbleView).layoutDirection = currentLayoutDirection
-            findViewById(R.id.bubbleView).setBackgroundResource(R.drawable.bg_timeline_incoming_message)
+            findViewById(R.id.messageEndGuideline).updateLayoutParams {
+                marginEnd = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_end)
+            }
         } else {
             val oppositeLayoutDirection = if (currentLayoutDirection == View.LAYOUT_DIRECTION_LTR) {
                 View.LAYOUT_DIRECTION_RTL
             } else {
                 View.LAYOUT_DIRECTION_LTR
             }
+
             findViewById(R.id.informationBottom).layoutDirection = oppositeLayoutDirection
             findViewById(R.id.bubbleWrapper).layoutDirection = oppositeLayoutDirection
             findViewById(R.id.bubbleView).layoutDirection = currentLayoutDirection
-            findViewById(R.id.bubbleView).setBackgroundResource(R.drawable.bg_timeline_outgoing_message)
+            findViewById(R.id.messageEndGuideline).updateLayoutParams {
+                marginEnd = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_start)
+            }
+        }
+    }
+
+    private fun createBackgroundDrawable(): Drawable {
+        val topCornerFamily = if (isFirst) CornerFamily.ROUNDED else CornerFamily.CUT
+        val bottomCornerFamily = if (isLast) CornerFamily.ROUNDED else CornerFamily.CUT
+        val topRadius = if (isFirst) cornerRadius else 0f
+        val bottomRadius = if (isLast) cornerRadius else 0f
+        val shapeAppearanceModelBuilder = ShapeAppearanceModel().toBuilder()
+        val backgroundColor: Int
+        if (incoming) {
+            backgroundColor = R.color.bubble_background_incoming
+            shapeAppearanceModelBuilder
+                    .setTopRightCorner(CornerFamily.ROUNDED, cornerRadius)
+                    .setBottomRightCorner(CornerFamily.ROUNDED, cornerRadius)
+                    .setTopLeftCorner(topCornerFamily, topRadius)
+                    .setBottomLeftCorner(bottomCornerFamily, bottomRadius)
+        } else {
+            backgroundColor = R.color.bubble_background_outgoing
+            shapeAppearanceModelBuilder
+                    .setTopLeftCorner(CornerFamily.ROUNDED, cornerRadius)
+                    .setBottomLeftCorner(CornerFamily.ROUNDED, cornerRadius)
+                    .setTopRightCorner(topCornerFamily, topRadius)
+                    .setBottomRightCorner(bottomCornerFamily, bottomRadius)
         }
+        val shapeAppearanceModel = shapeAppearanceModelBuilder.build()
+        val shapeDrawable = MaterialShapeDrawable(shapeAppearanceModel)
+        shapeDrawable.fillColor = ContextCompat.getColorStateList(context, backgroundColor)
+        return shapeDrawable
     }
 }
diff --git a/vector/src/main/res/drawable/bg_avatar_border.xml b/vector/src/main/res/drawable/bg_avatar_border.xml
new file mode 100644
index 00000000000..e22731c1a35
--- /dev/null
+++ b/vector/src/main/res/drawable/bg_avatar_border.xml
@@ -0,0 +1,12 @@
+
+
+
+    
+
+    
+
+
\ No newline at end of file
diff --git a/vector/src/main/res/drawable/bg_timeline_incoming_message.xml b/vector/src/main/res/drawable/bg_timeline_incoming_message.xml
index ad4a28a7706..2cbca337022 100644
--- a/vector/src/main/res/drawable/bg_timeline_incoming_message.xml
+++ b/vector/src/main/res/drawable/bg_timeline_incoming_message.xml
@@ -1,5 +1,4 @@
 
 
     
-    
 
\ No newline at end of file
diff --git a/vector/src/main/res/drawable/bg_timeline_outgoing_message.xml b/vector/src/main/res/drawable/bg_timeline_outgoing_message.xml
index 1ab85d23528..0f75705a773 100644
--- a/vector/src/main/res/drawable/bg_timeline_outgoing_message.xml
+++ b/vector/src/main/res/drawable/bg_timeline_outgoing_message.xml
@@ -1,5 +1,4 @@
 
 
     
-    
 
\ No newline at end of file
diff --git a/vector/src/main/res/layout/view_message_bubble.xml b/vector/src/main/res/layout/view_message_bubble.xml
index 36c5592f071..12f16910f1c 100644
--- a/vector/src/main/res/layout/view_message_bubble.xml
+++ b/vector/src/main/res/layout/view_message_bubble.xml
@@ -19,9 +19,12 @@
         android:id="@+id/messageAvatarImageView"
         android:layout_width="44dp"
         android:layout_height="44dp"
-        android:layout_marginStart="8dp"
+        android:layout_marginStart="12dp"
         android:layout_marginTop="4dp"
+        android:padding="2dp"
+        android:background="@drawable/bg_avatar_border"
         android:contentDescription="@string/avatar"
+        android:elevation="2dp"
         tools:src="@sample/user_round_avatars" />
 
     
 
+    
+
     
 
@@ -67,45 +78,49 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_below="@id/messageMemberNameView"
-        android:layout_toStartOf="@id/messageSendStateImageView"
+        android:layout_toStartOf="@id/messageEndGuideline"
         android:layout_toEndOf="@id/messageStartGuideline"
         android:addStatesFromChildren="true"
         android:clipChildren="false"
         android:clipToPadding="false">
 
-        
+            android:paddingStart="4dp"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent">
 
             
+                android:addStatesFromChildren="true"
+                app:layout_constrainedWidth="true"
+                app:layout_constraintEnd_toStartOf="@id/messageTimeView"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent"
+                app:layout_constraintWidth_max="300dp" />
 
             
 
-        
+        
     
 
     
@@ -151,7 +169,6 @@
             android:id="@+id/reactionsContainer"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:layout_marginStart="8dp"
             android:layout_marginBottom="4dp"
             app:dividerDrawable="@drawable/reaction_divider"
             app:flexWrap="wrap"

From ad63d3de1c57cb8d9bf83741c7c59771fb7fc514 Mon Sep 17 00:00:00 2001
From: ganfra 
Date: Thu, 6 Jan 2022 19:07:28 +0100
Subject: [PATCH 081/581] Bubbles: still R&D. Not sure how to handle every
 event types.

---
 .../res/values/stylable_message_bubble.xml    |  1 +
 .../timeline/TimelineEventController.kt       |  8 +-
 .../timeline/factory/MessageItemFactory.kt    |  7 ++
 .../helper/MessageInformationDataFactory.kt   |  5 ++
 .../timeline/item/AbsBaseMessageItem.kt       |  5 ++
 .../timeline/item/MessageImageVideoItem.kt    |  3 +
 .../timeline/item/MessageInformationData.kt   |  4 +-
 .../detail/timeline/view/MessageBubbleView.kt | 86 +++++++++++++++----
 .../timeline/view/MessageViewConfiguration.kt | 24 ++++++
 ...item_timeline_event_media_message_stub.xml |  1 +
 .../main/res/layout/view_message_bubble.xml   |  2 +-
 11 files changed, 121 insertions(+), 25 deletions(-)
 create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageViewConfiguration.kt

diff --git a/library/ui-styles/src/main/res/values/stylable_message_bubble.xml b/library/ui-styles/src/main/res/values/stylable_message_bubble.xml
index 1f55d074864..32ed23c6131 100644
--- a/library/ui-styles/src/main/res/values/stylable_message_bubble.xml
+++ b/library/ui-styles/src/main/res/values/stylable_message_bubble.xml
@@ -3,6 +3,7 @@
 
     
         
+        
         
         
     
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt
index 241ccb7428d..dc5c76725b8 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt
@@ -355,12 +355,12 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
         (0 until modelCache.size).forEach { position ->
             val event = currentSnapshot[position]
             val nextEvent = currentSnapshot.nextOrNull(position)
-            val prevEvent = currentSnapshot.prevOrNull(position)
-            val nextDisplayableEvent = currentSnapshot.subList(position + 1, currentSnapshot.size).firstOrNull {
-                timelineEventVisibilityHelper.shouldShowEvent(it, partialState.highlightedEventId)
-            }
             // Should be build if not cached or if model should be refreshed
             if (modelCache[position] == null || modelCache[position]?.isCacheable(partialState) == false) {
+                val prevEvent = currentSnapshot.prevOrNull(position)
+                val nextDisplayableEvent = currentSnapshot.subList(position + 1, currentSnapshot.size).firstOrNull {
+                    timelineEventVisibilityHelper.shouldShowEvent(it, partialState.highlightedEventId)
+                }
                 val timelineEventsGroup = timelineEventsGroups.getOrNull(event)
                 val params = TimelineItemFactoryParams(
                         event = event,
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
index 697c307d06b..c42d50e9246 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
@@ -389,6 +389,13 @@ class MessageItemFactory @Inject constructor(
                 allowNonMxcUrls = informationData.sendState.isSending()
         )
         return MessageImageVideoItem_()
+                .layout(
+                        if (informationData.sentByMe) {
+                            R.layout.item_timeline_event_bubble_outgoing_base
+                        } else {
+                            R.layout.item_timeline_event_bubble_incoming_base
+                        }
+                )
                 .attributes(attributes)
                 .leftGuideline(avatarSizeProvider.leftGuideline)
                 .imageContentRenderer(imageContentRenderer)
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 b203c23978a..b9ef9ca5581 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
@@ -57,8 +57,11 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
     fun create(params: TimelineItemFactoryParams): MessageInformationData {
         val event = params.event
         val nextDisplayableEvent = params.nextDisplayableEvent
+        val prevEvent = params.prevEvent
         val eventId = event.eventId
         val isSentByMe = event.root.senderId == session.myUserId
+        val isFirstFromThisSender = nextDisplayableEvent?.root?.senderId != event.root.senderId
+        val isLastFromThisSender = prevEvent?.root?.senderId != event.root.senderId
         val roomSummary = params.partialState.roomSummary
 
         val date = event.root.localDateTime()
@@ -128,6 +131,8 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
                     ReferencesInfoData(verificationState)
                 },
                 sentByMe = isSentByMe,
+                isFirstFromThisSender = isFirstFromThisSender,
+                isLastFromThisSender = isLastFromThisSender,
                 e2eDecoration = e2eDecoration,
                 sendStateDecoration = sendStateDecoration
         )
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt
index 080b7662580..3d9db4e8277 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt
@@ -29,6 +29,7 @@ import im.vector.app.core.ui.views.ShieldImageView
 import im.vector.app.features.home.AvatarRenderer
 import im.vector.app.features.home.room.detail.timeline.MessageColorProvider
 import im.vector.app.features.home.room.detail.timeline.TimelineEventController
+import im.vector.app.features.home.room.detail.timeline.view.MessageViewConfiguration
 import im.vector.app.features.reactions.widget.ReactionButton
 import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
 import org.matrix.android.sdk.api.session.room.send.SendState
@@ -98,6 +99,10 @@ abstract class AbsBaseMessageItem : BaseEventItem
 
         holder.view.onClick(baseAttributes.itemClickListener)
         holder.view.setOnLongClickListener(baseAttributes.itemLongClickListener)
+        (holder.view as? MessageViewConfiguration)?.apply {
+            isFirstFromSender = baseAttributes.informationData.isFirstFromThisSender
+            isLastFromSender = baseAttributes.informationData.isLastFromThisSender
+        }
     }
 
     override fun unbind(holder: H) {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt
index 3ae91db97c7..8e42297bc1e 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt
@@ -29,6 +29,8 @@ import im.vector.app.core.epoxy.onClick
 import im.vector.app.core.files.LocalFilesHelper
 import im.vector.app.core.glide.GlideApp
 import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder
+import im.vector.app.features.home.room.detail.timeline.view.MessageBubbleView
+import im.vector.app.features.home.room.detail.timeline.view.MessageViewConfiguration
 import im.vector.app.features.media.ImageContentRenderer
 
 @EpoxyModelClass(layout = R.layout.item_timeline_event_base)
@@ -70,6 +72,7 @@ abstract class MessageImageVideoItem : AbsMessageItem(R.id.bubbleView).apply {
+        val bubbleView: ConstraintLayout = findViewById(R.id.bubbleView)
+        bubbleView.apply {
             background = createBackgroundDrawable()
             outlineProvider = ViewOutlineProvider.BACKGROUND
             clipToOutline = true
         }
-        if (incoming) {
+        if (isIncoming) {
             findViewById(R.id.informationBottom).layoutDirection = currentLayoutDirection
             findViewById(R.id.bubbleWrapper).layoutDirection = currentLayoutDirection
-            findViewById(R.id.bubbleView).layoutDirection = currentLayoutDirection
+            bubbleView.layoutDirection = currentLayoutDirection
             findViewById(R.id.messageEndGuideline).updateLayoutParams {
                 marginEnd = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_end)
             }
@@ -73,21 +101,37 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri
 
             findViewById(R.id.informationBottom).layoutDirection = oppositeLayoutDirection
             findViewById(R.id.bubbleWrapper).layoutDirection = oppositeLayoutDirection
-            findViewById(R.id.bubbleView).layoutDirection = currentLayoutDirection
+            bubbleView.layoutDirection = currentLayoutDirection
             findViewById(R.id.messageEndGuideline).updateLayoutParams {
                 marginEnd = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_start)
             }
         }
+        ConstraintSet().apply {
+            clone(bubbleView)
+            clear(R.id.viewStubContainer, ConstraintSet.END)
+            if (displayBorder) {
+                connect(R.id.viewStubContainer, ConstraintSet.END, R.id.messageTimeView, ConstraintSet.START, 0)
+            } else {
+                connect(R.id.viewStubContainer, ConstraintSet.END, R.id.parent, ConstraintSet.END, 0)
+            }
+            applyTo(bubbleView)
+        }
     }
 
     private fun createBackgroundDrawable(): Drawable {
-        val topCornerFamily = if (isFirst) CornerFamily.ROUNDED else CornerFamily.CUT
-        val bottomCornerFamily = if (isLast) CornerFamily.ROUNDED else CornerFamily.CUT
-        val topRadius = if (isFirst) cornerRadius else 0f
-        val bottomRadius = if (isLast) cornerRadius else 0f
+        val (topCornerFamily, topRadius) = if (isFirstFromSender) {
+            Pair(CornerFamily.ROUNDED, cornerRadius)
+        } else {
+            Pair(CornerFamily.CUT, 0f)
+        }
+        val (bottomCornerFamily, bottomRadius) = if (isLastFromSender) {
+            Pair(CornerFamily.ROUNDED, cornerRadius)
+        } else {
+            Pair(CornerFamily.CUT, 0f)
+        }
         val shapeAppearanceModelBuilder = ShapeAppearanceModel().toBuilder()
         val backgroundColor: Int
-        if (incoming) {
+        if (isIncoming) {
             backgroundColor = R.color.bubble_background_incoming
             shapeAppearanceModelBuilder
                     .setTopRightCorner(CornerFamily.ROUNDED, cornerRadius)
@@ -104,7 +148,11 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri
         }
         val shapeAppearanceModel = shapeAppearanceModelBuilder.build()
         val shapeDrawable = MaterialShapeDrawable(shapeAppearanceModel)
-        shapeDrawable.fillColor = ContextCompat.getColorStateList(context, backgroundColor)
+        if (displayBorder) {
+            shapeDrawable.fillColor = ContextCompat.getColorStateList(context, backgroundColor)
+        } else {
+            shapeDrawable.fillColor = ContextCompat.getColorStateList(context, android.R.color.transparent)
+        }
         return shapeDrawable
     }
 }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageViewConfiguration.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageViewConfiguration.kt
new file mode 100644
index 00000000000..f441005edfd
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageViewConfiguration.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.view
+
+interface MessageViewConfiguration {
+    var isIncoming: Boolean
+    var isFirstFromSender: Boolean
+    var isLastFromSender: Boolean
+    var displayBorder: Boolean
+}
diff --git a/vector/src/main/res/layout/item_timeline_event_media_message_stub.xml b/vector/src/main/res/layout/item_timeline_event_media_message_stub.xml
index 9e2a5ef3ede..988e2d8bb15 100644
--- a/vector/src/main/res/layout/item_timeline_event_media_message_stub.xml
+++ b/vector/src/main/res/layout/item_timeline_event_media_message_stub.xml
@@ -4,6 +4,7 @@
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
+    android:background="@color/palette_element_green"
     tools:viewBindingIgnore="true">
 
     

From ae81f61958d4e6d52fe9b549efad513143bb5496 Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Fri, 7 Jan 2022 16:28:58 +0200
Subject: [PATCH 082/581] fix integration test

---
 .../android/sdk/common/CommonTestHelper.kt    | 32 ++-----------------
 1 file changed, 2 insertions(+), 30 deletions(-)

diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt
index da658fdf7d4..9f9667a7597 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt
@@ -157,7 +157,7 @@ class CommonTestHelper(context: Context) {
     /**
      * Will send nb of messages provided by count parameter but waits every 10 messages to avoid gap in sync
      */
-    private fun sendTextMessagesBatched(timeline: Timeline, room: Room, message: String, count: Int, timeout: Long,rootThreadEventId: String? = null): List {
+    private fun sendTextMessagesBatched(timeline: Timeline, room: Room, message: String, count: Int, timeout: Long, rootThreadEventId: String? = null): List {
         val sentEvents = ArrayList(count)
         (1 until count + 1)
                 .map { "$message #$it" }
@@ -214,37 +214,9 @@ class CommonTestHelper(context: Context) {
             numberOfMessages: Int,
             rootThreadEventId: String,
             timeout: Long = TestConstants.timeOutMillis): List {
-
-        val sentEvents = ArrayList(numberOfMessages)
         val timeline = room.createTimeline(null, TimelineSettings(10))
         timeline.start()
-        waitWithLatch(timeout + 1_000L * numberOfMessages) { latch ->
-            val timelineListener = object : Timeline.Listener {
-                override fun onTimelineFailure(throwable: Throwable) {
-                }
-
-                override fun onNewTimelineEvents(eventIds: List) {
-                    // noop
-                }
-
-                override fun onTimelineUpdated(snapshot: List) {
-                    val newMessages = snapshot
-                            .filter { it.root.sendState == SendState.SYNCED }
-                            .filter { it.root.getClearType() == EventType.MESSAGE }
-                            .filter { it.root.getClearContent().toModel()?.body?.startsWith(message) == true }
-
-                    Timber.v("New synced message size: ${newMessages.size}")
-                    if (newMessages.size == numberOfMessages) {
-                        sentEvents.addAll(newMessages)
-                        // Remove listener now, if not at the next update sendEvents could change
-                        timeline.removeListener(this)
-                        latch.countDown()
-                    }
-                }
-            }
-            timeline.addListener(timelineListener)
-            sendTextMessagesBatched(room, message, numberOfMessages, rootThreadEventId)
-        }
+        val sentEvents = sendTextMessagesBatched(timeline, room, message, numberOfMessages, timeout,rootThreadEventId)
         timeline.dispose()
         // Check that all events has been created
         assertEquals("Message number do not match $sentEvents", numberOfMessages.toLong(), sentEvents.size.toLong())

From e54163680273deaa41da0fc511913913e69be328 Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Mon, 10 Jan 2022 11:20:31 +0200
Subject: [PATCH 083/581] Make TimelineSettings aware of rootThreadEventId and
 welcome a new Thread mode for the timeline creation

---
 .../session/room/timeline/TimelineSettings.kt | 13 ++++++--
 .../session/room/timeline/DefaultTimeline.kt  | 16 +++++-----
 .../session/room/timeline/LoadMoreResult.kt   |  1 +
 .../room/timeline/LoadTimelineStrategy.kt     | 31 +++++++++++++++----
 .../session/room/timeline/TimelineChunk.kt    | 21 ++++++++++++-
 .../home/room/detail/RoomDetailViewModel.kt   |  2 +-
 .../timeline/factory/TimelineFactory.kt       | 10 ++++--
 .../helper/TimelineSettingsFactory.kt         |  6 ++--
 8 files changed, 79 insertions(+), 21 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineSettings.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineSettings.kt
index ceffedb2347..6548453c8a1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineSettings.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineSettings.kt
@@ -27,5 +27,14 @@ data class TimelineSettings(
         /**
          * If true, will build read receipts for each event.
          */
-        val buildReadReceipts: Boolean = true
-)
+        val buildReadReceipts: Boolean = true,
+        /**
+         * The root thread eventId if this is a thread timeline, or null if this is NOT a thread timeline
+         */
+        val rootThreadEventId: String? = null) {
+
+    /**
+     * Returns true if this is a thread timeline or false otherwise
+     */
+    fun isThreadTimeline() = rootThreadEventId != null
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
index 483851ebd7c..03ea2fdcb52 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
@@ -136,7 +136,7 @@ internal class DefaultTimeline(private val roomId: String,
                     ensureReadReceiptAreLoaded(realm)
                     backgroundRealm.set(realm)
                     listenToPostSnapshotSignals()
-                    openAround(initialEventId)
+                    openAround(initialEventId, rootThreadEventId)
                     postSnapshot()
                 }
             }
@@ -157,7 +157,7 @@ internal class DefaultTimeline(private val roomId: String,
 
     override fun restartWithEventId(eventId: String?) {
         timelineScope.launch {
-            openAround(eventId)
+            openAround(eventId,rootThreadEventId)
             postSnapshot()
         }
     }
@@ -226,18 +226,20 @@ internal class DefaultTimeline(private val roomId: String,
         return true
     }
 
-    private suspend fun openAround(eventId: String?) = withContext(timelineDispatcher) {
+    private suspend fun openAround(eventId: String?, rootThreadEventId: String?) = withContext(timelineDispatcher) {
         val baseLogMessage = "openAround(eventId: $eventId)"
         Timber.v("$baseLogMessage started")
         if (!isStarted.get()) {
             throw IllegalStateException("You should call start before using timeline")
         }
         strategy.onStop()
-        strategy = if (eventId == null) {
-            buildStrategy(LoadTimelineStrategy.Mode.Live)
-        } else {
-            buildStrategy(LoadTimelineStrategy.Mode.Permalink(eventId))
+
+        strategy = when {
+            rootThreadEventId != null -> buildStrategy(LoadTimelineStrategy.Mode.Thread(rootThreadEventId))
+            eventId == null           -> buildStrategy(LoadTimelineStrategy.Mode.Live)
+            else                      -> buildStrategy(LoadTimelineStrategy.Mode.Permalink(eventId))
         }
+
         initPaginationStates(eventId)
         strategy.onStart()
         loadMore(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadMoreResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadMoreResult.kt
index c419e8325ee..2949e35bd3a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadMoreResult.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadMoreResult.kt
@@ -20,4 +20,5 @@ internal enum class LoadMoreResult {
     REACHED_END,
     SUCCESS,
     FAILURE
+    // evenIDS
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt
index 528b564e8b1..4aee1b1a309 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt
@@ -51,6 +51,7 @@ internal class LoadTimelineStrategy(
     sealed interface Mode {
         object Live : Mode
         data class Permalink(val originEventId: String) : Mode
+        data class Thread(val rootThreadEventId: String) : Mode
 
         fun originEventId(): String? {
             return if (this is Permalink) {
@@ -59,6 +60,14 @@ internal class LoadTimelineStrategy(
                 null
             }
         }
+
+//        fun getRootThreadEventId(): String? {
+//            return if (this is Thread) {
+//                rootThreadEventId
+//            } else {
+//                null
+//            }
+//        }
     }
 
     data class Dependencies(
@@ -162,6 +171,7 @@ internal class LoadTimelineStrategy(
     }
 
     suspend fun loadMore(count: Int, direction: Timeline.Direction, fetchOnServerIfNeeded: Boolean = true): LoadMoreResult {
+        ///
         if (mode is Mode.Permalink && timelineChunk == null) {
             val params = GetContextOfEventTask.Params(roomId, mode.originEventId)
             try {
@@ -198,12 +208,21 @@ internal class LoadTimelineStrategy(
     }
 
     private fun getChunkEntity(realm: Realm): RealmResults {
-        return if (mode is Mode.Permalink) {
-            ChunkEntity.findAllIncludingEvents(realm, listOf(mode.originEventId))
-        } else {
-            ChunkEntity.where(realm, roomId)
-                    .equalTo(ChunkEntityFields.IS_LAST_FORWARD, true)
-                    .findAll()
+
+        return when (mode) {
+            is Mode.Live      -> {
+                ChunkEntity.where(realm, roomId)
+                        .equalTo(ChunkEntityFields.IS_LAST_FORWARD, true)
+                        .findAll()
+            }
+            is Mode.Permalink -> {
+                ChunkEntity.findAllIncludingEvents(realm, listOf(mode.originEventId))
+            }
+            is Mode.Thread    -> {
+                ChunkEntity.where(realm, roomId)
+                        .equalTo(ChunkEntityFields.IS_LAST_FORWARD, true)
+                        .findAll()
+            }
         }
     }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt
index 14cba2a4b8d..e05def3805a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt
@@ -23,6 +23,7 @@ import io.realm.RealmQuery
 import io.realm.RealmResults
 import io.realm.Sort
 import kotlinx.coroutines.CompletableDeferred
+import org.matrix.android.sdk.BuildConfig
 import org.matrix.android.sdk.api.extensions.orFalse
 import org.matrix.android.sdk.api.extensions.tryOrNull
 import org.matrix.android.sdk.api.session.events.model.EventType
@@ -271,7 +272,24 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity,
     private suspend fun loadFromStorage(count: Int, direction: Timeline.Direction): Int {
         val displayIndex = getNextDisplayIndex(direction) ?: return 0
         val baseQuery = timelineEventEntities.where()
-        val timelineEvents = baseQuery.offsets(direction, count, displayIndex).findAll().orEmpty()
+
+        val timelineEvents = if (timelineSettings.rootThreadEventId != null) {
+            baseQuery
+                    .beginGroup()
+                    .equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, timelineSettings.rootThreadEventId)
+                    .or()
+                    .equalTo(TimelineEventEntityFields.ROOT.EVENT_ID, timelineSettings.rootThreadEventId)
+                    .endGroup()
+                    .offsets(direction, count, displayIndex)
+                    .findAll()
+                    .orEmpty()
+        } else {
+            baseQuery
+                    .offsets(direction, count, displayIndex)
+                    .findAll()
+                    .orEmpty()
+        }
+
         if (timelineEvents.isEmpty()) return 0
         fetchRootThreadEventsIfNeeded(timelineEvents)
         if (direction == Timeline.Direction.FORWARDS) {
@@ -299,6 +317,7 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity,
      * in order to be able to display the event to the user appropriately
      */
     private suspend fun fetchRootThreadEventsIfNeeded(offsetResults: List) {
+        if (BuildConfig.THREADING_ENABLED) return
         val eventEntityList = offsetResults
                 .mapNotNull {
                     it.root
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
index f60df814e51..4ee628ff165 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
@@ -127,7 +127,7 @@ class RoomDetailViewModel @AssistedInject constructor(
     private val invisibleEventsSource = BehaviorDataSource()
     private val visibleEventsSource = BehaviorDataSource()
     private var timelineEvents = MutableSharedFlow>(0)
-    val timeline = timelineFactory.createTimeline(viewModelScope, room, eventId)
+    val timeline = timelineFactory.createTimeline(viewModelScope, room, eventId, initialState.rootThreadEventId)
 
     // Same lifecycle than the ViewModel (survive to screen rotation)
     val previewUrlRetriever = PreviewUrlRetriever(session, viewModelScope)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineFactory.kt
index b57e39b3cfe..3ec13661314 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineFactory.kt
@@ -35,8 +35,14 @@ private val secondaryTimelineAllowedTypes = listOf(
 
 class TimelineFactory @Inject constructor(private val session: Session, private val timelineSettingsFactory: TimelineSettingsFactory) {
 
-    fun createTimeline(coroutineScope: CoroutineScope, mainRoom: Room, eventId: String?): Timeline {
-        val settings = timelineSettingsFactory.create()
+    fun createTimeline(
+            coroutineScope: CoroutineScope,
+            mainRoom: Room,
+            eventId: String?,
+            rootThreadEventId: String?
+    ): Timeline {
+        val settings = timelineSettingsFactory.create(rootThreadEventId)
+
         if (!session.vectorCallService.protocolChecker.supportVirtualRooms) {
             return mainRoom.createTimeline(eventId, settings)
         }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineSettingsFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineSettingsFactory.kt
index 3aee65bf195..8b7dcc9c723 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineSettingsFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineSettingsFactory.kt
@@ -22,9 +22,11 @@ import javax.inject.Inject
 
 class TimelineSettingsFactory @Inject constructor(private val userPreferencesProvider: UserPreferencesProvider) {
 
-    fun create(): TimelineSettings {
+    fun create(rootThreadEventId: String?): TimelineSettings {
         return TimelineSettings(
                 initialSize = 30,
-                buildReadReceipts = userPreferencesProvider.shouldShowReadReceipts())
+                buildReadReceipts = userPreferencesProvider.shouldShowReadReceipts(),
+                rootThreadEventId = rootThreadEventId
+        )
     }
 }

From 1b41a72e72166e4750d3612470f715ee89e2f81f Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Mon, 10 Jan 2022 14:14:11 +0200
Subject: [PATCH 084/581] Fix Quote from within a thread

---
 .../sdk/api/session/room/send/SendService.kt  |  2 +-
 .../session/room/send/DefaultSendService.kt   | 10 +++-
 .../room/send/LocalEchoEventFactory.kt        | 17 ++++++-
 .../composer/MessageComposerViewModel.kt      | 51 +++++--------------
 4 files changed, 37 insertions(+), 43 deletions(-)

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 a132d9ff10a..f9a775589cc 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
@@ -63,7 +63,7 @@ interface SendService {
      * @param autoMarkdown If true, the SDK will generate a formatted HTML message from the body text if markdown syntax is present
      * @return a [Cancelable]
      */
-    fun sendQuotedTextMessage(quotedEvent: TimelineEvent, text: String, autoMarkdown: Boolean): Cancelable
+    fun sendQuotedTextMessage(quotedEvent: TimelineEvent, text: String, 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/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt
index 14b66b53772..23322b081c3 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
@@ -97,8 +97,14 @@ internal class DefaultSendService @AssistedInject constructor(
                 .let { sendEvent(it) }
     }
 
-    override fun sendQuotedTextMessage(quotedEvent: TimelineEvent, text: String, autoMarkdown: Boolean): Cancelable {
-        return localEchoEventFactory.createQuotedTextEvent(roomId, quotedEvent, text, autoMarkdown)
+    override fun sendQuotedTextMessage(quotedEvent: TimelineEvent, text: String, autoMarkdown: Boolean, rootThreadEventId: String?): Cancelable {
+        return localEchoEventFactory.createQuotedTextEvent(
+                roomId = roomId,
+                quotedEvent = quotedEvent,
+                text = text,
+                autoMarkdown = autoMarkdown,
+                rootThreadEventId = rootThreadEventId
+        )
                 .also { createLocalEcho(it) }
                 .let { sendEvent(it) }
     }
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 930327b41f9..86da1862226 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
@@ -476,6 +476,7 @@ internal class LocalEchoEventFactory @Inject constructor(
                 newBodyFormatted
         )
     }
+
     private fun buildReplyFallback(body: TextContent, originalSenderId: String?, newBodyText: String): String {
         return buildString {
             append("> <")
@@ -564,11 +565,25 @@ internal class LocalEchoEventFactory @Inject constructor(
             quotedEvent: TimelineEvent,
             text: String,
             autoMarkdown: Boolean,
+            rootThreadEventId: String?
     ): Event {
         val messageContent = quotedEvent.getLastMessageContent()
         val textMsg = messageContent?.body
         val quoteText = legacyRiotQuoteText(textMsg, text)
-        return createFormattedTextEvent(roomId, markdownParser.parse(quoteText, force = true, advanced = autoMarkdown), MessageType.MSGTYPE_TEXT)
+
+        return if (rootThreadEventId != null) {
+            createMessageEvent(
+                    roomId,
+                    markdownParser
+                            .parse(quoteText, force = true, advanced = autoMarkdown)
+                            .toThreadTextContent(rootThreadEventId, MessageType.MSGTYPE_TEXT)
+            )
+        } else {
+            createFormattedTextEvent(
+                    roomId,
+                    markdownParser.parse(quoteText, force = true, advanced = autoMarkdown),
+                    MessageType.MSGTYPE_TEXT)
+        }
     }
 
     private fun legacyRiotQuoteText(quotedText: String?, myText: String): String {
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 ea893131319..c15c026e9d5 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
@@ -242,7 +242,7 @@ class MessageComposerViewModel @AssistedInject constructor(
                             handleJoinToAnotherRoomSlashCommand(slashCommandResult)
                             popDraft()
                         }
-                        is ParsedCommand.PartRoom                 -> {
+                        is ParsedCommand.PartRoom                          -> {
                             handlePartSlashCommand(slashCommandResult)
                         }
                         is ParsedCommand.SendEmote                         -> {
@@ -325,7 +325,7 @@ class MessageComposerViewModel @AssistedInject constructor(
                         is ParsedCommand.ChangeAvatarForRoom               -> {
                             handleChangeAvatarForRoomSlashCommand(slashCommandResult)
                         }
-                        is ParsedCommand.ShowUser                 -> {
+                        is ParsedCommand.ShowUser                          -> {
                             _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk())
                             handleWhoisSlashCommand(slashCommandResult)
                             popDraft()
@@ -343,7 +343,7 @@ class MessageComposerViewModel @AssistedInject constructor(
                                 )
                             }
                         }
-                        is ParsedCommand.CreateSpace              -> {
+                        is ParsedCommand.CreateSpace                       -> {
                             _viewEvents.post(MessageComposerViewEvents.SlashCommandLoading)
                             viewModelScope.launch(Dispatchers.IO) {
                                 try {
@@ -367,7 +367,7 @@ class MessageComposerViewModel @AssistedInject constructor(
                             }
                             Unit
                         }
-                        is ParsedCommand.AddToSpace               -> {
+                        is ParsedCommand.AddToSpace                        -> {
                             _viewEvents.post(MessageComposerViewEvents.SlashCommandLoading)
                             viewModelScope.launch(Dispatchers.IO) {
                                 try {
@@ -386,7 +386,7 @@ class MessageComposerViewModel @AssistedInject constructor(
                             }
                             Unit
                         }
-                        is ParsedCommand.JoinSpace                -> {
+                        is ParsedCommand.JoinSpace                         -> {
                             _viewEvents.post(MessageComposerViewEvents.SlashCommandLoading)
                             viewModelScope.launch(Dispatchers.IO) {
                                 try {
@@ -399,7 +399,7 @@ class MessageComposerViewModel @AssistedInject constructor(
                             }
                             Unit
                         }
-                        is ParsedCommand.LeaveRoom                -> {
+                        is ParsedCommand.LeaveRoom                         -> {
                             viewModelScope.launch(Dispatchers.IO) {
                                 try {
                                     session.getRoom(slashCommandResult.roomId)?.leave(null)
@@ -411,7 +411,7 @@ class MessageComposerViewModel @AssistedInject constructor(
                             }
                             Unit
                         }
-                        is ParsedCommand.UpgradeRoom              -> {
+                        is ParsedCommand.UpgradeRoom                       -> {
                             _viewEvents.post(
                                     MessageComposerViewEvents.ShowRoomUpgradeDialog(
                                             slashCommandResult.newVersion,
@@ -446,39 +446,12 @@ class MessageComposerViewModel @AssistedInject constructor(
                     _viewEvents.post(MessageComposerViewEvents.MessageSent)
                     popDraft()
                 }
-//                is SendMode.Quote   -> {
-//                    val messageContent = state.sendMode.timelineEvent.getLastMessageContent()
-//                    val textMsg = messageContent?.body
-//
-//                    val finalText = legacyRiotQuoteText(textMsg, action.text.toString())
-//
-//                    // TODO check for pills?
-//
-//                    // TODO Refactor this, just temporary for quotes
-//                    val parser = Parser.builder().build()
-//                    val document = parser.parse(finalText)
-//                    val renderer = HtmlRenderer.builder().build()
-//                    val htmlText = renderer.render(document)
-//
-//                    if (finalText == htmlText) {
-//                        state.rootThreadEventId?.let {
-//                            room.replyInThread(
-//                                    rootThreadEventId = it,
-//                                    replyInThreadText = finalText)
-//                        } ?: room.sendTextMessage(finalText)
-//                    } else {
-//                        state.rootThreadEventId?.let {
-//                            room.replyInThread(
-//                                    rootThreadEventId = it,
-//                                    replyInThreadText = finalText,
-//                                    formattedText = htmlText)
-//                        } ?: room.sendFormattedTextMessage(finalText, htmlText)
-//                    }
-//                    _viewEvents.post(MessageComposerViewEvents.MessageSent)
-//                    popDraft()
-//                }
                 is SendMode.Quote   -> {
-                    room.sendQuotedTextMessage(state.sendMode.timelineEvent, action.text.toString(), action.autoMarkdown)
+                    room.sendQuotedTextMessage(
+                            quotedEvent = state.sendMode.timelineEvent,
+                            text = action.text.toString(),
+                            autoMarkdown = action.autoMarkdown,
+                            rootThreadEventId = state.rootThreadEventId)
                     _viewEvents.post(MessageComposerViewEvents.MessageSent)
                     popDraft()
                 }

From 37ec3fdf841b1ae9e853005dc01635c0307a3817 Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Tue, 11 Jan 2022 12:13:53 +0200
Subject: [PATCH 085/581] Refactor threads to support the new timeline
 implementation

---
 .../session/room/timeline/TimelineChunk.kt    | 102 +++++++++++++-----
 .../room/timeline/TokenChunkEventPersistor.kt |   1 -
 2 files changed, 74 insertions(+), 29 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt
index e05def3805a..2f53c666bc5 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt
@@ -93,7 +93,7 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity,
                 handleDatabaseChangeSet(frozenResults, changeSet)
             }
 
-    private var timelineEventEntities: RealmResults = chunkEntity.sortedTimelineEvents()
+    private var timelineEventEntities: RealmResults = chunkEntity.sortedTimelineEvents(timelineSettings.rootThreadEventId)
     private val builtEvents: MutableList = Collections.synchronizedList(ArrayList())
     private val builtEventsIndexes: MutableMap = Collections.synchronizedMap(HashMap())
 
@@ -138,13 +138,18 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity,
         } else if (direction == Timeline.Direction.BACKWARDS && prevChunk != null) {
             return prevChunk?.loadMore(count, direction, fetchOnServerIfNeeded) ?: LoadMoreResult.FAILURE
         }
-        val loadFromStorageCount = loadFromStorage(count, direction)
-        Timber.v("Has loaded $loadFromStorageCount items from storage in $direction")
-        val offsetCount = count - loadFromStorageCount
+        val loadFromStorage = loadFromStorage(count, direction).also{
+            logLoadedFromStorage(it,direction)
+        }
+
+        val offsetCount = count - loadFromStorage.numberOfEvents
+
         return if (direction == Timeline.Direction.FORWARDS && isLastForward.get()) {
             LoadMoreResult.REACHED_END
         } else if (direction == Timeline.Direction.BACKWARDS && isLastBackward.get()) {
             LoadMoreResult.REACHED_END
+        } else if (timelineSettings.isThreadTimeline() && loadFromStorage.threadReachedEnd) {
+            LoadMoreResult.REACHED_END
         } else if (offsetCount == 0) {
             LoadMoreResult.SUCCESS
         } else {
@@ -188,6 +193,15 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity,
         }
     }
 
+    /**
+     * Simple log that displays the number and timeline of loaded events
+     */
+    private fun logLoadedFromStorage(loadedFromStorage: LoadedFromStorage, direction: Timeline.Direction) =
+        Timber.v("[" +
+                "${if (timelineSettings.isThreadTimeline()) "ThreadTimeLine" else "Timeline"}] Has loaded " +
+                "${loadedFromStorage.numberOfEvents} items from storage in $direction " +
+                if (timelineSettings.isThreadTimeline() && loadedFromStorage.threadReachedEnd) "[Reached End]" else "")
+
     fun getBuiltEventIndex(eventId: String, searchInNext: Boolean, searchInPrev: Boolean): Int? {
         val builtEventIndex = builtEventsIndexes[eventId]
         if (builtEventIndex != null) {
@@ -268,29 +282,31 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity,
 
     /**
      * This method tries to read events from the current chunk.
+     * @return the number of events loaded. If we are in a thread timeline it also returns
+     * whether or not we reached the end/root message
      */
-    private suspend fun loadFromStorage(count: Int, direction: Timeline.Direction): Int {
-        val displayIndex = getNextDisplayIndex(direction) ?: return 0
+    private suspend fun loadFromStorage(count: Int, direction: Timeline.Direction): LoadedFromStorage {
+        val displayIndex = getNextDisplayIndex(direction) ?: return LoadedFromStorage()
         val baseQuery = timelineEventEntities.where()
 
-        val timelineEvents = if (timelineSettings.rootThreadEventId != null) {
-            baseQuery
-                    .beginGroup()
-                    .equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, timelineSettings.rootThreadEventId)
-                    .or()
-                    .equalTo(TimelineEventEntityFields.ROOT.EVENT_ID, timelineSettings.rootThreadEventId)
-                    .endGroup()
-                    .offsets(direction, count, displayIndex)
-                    .findAll()
-                    .orEmpty()
-        } else {
-            baseQuery
-                    .offsets(direction, count, displayIndex)
-                    .findAll()
-                    .orEmpty()
-        }
-
-        if (timelineEvents.isEmpty()) return 0
+//        val timelineEvents = if (timelineSettings.rootThreadEventId != null) {
+//            baseQuery
+//                    .beginGroup()
+//                    .equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, timelineSettings.rootThreadEventId)
+//                    .or()
+//                    .equalTo(TimelineEventEntityFields.ROOT.EVENT_ID, timelineSettings.rootThreadEventId)
+//                    .endGroup()
+//                    .offsets(direction, count, displayIndex)
+//                    .findAll()
+//                    .orEmpty()
+//        } else {
+        val timelineEvents = baseQuery
+                .offsets(direction, count, displayIndex)
+                .findAll()
+                .orEmpty()
+//        }
+
+        if (timelineEvents.isEmpty()) return LoadedFromStorage()
         fetchRootThreadEventsIfNeeded(timelineEvents)
         if (direction == Timeline.Direction.FORWARDS) {
             builtEventsIndexes.entries.forEach { it.setValue(it.value + timelineEvents.size) }
@@ -309,9 +325,20 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity,
                         builtEvents.add(timelineEvent)
                     }
                 }
-        return timelineEvents.size
+        return LoadedFromStorage(
+                threadReachedEnd = threadReachedEnd(timelineEvents),
+                numberOfEvents = timelineEvents.size)
     }
 
+    /**
+     * Returns whether or not the the thread has reached end. It returned false if the current timeline
+     * is not a thread timeline
+     */
+    private fun threadReachedEnd(timelineEvents: List): Boolean =
+            timelineSettings.rootThreadEventId?.let { rootThreadId ->
+                timelineEvents.firstOrNull { it.eventId == rootThreadId }?.let { true }
+            } ?: false
+
     /**
      * This function is responsible to fetch and store the root event of a thread event
      * in order to be able to display the event to the user appropriately
@@ -362,7 +389,8 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity,
         val loadMoreResult = try {
             if (token == null) {
                 if (direction == Timeline.Direction.BACKWARDS || !chunkEntity.hasBeenALastForwardChunk()) return LoadMoreResult.REACHED_END
-                val lastKnownEventId = chunkEntity.sortedTimelineEvents().firstOrNull()?.eventId ?: return LoadMoreResult.FAILURE
+                val lastKnownEventId = chunkEntity.sortedTimelineEvents(timelineSettings.rootThreadEventId).firstOrNull()?.eventId
+                        ?: return LoadMoreResult.FAILURE
                 val taskParams = FetchTokenAndPaginateTask.Params(roomId, lastKnownEventId, direction.toPaginationDirection(), count)
                 fetchTokenAndPaginateTask.execute(taskParams).toLoadMoreResult()
             } else {
@@ -473,6 +501,11 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity,
                 onBuiltEvents = this.onBuiltEvents
         )
     }
+
+    private data class LoadedFromStorage(
+            val threadReachedEnd: Boolean = false,
+            val numberOfEvents: Int = 0
+    )
 }
 
 private fun RealmQuery.offsets(
@@ -493,6 +526,19 @@ private fun Timeline.Direction.toPaginationDirection(): PaginationDirection {
     return if (this == Timeline.Direction.BACKWARDS) PaginationDirection.BACKWARDS else PaginationDirection.FORWARDS
 }
 
-private fun ChunkEntity.sortedTimelineEvents(): RealmResults {
-    return timelineEvents.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)
+private fun ChunkEntity.sortedTimelineEvents(rootThreadEventId: String?): RealmResults {
+    return if (rootThreadEventId == null) {
+        timelineEvents
+                .sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)
+    } else {
+        timelineEvents
+                .where()
+                .beginGroup()
+                .equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, rootThreadEventId)
+                .or()
+                .equalTo(TimelineEventEntityFields.ROOT.EVENT_ID, rootThreadEventId)
+                .endGroup()
+                .sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)
+                .findAll()
+    }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt
index 19d6a1bd282..2641d971b16 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt
@@ -25,7 +25,6 @@ import org.matrix.android.sdk.api.session.room.send.SendState
 import org.matrix.android.sdk.internal.database.helper.addIfNecessary
 import org.matrix.android.sdk.internal.database.helper.addStateEvent
 import org.matrix.android.sdk.internal.database.helper.addTimelineEvent
-import org.matrix.android.sdk.internal.database.helper.merge
 import org.matrix.android.sdk.internal.database.helper.updateThreadSummaryIfNeeded
 import org.matrix.android.sdk.internal.database.mapper.toEntity
 import org.matrix.android.sdk.internal.database.model.ChunkEntity

From f7c9b36cef33a037c0237f0c37f817ccdb542042 Mon Sep 17 00:00:00 2001
From: ganfra 
Date: Tue, 11 Jan 2022 11:57:35 +0100
Subject: [PATCH 086/581] Bubbles: continue exploration

---
 .../main/res/values/stylable_message_bubble.xml   |  2 +-
 .../detail/timeline/TimelineEventController.kt    |  4 ++++
 .../timeline/factory/TimelineItemFactoryParams.kt |  1 +
 .../helper/MessageInformationDataFactory.kt       | 10 ++++++----
 .../detail/timeline/item/MessageImageVideoItem.kt |  3 +--
 .../detail/timeline/view/MessageBubbleView.kt     | 15 ++++++---------
 .../timeline/view/MessageViewConfiguration.kt     |  2 +-
 .../item_timeline_event_media_message_stub.xml    |  2 --
 8 files changed, 20 insertions(+), 19 deletions(-)

diff --git a/library/ui-styles/src/main/res/values/stylable_message_bubble.xml b/library/ui-styles/src/main/res/values/stylable_message_bubble.xml
index 32ed23c6131..f7a877e3ed5 100644
--- a/library/ui-styles/src/main/res/values/stylable_message_bubble.xml
+++ b/library/ui-styles/src/main/res/values/stylable_message_bubble.xml
@@ -3,7 +3,7 @@
 
     
         
-        
+        
         
         
     
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt
index dc5c76725b8..460c3328126 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt
@@ -358,6 +358,9 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
             // Should be build if not cached or if model should be refreshed
             if (modelCache[position] == null || modelCache[position]?.isCacheable(partialState) == false) {
                 val prevEvent = currentSnapshot.prevOrNull(position)
+                val prevDisplayableEvent = currentSnapshot.subList(0, position).lastOrNull {
+                    timelineEventVisibilityHelper.shouldShowEvent(it, partialState.highlightedEventId)
+                }
                 val nextDisplayableEvent = currentSnapshot.subList(position + 1, currentSnapshot.size).firstOrNull {
                     timelineEventVisibilityHelper.shouldShowEvent(it, partialState.highlightedEventId)
                 }
@@ -365,6 +368,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
                 val params = TimelineItemFactoryParams(
                         event = event,
                         prevEvent = prevEvent,
+                        prevDisplayableEvent = prevDisplayableEvent,
                         nextEvent = nextEvent,
                         nextDisplayableEvent = nextDisplayableEvent,
                         partialState = partialState,
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactoryParams.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactoryParams.kt
index cdfedb29250..fad558344cf 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactoryParams.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactoryParams.kt
@@ -23,6 +23,7 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
 data class TimelineItemFactoryParams(
         val event: TimelineEvent,
         val prevEvent: TimelineEvent? = null,
+        val prevDisplayableEvent: TimelineEvent? = null,
         val nextEvent: TimelineEvent? = null,
         val nextDisplayableEvent: TimelineEvent? = null,
         val partialState: TimelineEventController.PartialState = TimelineEventController.PartialState(),
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 b9ef9ca5581..b9ea78e0db3 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
@@ -57,19 +57,21 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
     fun create(params: TimelineItemFactoryParams): MessageInformationData {
         val event = params.event
         val nextDisplayableEvent = params.nextDisplayableEvent
-        val prevEvent = params.prevEvent
+        val prevDisplayableEvent = params.prevDisplayableEvent
         val eventId = event.eventId
         val isSentByMe = event.root.senderId == session.myUserId
-        val isFirstFromThisSender = nextDisplayableEvent?.root?.senderId != event.root.senderId
-        val isLastFromThisSender = prevEvent?.root?.senderId != event.root.senderId
         val roomSummary = params.partialState.roomSummary
 
         val date = event.root.localDateTime()
         val nextDate = nextDisplayableEvent?.root?.localDateTime()
         val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate()
+
         val isNextMessageReceivedMoreThanOneHourAgo = nextDate?.isBefore(date.minusMinutes(60))
                 ?: false
 
+        val isFirstFromThisSender = nextDisplayableEvent?.root?.senderId != event.root.senderId || addDaySeparator
+        val isLastFromThisSender = prevDisplayableEvent?.root?.senderId != event.root.senderId || prevDisplayableEvent?.root?.localDateTime()?.toLocalDate() != date.toLocalDate()
+
         val showInformation =
                 (addDaySeparator ||
                         event.senderInfo.avatarUrl != nextDisplayableEvent?.senderInfo?.avatarUrl ||
@@ -77,7 +79,7 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
                         nextDisplayableEvent.root.getClearType() !in listOf(EventType.MESSAGE, EventType.STICKER, EventType.ENCRYPTED) ||
                         isNextMessageReceivedMoreThanOneHourAgo ||
                         isTileTypeMessage(nextDisplayableEvent) ||
-                        nextDisplayableEvent.isEdition() ) && !isSentByMe
+                        nextDisplayableEvent.isEdition()) && !isSentByMe
 
         val time = dateFormatter.format(event.root.originServerTs, DateFormatKind.MESSAGE_SIMPLE)
         val e2eDecoration = getE2EDecoration(roomSummary, event)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt
index 8e42297bc1e..e865354747f 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt
@@ -29,7 +29,6 @@ import im.vector.app.core.epoxy.onClick
 import im.vector.app.core.files.LocalFilesHelper
 import im.vector.app.core.glide.GlideApp
 import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder
-import im.vector.app.features.home.room.detail.timeline.view.MessageBubbleView
 import im.vector.app.features.home.room.detail.timeline.view.MessageViewConfiguration
 import im.vector.app.features.media.ImageContentRenderer
 
@@ -72,7 +71,7 @@ abstract class MessageImageVideoItem : AbsMessageItem
 
     
Date: Tue, 11 Jan 2022 15:31:21 +0200
Subject: [PATCH 087/581] - fix ktlint format - Update Threads toolbar UI

---
 .../android/sdk/common/CommonTestHelper.kt       |  2 +-
 .../session/room/threads/ThreadMessagingTest.kt  |  3 ---
 .../session/room/timeline/DefaultTimeline.kt     |  5 ++---
 .../room/timeline/LoadTimelineStrategy.kt        |  3 +--
 .../session/room/timeline/TimelineChunk.kt       |  4 ++--
 .../room/timeline/TokenChunkEventPersistor.kt    |  2 +-
 .../home/room/detail/TimelineFragment.kt         |  1 -
 .../layout/view_room_detail_thread_toolbar.xml   | 16 +++++++++-------
 8 files changed, 16 insertions(+), 20 deletions(-)

diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt
index 9f9667a7597..031d0a8bcf8 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt
@@ -216,7 +216,7 @@ class CommonTestHelper(context: Context) {
             timeout: Long = TestConstants.timeOutMillis): List {
         val timeline = room.createTimeline(null, TimelineSettings(10))
         timeline.start()
-        val sentEvents = sendTextMessagesBatched(timeline, room, message, numberOfMessages, timeout,rootThreadEventId)
+        val sentEvents = sendTextMessagesBatched(timeline, room, message, numberOfMessages, timeout, rootThreadEventId)
         timeline.dispose()
         // Check that all events has been created
         assertEquals("Message number do not match $sentEvents", numberOfMessages.toLong(), sentEvents.size.toLong())
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/ThreadMessagingTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/ThreadMessagingTest.kt
index 0fdcfb28702..62887beba76 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/ThreadMessagingTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/ThreadMessagingTest.kt
@@ -261,7 +261,6 @@ class ThreadMessagingTest : InstrumentedTest {
         val firstMessage = sentMessages[0]
         val secondMessage = sentMessages[1]
 
-
         // Alice will reply in thread to the second message 35 times
         val aliceThreadRepliesInSecondMessage = commonTestHelper.replyInThreadMessage(
                 room = aliceRoom,
@@ -288,7 +287,6 @@ class ThreadMessagingTest : InstrumentedTest {
                 numberOfMessages = 20,
                 rootThreadEventId = secondMessage.root.eventId.orEmpty())
 
-
         aliceThreadRepliesInSecondMessage.forEach {
             it.root.isThread().shouldBeTrue()
             it.root.isTextMessage().shouldBeTrue()
@@ -337,5 +335,4 @@ class ThreadMessagingTest : InstrumentedTest {
         }
         aliceSession.stopSync()
     }
-
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
index 03ea2fdcb52..037edbd83dd 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
@@ -127,11 +127,10 @@ internal class DefaultTimeline(private val roomId: String,
         }
         timelineScope.launch {
             sequencer.post {
-
                 if (isStarted.compareAndSet(false, true)) {
                     isFromThreadTimeline = rootThreadEventId != null
                     this@DefaultTimeline.rootThreadEventId = rootThreadEventId
-                    ///
+                    // /
                     val realm = Realm.getInstance(realmConfiguration)
                     ensureReadReceiptAreLoaded(realm)
                     backgroundRealm.set(realm)
@@ -157,7 +156,7 @@ internal class DefaultTimeline(private val roomId: String,
 
     override fun restartWithEventId(eventId: String?) {
         timelineScope.launch {
-            openAround(eventId,rootThreadEventId)
+            openAround(eventId, rootThreadEventId)
             postSnapshot()
         }
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt
index 4aee1b1a309..1d92d89a3aa 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt
@@ -171,7 +171,7 @@ internal class LoadTimelineStrategy(
     }
 
     suspend fun loadMore(count: Int, direction: Timeline.Direction, fetchOnServerIfNeeded: Boolean = true): LoadMoreResult {
-        ///
+        // /
         if (mode is Mode.Permalink && timelineChunk == null) {
             val params = GetContextOfEventTask.Params(roomId, mode.originEventId)
             try {
@@ -208,7 +208,6 @@ internal class LoadTimelineStrategy(
     }
 
     private fun getChunkEntity(realm: Realm): RealmResults {
-
         return when (mode) {
             is Mode.Live      -> {
                 ChunkEntity.where(realm, roomId)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt
index 2f53c666bc5..eac653c72d3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt
@@ -138,8 +138,8 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity,
         } else if (direction == Timeline.Direction.BACKWARDS && prevChunk != null) {
             return prevChunk?.loadMore(count, direction, fetchOnServerIfNeeded) ?: LoadMoreResult.FAILURE
         }
-        val loadFromStorage = loadFromStorage(count, direction).also{
-            logLoadedFromStorage(it,direction)
+        val loadFromStorage = loadFromStorage(count, direction).also {
+            logLoadedFromStorage(it, direction)
         }
 
         val offsetCount = count - loadFromStorage.numberOfEvents
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt
index 186c59a5622..5115c852caa 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt
@@ -50,7 +50,7 @@ import javax.inject.Inject
 internal class TokenChunkEventPersistor @Inject constructor(
                                                             @SessionDatabase private val monarchy: Monarchy,
                                                             @UserId private val userId: String,
-                                                            private val liveEventManager: Lazy ) {
+                                                            private val liveEventManager: Lazy) {
 
     enum class Result {
         SHOULD_FETCH_MORE,
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 86e7a5f78dd..dc5eb34e083 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
@@ -196,7 +196,6 @@ import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
-import kotlinx.parcelize.Parcelize
 import nl.dionsegijn.konfetti.models.Shape
 import nl.dionsegijn.konfetti.models.Size
 import org.billcarsonfr.jsonviewer.JSonViewerDialog
diff --git a/vector/src/main/res/layout/view_room_detail_thread_toolbar.xml b/vector/src/main/res/layout/view_room_detail_thread_toolbar.xml
index 389dab4936a..5de8ec3efa1 100644
--- a/vector/src/main/res/layout/view_room_detail_thread_toolbar.xml
+++ b/vector/src/main/res/layout/view_room_detail_thread_toolbar.xml
@@ -6,6 +6,7 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:visibility="gone"
+    tools:layout_height="?actionBarSize"
     tools:visibility="visible">
 
     
 
     
 
     
 

From 1e2fb88783c54877213e6e2f9b1f7a10ee1e7490 Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Tue, 11 Jan 2022 16:30:29 +0200
Subject: [PATCH 088/581] - fix lint error

---
 .github/workflows/quality.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml
index 483e8819f37..7eff52cc9fe 100644
--- a/.github/workflows/quality.yml
+++ b/.github/workflows/quality.yml
@@ -74,7 +74,7 @@ jobs:
           edit-mode: replace
       - name: Delete comment if needed
         if: always() && steps.fc.outputs.comment-id != '' && steps.ktlint-results.outputs.add_comment == 'false'
-        uses: actions/github-script@v5.1.0
+        uses: actions/github-script@v3
         with:
           script: |
             github.issues.deleteComment({

From af542a8243ff562791f8886cb438159bd314a253 Mon Sep 17 00:00:00 2001
From: ganfra 
Date: Tue, 11 Jan 2022 15:38:58 +0100
Subject: [PATCH 089/581] Bubbles: start adding "theming" mechanism

---
 .../timeline/factory/EncryptedItemFactory.kt  |  1 +
 .../timeline/factory/MessageItemFactory.kt    | 19 +---
 .../timeline/helper/AvatarSizeProvider.kt     | 26 ++++--
 .../helper/MessageInformationDataFactory.kt   | 24 ++---
 .../detail/timeline/item/AbsMessageItem.kt    |  6 +-
 .../timeline/item/MessageInformationData.kt   |  5 +-
 .../timeline/style/TimelineLayoutSettings.kt  | 22 +++++
 .../style/TimelineLayoutSettingsProvider.kt   | 26 ++++++
 .../timeline/style/TimelineMessageLayout.kt   | 44 ++++++++++
 .../style/TimelineMessageLayoutFactory.kt     | 88 +++++++++++++++++++
 10 files changed, 212 insertions(+), 49 deletions(-)
 create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineLayoutSettings.kt
 create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineLayoutSettingsProvider.kt
 create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt
 create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt

diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt
index b8d7d96ecfc..5112604dd30 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt
@@ -108,6 +108,7 @@ class EncryptedItemFactory @Inject constructor(private val messageInformationDat
                 val informationData = messageInformationDataFactory.create(params)
                 val attributes = attributesFactory.create(event.root.content.toModel(), informationData, params.callback)
                 return MessageTextItem_()
+                        .layout(informationData.messageLayout.layoutRes)
                         .leftGuideline(avatarSizeProvider.leftGuideline)
                         .highlighted(params.isHighlighted)
                         .attributes(attributes)
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 c42d50e9246..33d77fe16c4 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
@@ -154,7 +154,7 @@ class MessageItemFactory @Inject constructor(
 
 //        val all = event.root.toContent()
 //        val ev = all.toModel()
-        return when (messageContent) {
+        val messageItem = when (messageContent) {
             is MessageEmoteContent               -> buildEmoteMessageItem(messageContent, informationData, highlight, callback, attributes)
             is MessageTextContent                -> buildItemForTextContent(messageContent, informationData, highlight, callback, attributes)
             is MessageImageInfoContent           -> buildImageMessageItem(messageContent, informationData, highlight, callback, attributes)
@@ -172,6 +172,9 @@ class MessageItemFactory @Inject constructor(
             is MessagePollContent                -> buildPollContent(messageContent, informationData, highlight, callback, attributes)
             else                                 -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes)
         }
+        return messageItem?.apply {
+            layout(informationData.messageLayout.layoutRes)
+        }
     }
 
     private fun buildPollContent(pollContent: MessagePollContent,
@@ -389,13 +392,6 @@ class MessageItemFactory @Inject constructor(
                 allowNonMxcUrls = informationData.sendState.isSending()
         )
         return MessageImageVideoItem_()
-                .layout(
-                        if (informationData.sentByMe) {
-                            R.layout.item_timeline_event_bubble_outgoing_base
-                        } else {
-                            R.layout.item_timeline_event_bubble_incoming_base
-                        }
-                )
                 .attributes(attributes)
                 .leftGuideline(avatarSizeProvider.leftGuideline)
                 .imageContentRenderer(imageContentRenderer)
@@ -517,13 +513,6 @@ class MessageItemFactory @Inject constructor(
                             linkifiedBody
                         }.toEpoxyCharSequence()
                 )
-                .layout(
-                        if (informationData.sentByMe) {
-                            R.layout.item_timeline_event_bubble_outgoing_base
-                        } else {
-                            R.layout.item_timeline_event_bubble_incoming_base
-                        }
-                )
                 .useBigFont(linkifiedBody.length <= MAX_NUMBER_OF_EMOJI_FOR_BIG_FONT * 2 && containsOnlyEmojis(linkifiedBody.toString()))
                 .bindingOptions(bindingOptions)
                 .searchForPills(isFormatted)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AvatarSizeProvider.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AvatarSizeProvider.kt
index 00b02c2cf06..a34c216fad1 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AvatarSizeProvider.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/AvatarSizeProvider.kt
@@ -17,14 +17,22 @@
 package im.vector.app.features.home.room.detail.timeline.helper
 
 import im.vector.app.core.utils.DimensionConverter
+import im.vector.app.features.home.room.detail.timeline.style.TimelineLayoutSettings
+import im.vector.app.features.home.room.detail.timeline.style.TimelineLayoutSettingsProvider
 import javax.inject.Inject
 
-class AvatarSizeProvider @Inject constructor(private val dimensionConverter: DimensionConverter) {
+class AvatarSizeProvider @Inject constructor(private val dimensionConverter: DimensionConverter,
+                                             private val layoutSettingsProvider: TimelineLayoutSettingsProvider) {
 
-    private val avatarStyle = AvatarStyle.X_SMALL
+    private val avatarStyle by lazy {
+        when (layoutSettingsProvider.getLayoutSettings()) {
+            TimelineLayoutSettings.MODERN -> AvatarStyle.SMALL
+            TimelineLayoutSettings.BUBBLE -> AvatarStyle.BUBBLE
+        }
+    }
 
     val leftGuideline: Int by lazy {
-        dimensionConverter.dpToPx(avatarStyle.avatarSizeDP + 4)
+        dimensionConverter.dpToPx(avatarStyle.avatarSizeDP + avatarStyle.marginDP)
     }
 
     val avatarSize: Int by lazy {
@@ -33,12 +41,12 @@ class AvatarSizeProvider @Inject constructor(private val dimensionConverter: Dim
 
     companion object {
 
-        enum class AvatarStyle(val avatarSizeDP: Int) {
-            BIG(50),
-            MEDIUM(40),
-            SMALL(30),
-            X_SMALL(28),
-            NONE(0)
+        enum class AvatarStyle(val avatarSizeDP: Int, val marginDP: Int) {
+            BIG(50, 8),
+            MEDIUM(40, 8),
+            SMALL(30, 8),
+            BUBBLE(28, 4),
+            NONE(0, 8)
         }
     }
 }
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 b9ea78e0db3..2edab8af746 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
@@ -27,7 +27,7 @@ import im.vector.app.features.home.room.detail.timeline.item.PollVoteSummaryData
 import im.vector.app.features.home.room.detail.timeline.item.ReactionInfoData
 import im.vector.app.features.home.room.detail.timeline.item.ReferencesInfoData
 import im.vector.app.features.home.room.detail.timeline.item.SendStateDecoration
-import im.vector.app.features.settings.VectorPreferences
+import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayoutFactory
 import org.matrix.android.sdk.api.crypto.VerificationState
 import org.matrix.android.sdk.api.extensions.orFalse
 import org.matrix.android.sdk.api.session.Session
@@ -41,7 +41,6 @@ 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.isEdition
 import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
 import javax.inject.Inject
 
@@ -51,8 +50,7 @@ import javax.inject.Inject
  */
 class MessageInformationDataFactory @Inject constructor(private val session: Session,
                                                         private val dateFormatter: VectorDateFormatter,
-                                                        private val visibilityHelper: TimelineEventVisibilityHelper,
-                                                        private val vectorPreferences: VectorPreferences) {
+                                                        private val messageLayoutFactory: TimelineMessageLayoutFactory) {
 
     fun create(params: TimelineItemFactoryParams): MessageInformationData {
         val event = params.event
@@ -66,21 +64,9 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
         val nextDate = nextDisplayableEvent?.root?.localDateTime()
         val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate()
 
-        val isNextMessageReceivedMoreThanOneHourAgo = nextDate?.isBefore(date.minusMinutes(60))
-                ?: false
-
         val isFirstFromThisSender = nextDisplayableEvent?.root?.senderId != event.root.senderId || addDaySeparator
         val isLastFromThisSender = prevDisplayableEvent?.root?.senderId != event.root.senderId || prevDisplayableEvent?.root?.localDateTime()?.toLocalDate() != date.toLocalDate()
 
-        val showInformation =
-                (addDaySeparator ||
-                        event.senderInfo.avatarUrl != nextDisplayableEvent?.senderInfo?.avatarUrl ||
-                        event.senderInfo.disambiguatedDisplayName != nextDisplayableEvent?.senderInfo?.disambiguatedDisplayName ||
-                        nextDisplayableEvent.root.getClearType() !in listOf(EventType.MESSAGE, EventType.STICKER, EventType.ENCRYPTED) ||
-                        isNextMessageReceivedMoreThanOneHourAgo ||
-                        isTileTypeMessage(nextDisplayableEvent) ||
-                        nextDisplayableEvent.isEdition()) && !isSentByMe
-
         val time = dateFormatter.format(event.root.originServerTs, DateFormatKind.MESSAGE_SIMPLE)
         val e2eDecoration = getE2EDecoration(roomSummary, event)
 
@@ -95,6 +81,8 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
             SendStateDecoration.NONE
         }
 
+        val messageLayout = messageLayoutFactory.create(params)
+
         return MessageInformationData(
                 eventId = eventId,
                 senderId = event.root.senderId ?: "",
@@ -103,9 +91,7 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
                 ageLocalTS = event.root.ageLocalTs,
                 avatarUrl = event.senderInfo.avatarUrl,
                 memberName = event.senderInfo.disambiguatedDisplayName,
-                showAvatar = showInformation,
-                showDisplayName = showInformation,
-                showTimestamp = true,
+                messageLayout = messageLayout,
                 orderedReactionList = event.annotations?.reactionsSummary
                         // ?.filter { isSingleEmoji(it.key) }
                         ?.map {
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 a964af6f730..9f3b2bddf2f 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
@@ -62,7 +62,7 @@ abstract class AbsMessageItem : AbsBaseMessageItem
 
     override fun bind(holder: H) {
         super.bind(holder)
-        if (attributes.informationData.showAvatar) {
+        if (attributes.informationData.messageLayout.showAvatar) {
             holder.avatarImageView.layoutParams = holder.avatarImageView.layoutParams?.apply {
                 height = attributes.avatarSize
                 width = attributes.avatarSize
@@ -76,7 +76,7 @@ abstract class AbsMessageItem : AbsBaseMessageItem
             holder.avatarImageView.setOnLongClickListener(null)
             holder.avatarImageView.isVisible = false
         }
-        if (attributes.informationData.showDisplayName) {
+        if (attributes.informationData.messageLayout.showDisplayName) {
             holder.memberNameView.isVisible = true
             holder.memberNameView.text = attributes.informationData.memberName
             holder.memberNameView.setTextColor(attributes.getMemberNameColor())
@@ -87,7 +87,7 @@ abstract class AbsMessageItem : AbsBaseMessageItem
             holder.memberNameView.setOnLongClickListener(null)
             holder.memberNameView.isVisible = false
         }
-        if (attributes.informationData.showTimestamp) {
+        if (attributes.informationData.messageLayout.showTimestamp) {
             holder.timeView.isVisible = true
             holder.timeView.text = attributes.informationData.time
         } else {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt
index 76fc9a5effa..629d20e898f 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt
@@ -17,6 +17,7 @@
 package im.vector.app.features.home.room.detail.timeline.item
 
 import android.os.Parcelable
+import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout
 import kotlinx.parcelize.Parcelize
 import org.matrix.android.sdk.api.crypto.VerificationState
 import org.matrix.android.sdk.api.session.room.send.SendState
@@ -31,9 +32,7 @@ data class MessageInformationData(
         val ageLocalTS: Long?,
         val avatarUrl: String?,
         val memberName: CharSequence? = null,
-        val showAvatar: Boolean,
-        val showDisplayName: Boolean,
-        val showTimestamp: Boolean,
+        val messageLayout: TimelineMessageLayout,
         /*List of reactions (emoji,count,isSelected)*/
         val orderedReactionList: List? = null,
         val pollResponseAggregatedSummary: PollResponseData? = null,
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineLayoutSettings.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineLayoutSettings.kt
new file mode 100644
index 00000000000..873ef8c9075
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineLayoutSettings.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.style
+
+enum class TimelineLayoutSettings {
+    MODERN,
+    BUBBLE
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineLayoutSettingsProvider.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineLayoutSettingsProvider.kt
new file mode 100644
index 00000000000..a10c95befea
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineLayoutSettingsProvider.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.features.home.room.detail.timeline.style
+
+import javax.inject.Inject
+
+class TimelineLayoutSettingsProvider @Inject constructor() {
+
+    fun getLayoutSettings(): TimelineLayoutSettings {
+        return TimelineLayoutSettings.BUBBLE
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt
new file mode 100644
index 00000000000..48dba0a58ae
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.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.features.home.room.detail.timeline.style
+
+import android.os.Parcelable
+import im.vector.app.R
+import kotlinx.parcelize.Parcelize
+
+sealed interface TimelineMessageLayout : Parcelable {
+    val layoutRes: Int
+    val showAvatar: Boolean
+    val showDisplayName: Boolean
+    val showTimestamp: Boolean
+
+    @Parcelize
+    data class Modern(override val showAvatar: Boolean,
+                      override val showDisplayName: Boolean,
+                      override val showTimestamp: Boolean,
+                      override val layoutRes: Int = R.layout.item_timeline_event_base) : TimelineMessageLayout
+
+    @Parcelize
+    data class Bubble(override val showAvatar: Boolean,
+                      override val showDisplayName: Boolean,
+                      override val showTimestamp: Boolean = true,
+                      val isIncoming: Boolean,
+                      val isFirstFromThisSender: Boolean,
+                      val isLastFromThisSender: Boolean,
+                      override val layoutRes: Int = if (isIncoming) R.layout.item_timeline_event_bubble_incoming_base else R.layout.item_timeline_event_bubble_outgoing_base,
+    ) : TimelineMessageLayout
+}
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
new file mode 100644
index 00000000000..18df8c133b2
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt
@@ -0,0 +1,88 @@
+/*
+ * 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.style
+
+import im.vector.app.core.extensions.localDateTime
+import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFactoryParams
+import im.vector.app.features.settings.VectorPreferences
+import org.matrix.android.sdk.api.session.Session
+import org.matrix.android.sdk.api.session.events.model.EventType
+import org.matrix.android.sdk.api.session.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 javax.inject.Inject
+
+class TimelineMessageLayoutFactory @Inject constructor(private val session: Session,
+                                                       private val layoutSettingsProvider: TimelineLayoutSettingsProvider,
+                                                       private val vectorPreferences: VectorPreferences) {
+
+    fun create(params: TimelineItemFactoryParams): TimelineMessageLayout {
+
+        val event = params.event
+        val nextDisplayableEvent = params.nextDisplayableEvent
+        val prevDisplayableEvent = params.prevDisplayableEvent
+        val isSentByMe = event.root.senderId == session.myUserId
+
+        val date = event.root.localDateTime()
+        val nextDate = nextDisplayableEvent?.root?.localDateTime()
+        val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate()
+
+        val isNextMessageReceivedMoreThanOneHourAgo = nextDate?.isBefore(date.minusMinutes(60))
+                ?: false
+
+        val showInformation =
+                (addDaySeparator ||
+                        event.senderInfo.avatarUrl != nextDisplayableEvent?.senderInfo?.avatarUrl ||
+                        event.senderInfo.disambiguatedDisplayName != nextDisplayableEvent?.senderInfo?.disambiguatedDisplayName ||
+                        nextDisplayableEvent.root.getClearType() !in listOf(EventType.MESSAGE, EventType.STICKER, EventType.ENCRYPTED) ||
+                        isNextMessageReceivedMoreThanOneHourAgo ||
+                        isTileTypeMessage(nextDisplayableEvent) ||
+                        nextDisplayableEvent.isEdition()) && !isSentByMe
+
+        val messageLayout = when (layoutSettingsProvider.getLayoutSettings()) {
+            TimelineLayoutSettings.MODERN -> TimelineMessageLayout.Modern(showInformation, showInformation, showInformation || vectorPreferences.alwaysShowTimeStamps())
+            TimelineLayoutSettings.BUBBLE -> {
+                val isFirstFromThisSender = nextDisplayableEvent?.root?.senderId != event.root.senderId || addDaySeparator
+                val isLastFromThisSender = prevDisplayableEvent?.root?.senderId != event.root.senderId || prevDisplayableEvent?.root?.localDateTime()?.toLocalDate() != date.toLocalDate()
+                TimelineMessageLayout.Bubble(
+                        showAvatar = showInformation,
+                        showDisplayName = showInformation,
+                        isIncoming = !isSentByMe,
+                        isFirstFromThisSender = isFirstFromThisSender,
+                        isLastFromThisSender = isLastFromThisSender
+                )
+            }
+        }
+        return messageLayout
+    }
+
+    /**
+     * Tiles type message never show the sender information (like verification request), so we should repeat it for next message
+     * even if same sender
+     */
+    private fun isTileTypeMessage(event: TimelineEvent?): Boolean {
+        return when (event?.root?.getClearType()) {
+            EventType.KEY_VERIFICATION_DONE,
+            EventType.KEY_VERIFICATION_CANCEL -> true
+            EventType.MESSAGE                 -> {
+                event.getLastMessageContent() is MessageVerificationRequestContent
+            }
+            else                              -> false
+        }
+    }
+}

From 4560d748d3837f39aabd3a9ce5ae891401b68666 Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Tue, 11 Jan 2022 17:52:14 +0200
Subject: [PATCH 090/581] Display encrypted messages in thread summary and in
 thread list

---
 .../matrix/android/sdk/api/session/events/model/Event.kt  | 4 ++--
 .../android/sdk/internal/database/mapper/EventMapper.kt   | 2 +-
 .../app/features/home/room/detail/TimelineFragment.kt     | 6 +++++-
 .../room/detail/timeline/factory/EncryptedItemFactory.kt  | 7 ++++++-
 .../timeline/helper/MessageItemAttributesFactory.kt       | 4 ++++
 .../home/room/detail/timeline/item/AbsMessageItem.kt      | 3 ++-
 .../room/threads/list/viewmodel/ThreadListController.kt   | 8 ++++++--
 7 files changed, 26 insertions(+), 8 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
index 7372a83873c..895280732e6 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
@@ -197,8 +197,8 @@ data class Event(
      * It can be used especially for message summaries.
      * It will return a decrypted text message or an empty string otherwise.
      */
-    fun getDecryptedTextSummary(): String {
-        val text = getDecryptedValue().orEmpty()
+    fun getDecryptedTextSummary(): String? {
+        val text = getDecryptedValue() ?: return null
         return when {
             isReply() || isQuote() -> ContentUtils.extractUsefulTextFromReply(text)
             isFileMessage()        -> "sent a file."
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt
index 05070efe1f4..35042844271 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt
@@ -113,7 +113,7 @@ internal object EventMapper {
                         )
                     },
                     threadNotificationState = eventEntity.threadNotificationState,
-                    threadSummaryLatestTextMessage = eventEntity.threadSummaryLatestMessage?.root?.asDomain()?.getDecryptedTextSummary().orEmpty(),
+                    threadSummaryLatestTextMessage = eventEntity.threadSummaryLatestMessage?.root?.asDomain()?.getDecryptedTextSummary(),
                     lastMessageTimestamp = eventEntity.threadSummaryLatestMessage?.root?.originServerTs
 
             )
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 dc5eb34e083..7f44aed15b2 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
@@ -1902,7 +1902,11 @@ class TimelineFragment @Inject constructor(
                 roomDetailViewModel.handle(action)
             }
             is EncryptedEventContent             -> {
-                roomDetailViewModel.handle(RoomDetailAction.TapOnFailedToDecrypt(informationData.eventId))
+                if(isRootThreadEvent){
+                    onThreadSummaryClicked(informationData.eventId, isRootThreadEvent)
+                }else {
+                    roomDetailViewModel.handle(RoomDetailAction.TapOnFailedToDecrypt(informationData.eventId))
+                }
             }
             else                                 -> {
                 onThreadSummaryClicked(informationData.eventId, isRootThreadEvent)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt
index 89c9c51f0c5..f1ffc77a366 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt
@@ -106,7 +106,12 @@ class EncryptedItemFactory @Inject constructor(private val messageInformationDat
                 }
 
                 val informationData = messageInformationDataFactory.create(params)
-                val attributes = attributesFactory.create(event.root.content.toModel(), informationData, params.callback)
+                val threadDetails = if (params.isFromThreadTimeline()) null else event.root.threadDetails
+                val attributes = attributesFactory.create(
+                        messageContent = event.root.content.toModel(),
+                        informationData = informationData,
+                        callback = params.callback,
+                        threadDetails = threadDetails)
                 return MessageTextItem_()
                         .leftGuideline(avatarSizeProvider.leftGuideline)
                         .highlighted(params.isHighlighted)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt
index 8cc5ffe1ee6..a2cdbec7c61 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageItemAttributesFactory.kt
@@ -16,6 +16,8 @@
 package im.vector.app.features.home.room.detail.timeline.helper
 
 import im.vector.app.EmojiCompatFontProvider
+import im.vector.app.R
+import im.vector.app.core.resources.StringProvider
 import im.vector.app.features.home.AvatarRenderer
 import im.vector.app.features.home.room.detail.timeline.MessageColorProvider
 import im.vector.app.features.home.room.detail.timeline.TimelineEventController
@@ -28,6 +30,7 @@ class MessageItemAttributesFactory @Inject constructor(
         private val avatarRenderer: AvatarRenderer,
         private val messageColorProvider: MessageColorProvider,
         private val avatarSizeProvider: AvatarSizeProvider,
+        private val stringProvider: StringProvider,
         private val emojiCompatFontProvider: EmojiCompatFontProvider) {
 
     fun create(messageContent: Any?,
@@ -53,6 +56,7 @@ class MessageItemAttributesFactory @Inject constructor(
                 threadCallback = callback,
                 readReceiptsCallback = callback,
                 emojiTypeFace = emojiCompatFontProvider.typeface,
+                decryptionErrorMessage = stringProvider.getString(R.string.encrypted_message),
                 threadDetails = threadDetails
         )
     }
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 f75df309161..33eae89e313 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
@@ -118,7 +118,7 @@ abstract class AbsMessageItem : AbsBaseMessageItem
             attributes.threadDetails?.let { threadDetails ->
                 holder.threadSummaryConstraintLayout.isVisible = threadDetails.isRootThread
                 holder.threadSummaryCounterTextView.text = threadDetails.numberOfThreads.toString()
-                holder.threadSummaryInfoTextView.text = threadDetails.threadSummaryLatestTextMessage
+                holder.threadSummaryInfoTextView.text = threadDetails.threadSummaryLatestTextMessage ?: attributes.decryptionErrorMessage
 
                 val userId = threadDetails.threadSummarySenderInfo?.userId ?: return@let
                 val displayName = threadDetails.threadSummarySenderInfo?.displayName
@@ -185,6 +185,7 @@ abstract class AbsMessageItem : AbsBaseMessageItem
             val threadCallback: TimelineEventController.ThreadCallback? = null,
             override val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null,
             val emojiTypeFace: Typeface? = null,
+            val decryptionErrorMessage: String? = null,
             val threadDetails: ThreadDetails? = null
     ) : AbsBaseMessageItem.Attributes {
 
diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListController.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListController.kt
index 3f69701a314..984c8e8f7ed 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListController.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListController.kt
@@ -17,8 +17,10 @@
 package im.vector.app.features.home.room.threads.list.viewmodel
 
 import com.airbnb.epoxy.EpoxyController
+import im.vector.app.R
 import im.vector.app.core.date.DateFormatKind
 import im.vector.app.core.date.VectorDateFormatter
+import im.vector.app.core.resources.StringProvider
 import im.vector.app.features.home.AvatarRenderer
 import im.vector.app.features.home.room.threads.list.model.threadList
 import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
@@ -28,6 +30,7 @@ import javax.inject.Inject
 
 class ThreadListController @Inject constructor(
         private val avatarRenderer: AvatarRenderer,
+        private val stringProvider: StringProvider,
         private val dateFormatter: VectorDateFormatter
 ) : EpoxyController() {
 
@@ -56,6 +59,7 @@ class ThreadListController @Inject constructor(
                 }
                 ?.forEach { timelineEvent ->
                     val date = dateFormatter.format(timelineEvent.root.threadDetails?.lastMessageTimestamp, DateFormatKind.ROOM_LIST)
+                    val decryptionErrorMessage = stringProvider.getString(R.string.encrypted_message)
                     threadList {
                         id(timelineEvent.eventId)
                         avatarRenderer(host.avatarRenderer)
@@ -64,8 +68,8 @@ class ThreadListController @Inject constructor(
                         date(date)
                         rootMessageDeleted(timelineEvent.root.isRedacted())
                         threadNotificationState(timelineEvent.root.threadDetails?.threadNotificationState ?: ThreadNotificationState.NO_NEW_MESSAGE)
-                        rootMessage(timelineEvent.root.getDecryptedTextSummary())
-                        lastMessage(timelineEvent.root.threadDetails?.threadSummaryLatestTextMessage.orEmpty())
+                        rootMessage(timelineEvent.root.getDecryptedTextSummary() ?: decryptionErrorMessage)
+                        lastMessage(timelineEvent.root.threadDetails?.threadSummaryLatestTextMessage ?: decryptionErrorMessage)
                         lastMessageCounter(timelineEvent.root.threadDetails?.numberOfThreads.toString())
                         lastMessageMatrixItem(timelineEvent.root.threadDetails?.threadSummarySenderInfo?.toMatrixItem())
                         itemClickListener {

From c04935113008dcfb40ad223e95d268e8d3fdf0c0 Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Wed, 12 Jan 2022 18:30:43 +0200
Subject: [PATCH 091/581] Fix kltint errors

---
 .../vector/app/features/home/room/detail/TimelineFragment.kt  | 4 ++--
 1 file changed, 2 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 954e62809ab..cba99866a9b 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
@@ -1917,9 +1917,9 @@ class TimelineFragment @Inject constructor(
                 roomDetailViewModel.handle(action)
             }
             is EncryptedEventContent             -> {
-                if(isRootThreadEvent){
+                if (isRootThreadEvent) {
                     onThreadSummaryClicked(informationData.eventId, isRootThreadEvent)
-                }else {
+                } else {
                     roomDetailViewModel.handle(RoomDetailAction.TapOnFailedToDecrypt(informationData.eventId))
                 }
             }

From b89054685f7a95b7fc4dbff27cbe13bb6ef64e50 Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Wed, 12 Jan 2022 18:40:33 +0200
Subject: [PATCH 092/581] Fix migration from 21 to 22

---
 .../sdk/internal/database/RealmSessionStoreMigration.kt        | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
index 52c7a5cef19..e7ccae38d56 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
@@ -91,6 +91,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
         if (oldVersion <= 18) migrateTo19(realm)
         if (oldVersion <= 19) migrateTo20(realm)
         if (oldVersion <= 20) migrateTo21(realm)
+        if (oldVersion <= 21) migrateTo22(realm)
     }
 
     private fun migrateTo1(realm: DynamicRealm) {
@@ -447,7 +448,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
                 }
     }
 
-    private fun migrateTo21(realm: DynamicRealm) {
+    private fun migrateTo22(realm: DynamicRealm) {
         Timber.d("Step 21 -> 22")
         val eventEntity = realm.schema.get("TimelineEventEntity") ?: return
 

From 53fecef2d479b54e6e156b9cd485ee889dd3ff75 Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Wed, 12 Jan 2022 18:47:34 +0200
Subject: [PATCH 093/581] Fix compilation error on TimelineFragment

---
 .../im/vector/app/features/home/room/detail/TimelineFragment.kt | 2 +-
 1 file changed, 1 insertion(+), 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 cba99866a9b..b7af9caf15a 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
@@ -588,7 +588,7 @@ class TimelineFragment @Inject constructor(
     private fun handleOpenRoomSettings(directAccess: Int? = null) {
         navigator.openRoomProfile(
                 requireContext(),
-                roomDetailArgs.roomId,
+                timelineArgs.roomId,
                 directAccess
         )
     }

From f7df0b891efbf53d55f46f574d0da9a4de774a08 Mon Sep 17 00:00:00 2001
From: ganfra 
Date: Wed, 12 Jan 2022 18:45:40 +0100
Subject: [PATCH 094/581] Bubbles: fix recycling issue

---
 .../detail/timeline/factory/MessageItemFactory.kt |  4 +++-
 .../room/detail/timeline/item/BaseEventItem.kt    | 15 +++++++++++++++
 .../detail/timeline/item/CallTileTimelineItem.kt  |  2 +-
 .../home/room/detail/timeline/item/DefaultItem.kt |  2 +-
 .../timeline/item/MergedMembershipEventsItem.kt   |  2 +-
 .../timeline/item/MergedRoomCreationItem.kt       |  2 +-
 .../detail/timeline/item/MessageBlockCodeItem.kt  |  2 +-
 .../room/detail/timeline/item/MessageFileItem.kt  |  2 +-
 .../detail/timeline/item/MessageImageVideoItem.kt |  2 +-
 .../room/detail/timeline/item/MessageTextItem.kt  |  2 +-
 .../room/detail/timeline/item/MessageVoiceItem.kt |  2 +-
 .../home/room/detail/timeline/item/NoticeItem.kt  |  2 +-
 .../home/room/detail/timeline/item/PollItem.kt    |  2 ++
 .../detail/timeline/item/RedactedMessageItem.kt   |  2 +-
 .../timeline/item/StatusTileTimelineItem.kt       |  2 +-
 .../timeline/item/VerificationRequestItem.kt      |  2 +-
 .../timeline/item/WidgetTileTimelineItem.kt       |  2 +-
 17 files changed, 34 insertions(+), 15 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 33d77fe16c4..80343fd909e 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
@@ -172,7 +172,9 @@ class MessageItemFactory @Inject constructor(
             is MessagePollContent                -> buildPollContent(messageContent, informationData, highlight, callback, attributes)
             else                                 -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes)
         }
-        return messageItem?.apply {
+        return messageItem?.takeIf {
+            it.layout == R.layout.item_timeline_event_base
+        }?.apply {
             layout(informationData.messageLayout.layoutRes)
         }
     }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BaseEventItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BaseEventItem.kt
index 5dfbf5d8f6b..3df6393c056 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BaseEventItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BaseEventItem.kt
@@ -27,6 +27,7 @@ import im.vector.app.core.epoxy.VectorEpoxyHolder
 import im.vector.app.core.epoxy.VectorEpoxyModel
 import im.vector.app.core.platform.CheckableView
 import im.vector.app.core.utils.DimensionConverter
+import timber.log.Timber
 
 /**
  * Children must override getViewType()
@@ -43,6 +44,20 @@ abstract class BaseEventItem : VectorEpoxyModel
     @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
     lateinit var dimensionConverter: DimensionConverter
 
+    final override fun getViewType(): Int {
+        // This makes sure we have a unique integer for the combination of layout and ViewStubId.
+        return pairingFunction(layout, getViewStubId()).also {
+            Timber.v("GetViewType: for ${javaClass.canonicalName} $it with layout:$layout and stubId:${getViewStubId()}")
+        }
+    }
+
+    abstract fun getViewStubId(): Int
+
+    // Szudzik function
+    private fun pairingFunction(a: Int, b: Int): Int {
+        return if (a >= b) a * a + a + b else a + b * b
+    }
+
     @CallSuper
     override fun bind(holder: H) {
         super.bind(holder)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/CallTileTimelineItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/CallTileTimelineItem.kt
index 5f8ac822dab..218d6483189 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/CallTileTimelineItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/CallTileTimelineItem.kt
@@ -50,7 +50,7 @@ abstract class CallTileTimelineItem : AbsBaseMessageItem() {
         return listOf(attributes.informationData.eventId)
     }
 
-    override fun getViewType() = STUB_ID
+    override fun getViewStubId() = STUB_ID
 
     class Holder : BaseHolder(STUB_ID) {
         val avatarImageView by bind(R.id.itemDefaultAvatarView)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedMembershipEventsItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedMembershipEventsItem.kt
index a52ddf8336d..e19dc33fffb 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedMembershipEventsItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedMembershipEventsItem.kt
@@ -29,7 +29,7 @@ import im.vector.app.features.home.AvatarRenderer
 @EpoxyModelClass(layout = R.layout.item_timeline_event_base_noinfo)
 abstract class MergedMembershipEventsItem : BasedMergedItem() {
 
-    override fun getViewType() = STUB_ID
+    override fun getViewStubId() = STUB_ID
 
     @EpoxyAttribute
     override lateinit var attributes: Attributes
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt
index 1e8e96426f4..9f631f7a0ea 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt
@@ -51,7 +51,7 @@ abstract class MergedRoomCreationItem : BasedMergedItem(R.id.codeBlockTextView)
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 b15f909b79c..bd35532eaed 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
@@ -95,7 +95,7 @@ abstract class MessageFileItem : AbsMessageItem() {
         contentDownloadStateTrackerBinder.unbind(mxcUrl)
     }
 
-    override fun getViewType() = STUB_ID
+    override fun getViewStubId() = STUB_ID
 
     class Holder : AbsMessageItem.Holder(STUB_ID) {
         val progressLayout by bind(R.id.messageFileUploadProgressLayout)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt
index e865354747f..e20ad48b172 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt
@@ -83,7 +83,7 @@ abstract class MessageImageVideoItem : AbsMessageItem(R.id.messageMediaUploadProgressLayout)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt
index 6a438d3a06b..e83611d6d8f 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt
@@ -115,7 +115,7 @@ abstract class MessageTextItem : AbsMessageItem() {
         previewUrlRetriever?.removeListener(attributes.informationData.eventId, previewUrlViewUpdater)
     }
 
-    override fun getViewType() = STUB_ID + layout
+    override fun getViewStubId() = STUB_ID
 
     class Holder : AbsMessageItem.Holder(STUB_ID) {
         val messageView by bind(R.id.messageTextView)
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 f006c2aa35a..b6fa684ecad 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
@@ -120,7 +120,7 @@ abstract class MessageVoiceItem : AbsMessageItem() {
         voiceMessagePlaybackTracker.unTrack(attributes.informationData.eventId)
     }
 
-    override fun getViewType() = STUB_ID
+    override fun getViewStubId() = STUB_ID
 
     class Holder : AbsMessageItem.Holder(STUB_ID) {
         val voiceLayout by bind(R.id.voiceLayout)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/NoticeItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/NoticeItem.kt
index 2851668df58..e998b7e58f5 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/NoticeItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/NoticeItem.kt
@@ -64,7 +64,7 @@ abstract class NoticeItem : BaseEventItem() {
         return listOf(attributes.informationData.eventId)
     }
 
-    override fun getViewType() = STUB_ID
+    override fun getViewStubId() = STUB_ID
 
     class Holder : BaseHolder(STUB_ID) {
         val avatarImageView by bind(R.id.itemNoticeAvatarView)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt
index 1308fa49c88..784eec87fec 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/PollItem.kt
@@ -46,6 +46,8 @@ abstract class PollItem : AbsMessageItem() {
     @EpoxyAttribute
     lateinit var optionViewStates: List
 
+    override fun getViewStubId() = STUB_ID
+
     override fun bind(holder: Holder) {
         super.bind(holder)
         val relatedEventId = eventId ?: return
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/RedactedMessageItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/RedactedMessageItem.kt
index 282550daec9..204bab22548 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/RedactedMessageItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/RedactedMessageItem.kt
@@ -22,7 +22,7 @@ import im.vector.app.R
 @EpoxyModelClass(layout = R.layout.item_timeline_event_base)
 abstract class RedactedMessageItem : AbsMessageItem() {
 
-    override fun getViewType() = STUB_ID
+    override fun getViewStubId() = STUB_ID
 
     override fun shouldShowReactionAtBottom() = false
 
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/StatusTileTimelineItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/StatusTileTimelineItem.kt
index 6531efb82d1..66bf41ddac1 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/StatusTileTimelineItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/StatusTileTimelineItem.kt
@@ -40,7 +40,7 @@ abstract class StatusTileTimelineItem : AbsBaseMessageItem
Date: Wed, 12 Jan 2022 19:01:13 +0100
Subject: [PATCH 095/581] Bubbles: add quick settings (temporary)

---
 .../timeline/style/TimelineLayoutSettingsProvider.kt   |  9 +++++++--
 .../vector/app/features/settings/VectorPreferences.kt  | 10 ++++++++++
 .../src/main/res/xml/vector_settings_preferences.xml   |  5 +++++
 3 files changed, 22 insertions(+), 2 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineLayoutSettingsProvider.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineLayoutSettingsProvider.kt
index a10c95befea..9e351a706df 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineLayoutSettingsProvider.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineLayoutSettingsProvider.kt
@@ -16,11 +16,16 @@
 
 package im.vector.app.features.home.room.detail.timeline.style
 
+import im.vector.app.features.settings.VectorPreferences
 import javax.inject.Inject
 
-class TimelineLayoutSettingsProvider @Inject constructor() {
+class TimelineLayoutSettingsProvider @Inject constructor(private val vectorPreferences: VectorPreferences) {
 
     fun getLayoutSettings(): TimelineLayoutSettings {
-        return TimelineLayoutSettings.BUBBLE
+        return if (vectorPreferences.useMessageBubblesLayout()) {
+            TimelineLayoutSettings.BUBBLE
+        } else {
+            TimelineLayoutSettings.MODERN
+        }
     }
 }
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 3436c20ce32..9c472a387cf 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
@@ -83,6 +83,7 @@ class VectorPreferences @Inject constructor(private val context: Context) {
         // interface
         const val SETTINGS_INTERFACE_LANGUAGE_PREFERENCE_KEY = "SETTINGS_INTERFACE_LANGUAGE_PREFERENCE_KEY"
         const val SETTINGS_INTERFACE_TEXT_SIZE_KEY = "SETTINGS_INTERFACE_TEXT_SIZE_KEY"
+        const val SETTINGS_INTERFACE_BUBBLE_KEY = "SETTINGS_INTERFACE_BUBBLE_KEY"
         const val SETTINGS_SHOW_URL_PREVIEW_KEY = "SETTINGS_SHOW_URL_PREVIEW_KEY"
         private const val SETTINGS_SEND_TYPING_NOTIF_KEY = "SETTINGS_SEND_TYPING_NOTIF_KEY"
         private const val SETTINGS_ENABLE_MARKDOWN_KEY = "SETTINGS_ENABLE_MARKDOWN_KEY"
@@ -852,6 +853,15 @@ class VectorPreferences @Inject constructor(private val context: Context) {
         return defaultPrefs.getBoolean(SETTINGS_SHOW_EMOJI_KEYBOARD, true)
     }
 
+    /**
+     * Tells if the emoji keyboard button should be visible or not.
+     *
+     * @return true to show emoji keyboard button.
+     */
+    fun useMessageBubblesLayout(): Boolean {
+        return defaultPrefs.getBoolean(SETTINGS_INTERFACE_BUBBLE_KEY, false)
+    }
+
     /**
      * Tells if the rage shake is used.
      *
diff --git a/vector/src/main/res/xml/vector_settings_preferences.xml b/vector/src/main/res/xml/vector_settings_preferences.xml
index 14c7dc7b803..ac8a48fb2e3 100644
--- a/vector/src/main/res/xml/vector_settings_preferences.xml
+++ b/vector/src/main/res/xml/vector_settings_preferences.xml
@@ -22,6 +22,11 @@
             android:title="@string/settings_theme"
             app:iconSpaceReserved="false" />
 
+        
+
         
Date: Wed, 12 Jan 2022 19:22:14 +0100
Subject: [PATCH 096/581] Bubbles: fix avatar/name visibility in modern layout

---
 .../style/TimelineMessageLayoutFactory.kt     | 27 +++++++++++--------
 1 file changed, 16 insertions(+), 11 deletions(-)

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 18df8c133b2..bd37c2d66a0 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
@@ -45,23 +45,28 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess
         val isNextMessageReceivedMoreThanOneHourAgo = nextDate?.isBefore(date.minusMinutes(60))
                 ?: false
 
-        val showInformation =
-                (addDaySeparator ||
-                        event.senderInfo.avatarUrl != nextDisplayableEvent?.senderInfo?.avatarUrl ||
-                        event.senderInfo.disambiguatedDisplayName != nextDisplayableEvent?.senderInfo?.disambiguatedDisplayName ||
-                        nextDisplayableEvent.root.getClearType() !in listOf(EventType.MESSAGE, EventType.STICKER, EventType.ENCRYPTED) ||
-                        isNextMessageReceivedMoreThanOneHourAgo ||
-                        isTileTypeMessage(nextDisplayableEvent) ||
-                        nextDisplayableEvent.isEdition()) && !isSentByMe
+        val showInformation = addDaySeparator ||
+                event.senderInfo.avatarUrl != nextDisplayableEvent?.senderInfo?.avatarUrl ||
+                event.senderInfo.disambiguatedDisplayName != nextDisplayableEvent?.senderInfo?.disambiguatedDisplayName ||
+                nextDisplayableEvent.root.getClearType() !in listOf(EventType.MESSAGE, EventType.STICKER, EventType.ENCRYPTED) ||
+                isNextMessageReceivedMoreThanOneHourAgo ||
+                isTileTypeMessage(nextDisplayableEvent) ||
+                nextDisplayableEvent.isEdition()
 
         val messageLayout = when (layoutSettingsProvider.getLayoutSettings()) {
-            TimelineLayoutSettings.MODERN -> TimelineMessageLayout.Modern(showInformation, showInformation, showInformation || vectorPreferences.alwaysShowTimeStamps())
+            TimelineLayoutSettings.MODERN -> {
+                TimelineMessageLayout.Modern(
+                        showAvatar = showInformation,
+                        showDisplayName = showInformation,
+                        showTimestamp = showInformation || vectorPreferences.alwaysShowTimeStamps()
+                )
+            }
             TimelineLayoutSettings.BUBBLE -> {
                 val isFirstFromThisSender = nextDisplayableEvent?.root?.senderId != event.root.senderId || addDaySeparator
                 val isLastFromThisSender = prevDisplayableEvent?.root?.senderId != event.root.senderId || prevDisplayableEvent?.root?.localDateTime()?.toLocalDate() != date.toLocalDate()
                 TimelineMessageLayout.Bubble(
-                        showAvatar = showInformation,
-                        showDisplayName = showInformation,
+                        showAvatar = showInformation && !isSentByMe,
+                        showDisplayName = showInformation && !isSentByMe,
                         isIncoming = !isSentByMe,
                         isFirstFromThisSender = isFirstFromThisSender,
                         isLastFromThisSender = isLastFromThisSender

From b9cc7959967aa2098b99eb9977b97b13aa308d1f Mon Sep 17 00:00:00 2001
From: ganfra 
Date: Thu, 13 Jan 2022 12:33:36 +0100
Subject: [PATCH 097/581] Bubbles : fix background colors

---
 library/ui-styles/src/main/res/values/colors.xml     |  3 ---
 .../room/detail/timeline/view/MessageBubbleView.kt   | 12 ++++++++----
 .../res/drawable/bg_timeline_incoming_message.xml    |  4 ----
 .../res/drawable/bg_timeline_outgoing_message.xml    |  4 ----
 vector/src/main/res/layout/view_message_bubble.xml   |  1 -
 5 files changed, 8 insertions(+), 16 deletions(-)
 delete mode 100644 vector/src/main/res/drawable/bg_timeline_incoming_message.xml
 delete mode 100644 vector/src/main/res/drawable/bg_timeline_outgoing_message.xml

diff --git a/library/ui-styles/src/main/res/values/colors.xml b/library/ui-styles/src/main/res/values/colors.xml
index e3ec542c89f..ca6f6d31424 100644
--- a/library/ui-styles/src/main/res/values/colors.xml
+++ b/library/ui-styles/src/main/res/values/colors.xml
@@ -13,9 +13,6 @@
     #14368BD6
     @color/palette_azure
 
-    #0F0DBD8B
-    @color/element_system_light
-
     
     @color/palette_azure
     @color/palette_melon
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 5bb732bddea..46e3edca441 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
@@ -17,6 +17,7 @@
 package im.vector.app.features.home.room.detail.timeline.view
 
 import android.content.Context
+import android.content.res.ColorStateList
 import android.graphics.drawable.Drawable
 import android.util.AttributeSet
 import android.view.View
@@ -26,12 +27,14 @@ import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.core.content.ContextCompat
 import androidx.core.content.withStyledAttributes
+import androidx.core.graphics.ColorUtils
 import androidx.core.view.updateLayoutParams
 import com.google.android.material.shape.CornerFamily
 import com.google.android.material.shape.MaterialShapeDrawable
 import com.google.android.material.shape.ShapeAppearanceModel
 import im.vector.app.R
 import im.vector.app.core.utils.DimensionConverter
+import im.vector.app.features.themes.ThemeUtils
 
 class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null,
                                                   defStyleAttr: Int = 0)
@@ -116,7 +119,6 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri
             }
             applyTo(bubbleView)
         }
-
     }
 
     private fun createBackgroundDrawable(): Drawable {
@@ -133,14 +135,16 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri
         val shapeAppearanceModelBuilder = ShapeAppearanceModel().toBuilder()
         val backgroundColor: Int
         if (isIncoming) {
-            backgroundColor = R.color.bubble_background_incoming
+            backgroundColor = ThemeUtils.getColor(context, R.attr.vctr_system)
             shapeAppearanceModelBuilder
                     .setTopRightCorner(CornerFamily.ROUNDED, cornerRadius)
                     .setBottomRightCorner(CornerFamily.ROUNDED, cornerRadius)
                     .setTopLeftCorner(topCornerFamily, topRadius)
                     .setBottomLeftCorner(bottomCornerFamily, bottomRadius)
         } else {
-            backgroundColor = R.color.bubble_background_outgoing
+            val resolvedColor = ContextCompat.getColor(context, R.color.palette_element_green)
+            val alpha = if (ThemeUtils.isLightTheme(context)) 0x0E else 0x26
+            backgroundColor = ColorUtils.setAlphaComponent(resolvedColor, alpha)
             shapeAppearanceModelBuilder
                     .setTopLeftCorner(CornerFamily.ROUNDED, cornerRadius)
                     .setBottomLeftCorner(CornerFamily.ROUNDED, cornerRadius)
@@ -149,7 +153,7 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri
         }
         val shapeAppearanceModel = shapeAppearanceModelBuilder.build()
         return MaterialShapeDrawable(shapeAppearanceModel).apply {
-            fillColor = ContextCompat.getColorStateList(context, backgroundColor)
+            fillColor = ColorStateList.valueOf(backgroundColor)
         }
     }
 }
diff --git a/vector/src/main/res/drawable/bg_timeline_incoming_message.xml b/vector/src/main/res/drawable/bg_timeline_incoming_message.xml
deleted file mode 100644
index 2cbca337022..00000000000
--- a/vector/src/main/res/drawable/bg_timeline_incoming_message.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-    
-
\ No newline at end of file
diff --git a/vector/src/main/res/drawable/bg_timeline_outgoing_message.xml b/vector/src/main/res/drawable/bg_timeline_outgoing_message.xml
deleted file mode 100644
index 0f75705a773..00000000000
--- a/vector/src/main/res/drawable/bg_timeline_outgoing_message.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-    
-
\ No newline at end of file
diff --git a/vector/src/main/res/layout/view_message_bubble.xml b/vector/src/main/res/layout/view_message_bubble.xml
index 8e9a95222a0..8570a3dee2d 100644
--- a/vector/src/main/res/layout/view_message_bubble.xml
+++ b/vector/src/main/res/layout/view_message_bubble.xml
@@ -91,7 +91,6 @@
             android:layout_marginStart="0dp"
             android:layout_marginEnd="0dp"
             android:addStatesFromChildren="true"
-            android:background="@drawable/bg_timeline_incoming_message"
             android:paddingStart="4dp"
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintTop_toTopOf="parent">

From baee076e41c628ff3897b80097212f8b8fd40ae9 Mon Sep 17 00:00:00 2001
From: ganfra 
Date: Thu, 13 Jan 2022 12:33:58 +0100
Subject: [PATCH 098/581] Bubbles: fix types using wrong layout

---
 .../timeline/factory/MessageItemFactory.kt    |  5 +-
 .../timeline/style/TimelineMessageLayout.kt   |  9 +--
 .../style/TimelineMessageLayoutFactory.kt     | 56 ++++++++++++++-----
 3 files changed, 49 insertions(+), 21 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 80343fd909e..a80b9484287 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
@@ -172,9 +172,7 @@ class MessageItemFactory @Inject constructor(
             is MessagePollContent                -> buildPollContent(messageContent, informationData, highlight, callback, attributes)
             else                                 -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes)
         }
-        return messageItem?.takeIf {
-            it.layout == R.layout.item_timeline_event_base
-        }?.apply {
+        return messageItem?.apply {
             layout(informationData.messageLayout.layoutRes)
         }
     }
@@ -650,6 +648,7 @@ class MessageItemFactory @Inject constructor(
     private fun buildRedactedItem(attributes: AbsMessageItem.Attributes,
                                   highlight: Boolean): RedactedMessageItem? {
         return RedactedMessageItem_()
+                .layout(attributes.informationData.messageLayout.layoutRes)
                 .leftGuideline(avatarSizeProvider.leftGuideline)
                 .attributes(attributes)
                 .highlighted(highlight)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt
index 48dba0a58ae..50f4e95cee7 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt
@@ -27,10 +27,11 @@ sealed interface TimelineMessageLayout : Parcelable {
     val showTimestamp: Boolean
 
     @Parcelize
-    data class Modern(override val showAvatar: Boolean,
-                      override val showDisplayName: Boolean,
-                      override val showTimestamp: Boolean,
-                      override val layoutRes: Int = R.layout.item_timeline_event_base) : TimelineMessageLayout
+    data class Default(override val showAvatar: Boolean,
+                       override val showDisplayName: Boolean,
+                       override val showTimestamp: Boolean,
+                       // Keep defaultLayout generated on epoxy items
+                       override val layoutRes: Int = 0) : TimelineMessageLayout
 
     @Parcelize
     data class Bubble(override val showAvatar: Boolean,
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 bd37c2d66a0..9b2877f0f89 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
@@ -21,6 +21,7 @@ import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFact
 import im.vector.app.features.settings.VectorPreferences
 import org.matrix.android.sdk.api.session.Session
 import org.matrix.android.sdk.api.session.events.model.EventType
+import org.matrix.android.sdk.api.session.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
@@ -31,6 +32,18 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess
                                                        private val layoutSettingsProvider: TimelineLayoutSettingsProvider,
                                                        private val vectorPreferences: VectorPreferences) {
 
+    companion object {
+        private val EVENT_TYPES_WITH_BUBBLE_LAYOUT = setOf(
+                EventType.MESSAGE,
+                EventType.ENCRYPTED,
+                EventType.STICKER
+        )
+        private val MSG_TYPES_WITHOUT_BUBBLE_LAYOUT = setOf(
+                MessageType.MSGTYPE_POLL_START,
+                MessageType.MSGTYPE_VERIFICATION_REQUEST
+        )
+    }
+
     fun create(params: TimelineItemFactoryParams): TimelineMessageLayout {
 
         val event = params.event
@@ -55,27 +68,42 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess
 
         val messageLayout = when (layoutSettingsProvider.getLayoutSettings()) {
             TimelineLayoutSettings.MODERN -> {
-                TimelineMessageLayout.Modern(
-                        showAvatar = showInformation,
-                        showDisplayName = showInformation,
-                        showTimestamp = showInformation || vectorPreferences.alwaysShowTimeStamps()
-                )
+                buildModernLayout(showInformation)
             }
             TimelineLayoutSettings.BUBBLE -> {
-                val isFirstFromThisSender = nextDisplayableEvent?.root?.senderId != event.root.senderId || addDaySeparator
-                val isLastFromThisSender = prevDisplayableEvent?.root?.senderId != event.root.senderId || prevDisplayableEvent?.root?.localDateTime()?.toLocalDate() != date.toLocalDate()
-                TimelineMessageLayout.Bubble(
-                        showAvatar = showInformation && !isSentByMe,
-                        showDisplayName = showInformation && !isSentByMe,
-                        isIncoming = !isSentByMe,
-                        isFirstFromThisSender = isFirstFromThisSender,
-                        isLastFromThisSender = isLastFromThisSender
-                )
+                val type = event.root.getClearType()
+                if (type in EVENT_TYPES_WITH_BUBBLE_LAYOUT) {
+                    val messageContent = if (type == EventType.MESSAGE) params.event.getLastMessageContent() else null
+                    if (messageContent?.msgType in MSG_TYPES_WITHOUT_BUBBLE_LAYOUT) {
+                        buildModernLayout(showInformation)
+                    }
+                    val isFirstFromThisSender = nextDisplayableEvent?.root?.senderId != event.root.senderId || addDaySeparator
+                    val isLastFromThisSender = prevDisplayableEvent?.root?.senderId != event.root.senderId
+                            || prevDisplayableEvent?.root?.localDateTime()?.toLocalDate() != date.toLocalDate()
+
+                    TimelineMessageLayout.Bubble(
+                            showAvatar = showInformation && !isSentByMe,
+                            showDisplayName = showInformation && !isSentByMe,
+                            isIncoming = !isSentByMe,
+                            isFirstFromThisSender = isFirstFromThisSender,
+                            isLastFromThisSender = isLastFromThisSender
+                    )
+                } else {
+                    buildModernLayout(showInformation)
+                }
             }
         }
         return messageLayout
     }
 
+    private fun buildModernLayout(showInformation: Boolean): TimelineMessageLayout.Default {
+        return TimelineMessageLayout.Default(
+                showAvatar = showInformation,
+                showDisplayName = showInformation,
+                showTimestamp = showInformation || vectorPreferences.alwaysShowTimeStamps()
+        )
+    }
+
     /**
      * Tiles type message never show the sender information (like verification request), so we should repeat it for next message
      * even if same sender

From 5ac155285bcbf13992e3e2ff2d9352f30bbe0cb3 Mon Sep 17 00:00:00 2001
From: ganfra 
Date: Thu, 13 Jan 2022 13:14:37 +0100
Subject: [PATCH 099/581] Bubbles: some clean up

---
 tools/check/forbidden_strings_in_code.txt     |  2 +-
 .../app/features/home/AvatarRenderer.kt       |  1 -
 .../helper/MessageInformationDataFactory.kt   |  3 ++-
 .../timeline/style/TimelineMessageLayout.kt   | 21 ++++++++++++-------
 .../style/TimelineMessageLayoutFactory.kt     |  5 ++---
 .../detail/timeline/view/MessageBubbleView.kt |  4 ++--
 vector/src/main/res/values/strings.xml        |  1 +
 .../res/xml/vector_settings_preferences.xml   |  2 +-
 8 files changed, 22 insertions(+), 17 deletions(-)

diff --git a/tools/check/forbidden_strings_in_code.txt b/tools/check/forbidden_strings_in_code.txt
index a0c7c2f4c62..cbfaf202472 100644
--- a/tools/check/forbidden_strings_in_code.txt
+++ b/tools/check/forbidden_strings_in_code.txt
@@ -160,7 +160,7 @@ Formatter\.formatShortFileSize===1
 # android\.text\.TextUtils
 
 ### This is not a rule, but a warning: the number of "enum class" has changed. For Json classes, it is mandatory that they have `@JsonClass(generateAdapter = false)`. If the enum is not used as a Json class, change the value in file forbidden_strings_in_code.txt
-enum class===115
+enum class===116
 
 ### Do not import temporary legacy classes
 import org.matrix.android.sdk.internal.legacy.riot===3
diff --git a/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt b/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt
index be689eb27a3..2ee32336370 100644
--- a/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt
+++ b/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt
@@ -145,7 +145,6 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
                         }
                         else                    -> {
                             it.apply(RequestOptions.circleCropTransform())
-
                         }
                     }
                 }
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 2edab8af746..276802e574b 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
@@ -65,7 +65,8 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
         val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate()
 
         val isFirstFromThisSender = nextDisplayableEvent?.root?.senderId != event.root.senderId || addDaySeparator
-        val isLastFromThisSender = prevDisplayableEvent?.root?.senderId != event.root.senderId || prevDisplayableEvent?.root?.localDateTime()?.toLocalDate() != date.toLocalDate()
+        val isLastFromThisSender = prevDisplayableEvent?.root?.senderId != event.root.senderId ||
+                prevDisplayableEvent?.root?.localDateTime()?.toLocalDate() != date.toLocalDate()
 
         val time = dateFormatter.format(event.root.originServerTs, DateFormatKind.MESSAGE_SIMPLE)
         val e2eDecoration = getE2EDecoration(roomSummary, event)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt
index 50f4e95cee7..bd4be10783b 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayout.kt
@@ -30,16 +30,21 @@ sealed interface TimelineMessageLayout : Parcelable {
     data class Default(override val showAvatar: Boolean,
                        override val showDisplayName: Boolean,
                        override val showTimestamp: Boolean,
-                       // Keep defaultLayout generated on epoxy items
+            // Keep defaultLayout generated on epoxy items
                        override val layoutRes: Int = 0) : TimelineMessageLayout
 
     @Parcelize
-    data class Bubble(override val showAvatar: Boolean,
-                      override val showDisplayName: Boolean,
-                      override val showTimestamp: Boolean = true,
-                      val isIncoming: Boolean,
-                      val isFirstFromThisSender: Boolean,
-                      val isLastFromThisSender: Boolean,
-                      override val layoutRes: Int = if (isIncoming) R.layout.item_timeline_event_bubble_incoming_base else R.layout.item_timeline_event_bubble_outgoing_base,
+    data class Bubble(
+            override val showAvatar: Boolean,
+            override val showDisplayName: Boolean,
+            override val showTimestamp: Boolean = true,
+            val isIncoming: Boolean,
+            val isFirstFromThisSender: Boolean,
+            val isLastFromThisSender: Boolean,
+            override val layoutRes: Int = if (isIncoming) {
+                R.layout.item_timeline_event_bubble_incoming_base
+            } else {
+                R.layout.item_timeline_event_bubble_outgoing_base
+            },
     ) : TimelineMessageLayout
 }
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 9b2877f0f89..6c266791055 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
@@ -45,7 +45,6 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess
     }
 
     fun create(params: TimelineItemFactoryParams): TimelineMessageLayout {
-
         val event = params.event
         val nextDisplayableEvent = params.nextDisplayableEvent
         val prevDisplayableEvent = params.prevDisplayableEvent
@@ -78,8 +77,8 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess
                         buildModernLayout(showInformation)
                     }
                     val isFirstFromThisSender = nextDisplayableEvent?.root?.senderId != event.root.senderId || addDaySeparator
-                    val isLastFromThisSender = prevDisplayableEvent?.root?.senderId != event.root.senderId
-                            || prevDisplayableEvent?.root?.localDateTime()?.toLocalDate() != date.toLocalDate()
+                    val isLastFromThisSender = prevDisplayableEvent?.root?.senderId != event.root.senderId ||
+                            prevDisplayableEvent?.root?.localDateTime()?.toLocalDate() != date.toLocalDate()
 
                     TimelineMessageLayout.Bubble(
                             showAvatar = showInformation && !isSentByMe,
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 46e3edca441..68e32861c3b 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
@@ -37,8 +37,8 @@ import im.vector.app.core.utils.DimensionConverter
 import im.vector.app.features.themes.ThemeUtils
 
 class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null,
-                                                  defStyleAttr: Int = 0)
-    : RelativeLayout(context, attrs, defStyleAttr), MessageViewConfiguration {
+                                                  defStyleAttr: Int = 0) :
+    RelativeLayout(context, attrs, defStyleAttr), MessageViewConfiguration {
 
     override var isIncoming: Boolean = false
         set(value) {
diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml
index 34ac5fcddc7..4e927701d79 100644
--- a/vector/src/main/res/values/strings.xml
+++ b/vector/src/main/res/values/strings.xml
@@ -3701,4 +3701,5 @@
     Poll ended
     Remove poll
     Are you sure you want to remove this poll? You won\'t be able to recover it once removed.
+    Message bubbles
 
diff --git a/vector/src/main/res/xml/vector_settings_preferences.xml b/vector/src/main/res/xml/vector_settings_preferences.xml
index ac8a48fb2e3..a25562e3d29 100644
--- a/vector/src/main/res/xml/vector_settings_preferences.xml
+++ b/vector/src/main/res/xml/vector_settings_preferences.xml
@@ -25,7 +25,7 @@
         
+            android:title="@string/message_bubbles" />
 
         
Date: Fri, 14 Jan 2022 13:02:08 +0200
Subject: [PATCH 100/581] Fix permalink handling for threads regarding timeline
 changes

---
 .../sdk/internal/session/room/timeline/DefaultTimeline.kt   | 5 ++++-
 .../home/room/threads/list/model/ThreadListModel.kt         | 2 +-
 vector/src/main/res/layout/item_thread_list.xml             | 5 +++--
 vector/src/main/res/layout/item_timeline_event_base.xml     | 4 ++--
 .../src/main/res/layout/view_room_detail_thread_toolbar.xml | 2 +-
 vector/src/main/res/layout/view_thread_room_summary.xml     | 6 +++---
 6 files changed, 14 insertions(+), 10 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
index 037edbd83dd..b2b033c0bb2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
@@ -239,7 +239,10 @@ internal class DefaultTimeline(private val roomId: String,
             else                      -> buildStrategy(LoadTimelineStrategy.Mode.Permalink(eventId))
         }
 
-        initPaginationStates(eventId)
+        rootThreadEventId?.let {
+            initPaginationStates(null)
+        } ?: initPaginationStates(eventId)
+
         strategy.onStart()
         loadMore(
                 count = strategyDependencies.timelineSettings.initialSize,
diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadListModel.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadListModel.kt
index b8909527197..72ba673972f 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadListModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadListModel.kt
@@ -60,7 +60,7 @@ abstract class ThreadListModel : VectorEpoxyModel() {
         holder.dateTextView.text = date
         if (rootMessageDeleted) {
             holder.rootMessageTextView.text = holder.view.context.getString(R.string.event_redacted)
-            holder.rootMessageTextView.setLeftDrawable(R.drawable.ic_trash_16, R.attr.colorOnPrimary)
+            holder.rootMessageTextView.setLeftDrawable(R.drawable.ic_trash_16, R.attr.vctr_content_tertiary)
             holder.rootMessageTextView.compoundDrawablePadding = DimensionConverter(holder.view.context.resources).dpToPx(10)
         } else {
             holder.rootMessageTextView.text = rootMessage
diff --git a/vector/src/main/res/layout/item_thread_list.xml b/vector/src/main/res/layout/item_thread_list.xml
index 6a1d075b7cf..37186f031c9 100644
--- a/vector/src/main/res/layout/item_thread_list.xml
+++ b/vector/src/main/res/layout/item_thread_list.xml
@@ -45,7 +45,7 @@
         android:layout_width="0dp"
         android:layout_height="wrap_content"
         android:layout_marginStart="8dp"
-        android:layout_marginEnd="25dp"
+        android:layout_marginEnd="28dp"
         android:gravity="end"
         android:maxLines="1"
         android:textColor="?vctr_content_secondary"
@@ -73,6 +73,7 @@
         style="@style/Widget.Vector.TextView.Body"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
+        android:layout_marginTop="3dp"
         android:layout_marginEnd="25dp"
         android:ellipsize="end"
         android:maxLines="2"
@@ -91,7 +92,7 @@
         android:maxWidth="496dp"
         android:minWidth="144dp"
         android:paddingTop="10dp"
-        android:paddingBottom="10dp"
+        android:paddingBottom="12dp"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="@id/threadSummaryTitleTextView"
         app:layout_constraintTop_toBottomOf="@id/threadSummaryRootMessageTextView"
diff --git a/vector/src/main/res/layout/item_timeline_event_base.xml b/vector/src/main/res/layout/item_timeline_event_base.xml
index 69712a02fcd..532cd355c50 100644
--- a/vector/src/main/res/layout/item_timeline_event_base.xml
+++ b/vector/src/main/res/layout/item_timeline_event_base.xml
@@ -205,8 +205,8 @@
         android:contentDescription="@string/room_threads_filter"
         android:maxWidth="496dp"
         android:minWidth="144dp"
-        android:paddingTop="10dp"
-        android:paddingBottom="10dp"
+        android:paddingTop="8dp"
+        android:paddingBottom="8dp"
         android:visibility="gone"
         tools:visibility="visible">
 
diff --git a/vector/src/main/res/layout/view_room_detail_thread_toolbar.xml b/vector/src/main/res/layout/view_room_detail_thread_toolbar.xml
index 5de8ec3efa1..af65dee2b40 100644
--- a/vector/src/main/res/layout/view_room_detail_thread_toolbar.xml
+++ b/vector/src/main/res/layout/view_room_detail_thread_toolbar.xml
@@ -37,7 +37,7 @@
 
     
+        tools:text="123" />
 
     
Date: Fri, 14 Jan 2022 15:11:20 +0200
Subject: [PATCH 101/581] Add empty screen UI on empty thread list

---
 .../threads/list/views/ThreadListFragment.kt  |  7 ++
 .../main/res/layout/fragment_thread_list.xml  | 85 +++++++++++++++++--
 vector/src/main/res/values/strings.xml        |  3 +
 3 files changed, 90 insertions(+), 5 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt
index 2dcd6a48a3f..281a292c392 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt
@@ -36,6 +36,7 @@ import im.vector.app.features.home.room.threads.ThreadsActivity
 import im.vector.app.features.home.room.threads.arguments.ThreadListArgs
 import im.vector.app.features.home.room.threads.list.viewmodel.ThreadListController
 import im.vector.app.features.home.room.threads.list.viewmodel.ThreadListViewModel
+import im.vector.app.features.home.room.threads.list.viewmodel.ThreadListViewState
 import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
 import org.matrix.android.sdk.api.util.MatrixItem
 import javax.inject.Inject
@@ -90,6 +91,7 @@ class ThreadListFragment @Inject constructor(
     }
 
     override fun invalidate() = withState(threadListViewModel) { state ->
+        renderEmptyStateIfNeeded(state)
         threadListController.update(state)
     }
 
@@ -104,4 +106,9 @@ class ThreadListFragment @Inject constructor(
     override fun onThreadClicked(timelineEvent: TimelineEvent) {
         (activity as? ThreadsActivity)?.navigateToThreadTimeline(timelineEvent)
     }
+
+    private fun renderEmptyStateIfNeeded(state: ThreadListViewState) {
+        val show = state.rootThreadEventList.invoke().isNullOrEmpty()
+        views.threadListEmptyConstraintLayout.isVisible = show
+    }
 }
diff --git a/vector/src/main/res/layout/fragment_thread_list.xml b/vector/src/main/res/layout/fragment_thread_list.xml
index be042a7bcec..77f46bf3eea 100644
--- a/vector/src/main/res/layout/fragment_thread_list.xml
+++ b/vector/src/main/res/layout/fragment_thread_list.xml
@@ -1,6 +1,7 @@
 
 
 
@@ -25,16 +26,90 @@
     
 
     
+
+    
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:visibility="visible">
+
+        
+
+        
+
+        
+
+        
 
 
+    
 
\ No newline at end of file
diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml
index d88b7a6f2f4..b86b2dd27f6 100644
--- a/vector/src/main/res/values/strings.xml
+++ b/vector/src/main/res/values/strings.xml
@@ -1046,6 +1046,9 @@
     Shows all threads from current room
     My Threads
     Shows all threads you’ve participated in
+    Keep discussions organised with threads
+    Threads help keep your conversations on-topic and easy to track.
+    Tip: Long tap a message and use “Reply in thread”.
 
     
     Reason for reporting this content

From 3a3cce85f883d67b37c1442ee2bf4b11b50da4aa Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Fri, 14 Jan 2022 18:42:57 +0200
Subject: [PATCH 102/581] Add encryption shield Change thread list filtering UI
 tick to radio buttons

---
 .../home/room/detail/TimelineFragment.kt      |  3 ++
 .../home/room/detail/search/SearchFragment.kt |  7 ++++-
 .../home/room/threads/ThreadsActivity.kt      |  1 +
 .../room/threads/arguments/ThreadListArgs.kt  |  2 ++
 .../threads/arguments/ThreadTimelineArgs.kt   |  2 ++
 .../list/views/ThreadListBottomSheet.kt       | 31 +++++++++++++++++--
 .../threads/list/views/ThreadListFragment.kt  |  1 +
 .../features/navigation/DefaultNavigator.kt   |  3 +-
 .../features/permalink/PermalinkHandler.kt    |  7 ++++-
 vector/src/main/res/drawable/ic_filter.xml    | 15 ++-------
 .../res/layout/bottom_sheet_thread_list.xml   |  1 -
 .../view_room_detail_thread_toolbar.xml       |  9 ++++++
 12 files changed, 62 insertions(+), 20 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 b7af9caf15a..2c4ac2d9d77 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
@@ -1594,6 +1594,7 @@ class TimelineFragment @Inject constructor(
             timelineArgs.threadTimelineArgs?.let {
                 val matrixItem = MatrixItem.RoomItem(it.roomId, it.displayName, it.avatarUrl)
                 avatarRenderer.render(matrixItem, views.includeThreadToolbar.roomToolbarThreadImageView)
+                views.includeThreadToolbar.roomToolbarThreadShieldImageView.render(it.roomEncryptionTrustLevel)
                 views.includeThreadToolbar.roomToolbarThreadSubtitleTextView.text = it.displayName
             }
             views.includeThreadToolbar.roomToolbarThreadTitleTextView.text = resources.getText(R.string.thread_timeline_title)
@@ -2321,6 +2322,7 @@ class TimelineFragment @Inject constructor(
                     roomId = timelineArgs.roomId,
                     displayName = roomDetailViewModel.getRoomSummary()?.displayName,
                     avatarUrl = roomDetailViewModel.getRoomSummary()?.avatarUrl,
+                    roomEncryptionTrustLevel = roomDetailViewModel.getRoomSummary()?.roomEncryptionTrustLevel,
                     rootThreadEventId = rootThreadEventId)
             navigator.openThread(it, roomThreadDetailArgs)
         }
@@ -2336,6 +2338,7 @@ class TimelineFragment @Inject constructor(
             val roomThreadDetailArgs = ThreadTimelineArgs(
                     roomId = timelineArgs.roomId,
                     displayName = roomDetailViewModel.getRoomSummary()?.displayName,
+                    roomEncryptionTrustLevel = roomDetailViewModel.getRoomSummary()?.roomEncryptionTrustLevel,
                     avatarUrl = roomDetailViewModel.getRoomSummary()?.avatarUrl)
             navigator.openThreadList(it, roomThreadDetailArgs)
         }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchFragment.kt
index aba5c65ae80..62c142238eb 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchFragment.kt
@@ -126,7 +126,12 @@ class SearchFragment @Inject constructor(
     private fun navigateToEvent(event: Event) {
         val roomId = event.roomId ?: return
         event.getRootThreadEventId()?.let {
-            val threadTimelineArgs = ThreadTimelineArgs(roomId, displayName = fragmentArgs.roomDisplayName, fragmentArgs.roomAvatarUrl, it)
+            val threadTimelineArgs = ThreadTimelineArgs(
+                    roomId = roomId,
+                    displayName = fragmentArgs.roomDisplayName,
+                    avatarUrl = fragmentArgs.roomAvatarUrl,
+                    roomEncryptionTrustLevel = null,
+                    rootThreadEventId = it)
             navigator.openThread(requireContext(), threadTimelineArgs, event.eventId)
         } ?: navigator.openRoom(requireContext(), roomId, event.eventId)
     }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt
index fe3c32ae651..b9d77a323a9 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/threads/ThreadsActivity.kt
@@ -100,6 +100,7 @@ class ThreadsActivity : VectorBaseActivity(), ToolbarCon
                 roomId = timelineEvent.roomId,
                 displayName = timelineEvent.senderInfo.displayName,
                 avatarUrl = timelineEvent.senderInfo.avatarUrl,
+                roomEncryptionTrustLevel = null,
                 rootThreadEventId = timelineEvent.eventId)
         val commonOption: (FragmentTransaction) -> Unit = {
             it.setCustomAnimations(
diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/arguments/ThreadListArgs.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/arguments/ThreadListArgs.kt
index 50819a30174..aa3746ea418 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/threads/arguments/ThreadListArgs.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/threads/arguments/ThreadListArgs.kt
@@ -18,10 +18,12 @@ package im.vector.app.features.home.room.threads.arguments
 
 import android.os.Parcelable
 import kotlinx.parcelize.Parcelize
+import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
 
 @Parcelize
 data class ThreadListArgs(
         val roomId: String,
         val displayName: String?,
         val avatarUrl: String?,
+        val roomEncryptionTrustLevel: RoomEncryptionTrustLevel?
 ) : Parcelable
diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/arguments/ThreadTimelineArgs.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/arguments/ThreadTimelineArgs.kt
index 2ebed2f7454..aadad3d97ce 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/threads/arguments/ThreadTimelineArgs.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/threads/arguments/ThreadTimelineArgs.kt
@@ -18,11 +18,13 @@ package im.vector.app.features.home.room.threads.arguments
 
 import android.os.Parcelable
 import kotlinx.parcelize.Parcelize
+import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
 
 @Parcelize
 data class ThreadTimelineArgs(
         val roomId: String,
         val displayName: String?,
         val avatarUrl: String?,
+        val roomEncryptionTrustLevel: RoomEncryptionTrustLevel?,
         val rootThreadEventId: String? = null
 ) : Parcelable
diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListBottomSheet.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListBottomSheet.kt
index bd62f658970..7ad4804e5b2 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListBottomSheet.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListBottomSheet.kt
@@ -16,17 +16,21 @@
 
 package im.vector.app.features.home.room.threads.list.views
 
+import android.graphics.drawable.Drawable
 import android.os.Bundle
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
+import androidx.annotation.AttrRes
 import androidx.core.content.ContextCompat
+import androidx.core.graphics.drawable.DrawableCompat
 import com.airbnb.mvrx.parentFragmentViewModel
 import im.vector.app.R
 import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
 import im.vector.app.databinding.BottomSheetThreadListBinding
 import im.vector.app.features.home.room.threads.list.viewmodel.ThreadListViewModel
 import im.vector.app.features.home.room.threads.list.viewmodel.ThreadListViewState
+import im.vector.app.features.themes.ThemeUtils
 
 class ThreadListBottomSheet : VectorBaseBottomSheetDialogFragment() {
 
@@ -53,8 +57,29 @@ class ThreadListBottomSheet : VectorBaseBottomSheetDialogFragment
   
-  
+      android:pathData="M10.9996,18H12.9996C13.5496,18 13.9996,17.55 13.9996,17C13.9996,16.45 13.5496,16 12.9996,16H10.9996C10.4496,16 9.9996,16.45 9.9996,17C9.9996,17.55 10.4496,18 10.9996,18ZM2.9996,7C2.9996,7.55 3.4496,8 3.9996,8H19.9996C20.5496,8 20.9996,7.55 20.9996,7C20.9996,6.45 20.5496,6 19.9996,6H3.9996C3.4496,6 2.9996,6.45 2.9996,7ZM6.9996,13H16.9996C17.5496,13 17.9996,12.55 17.9996,12C17.9996,11.45 17.5496,11 16.9996,11H6.9996C6.4496,11 5.9996,11.45 5.9996,12C5.9996,12.55 6.4496,13 6.9996,13Z"
+      android:fillColor="#737D8C"/>
 
diff --git a/vector/src/main/res/layout/bottom_sheet_thread_list.xml b/vector/src/main/res/layout/bottom_sheet_thread_list.xml
index 3fd75e18235..e736f30edc3 100644
--- a/vector/src/main/res/layout/bottom_sheet_thread_list.xml
+++ b/vector/src/main/res/layout/bottom_sheet_thread_list.xml
@@ -1,7 +1,6 @@
 
 
 
+    
+
     
Date: Fri, 14 Jan 2022 19:19:23 +0100
Subject: [PATCH 103/581] Bubbles: make it works for file, voice and polls.
 Also add parity for "modern" layout.

---
 .../src/main/res/drawable/bg_media_pill.xml   |   3 -
 .../main/res/drawable/file_progress_bar.xml   |   8 +-
 .../ui-styles/src/main/res/values/dimens.xml  |   3 +
 .../src/main/res/values/styles_progress.xml   |   1 +
 .../src/main/res/values/styles_timeline.xml   |  10 ++
 .../ContentDownloadStateTrackerBinder.kt      |  12 +-
 .../detail/timeline/item/MessageFileItem.kt   |  15 ++-
 .../timeline/item/MessageImageVideoItem.kt    |   5 +-
 .../detail/timeline/item/MessageVoiceItem.kt  |  13 +++
 .../style/TimelineMessageLayoutFactory.kt     |  27 ++---
 .../detail/timeline/view/MessageBubbleView.kt |  23 +++-
 .../res/drawable/overlay_bubble_media.xml     |   8 ++
 .../res/layout/item_timeline_event_base.xml   |   4 +-
 .../layout/item_timeline_event_file_stub.xml  | 104 ++++++++----------
 ...item_timeline_event_media_message_stub.xml |  12 ++
 .../res/layout/item_timeline_event_poll.xml   |   4 +-
 ...em_timeline_event_view_stubs_container.xml |   5 +-
 .../layout/item_timeline_event_voice_stub.xml |  29 ++---
 vector/src/main/res/layout/view_file_icon.xml |  14 +--
 .../main/res/layout/view_message_bubble.xml   |   2 +-
 .../layout/view_voice_message_recorder.xml    |   2 +-
 21 files changed, 172 insertions(+), 132 deletions(-)
 rename vector/src/main/res/drawable/bg_voice_playback.xml => library/ui-styles/src/main/res/drawable/bg_media_pill.xml (84%)
 create mode 100644 vector/src/main/res/drawable/overlay_bubble_media.xml

diff --git a/vector/src/main/res/drawable/bg_voice_playback.xml b/library/ui-styles/src/main/res/drawable/bg_media_pill.xml
similarity index 84%
rename from vector/src/main/res/drawable/bg_voice_playback.xml
rename to library/ui-styles/src/main/res/drawable/bg_media_pill.xml
index 4474c003454..2ad9ca99181 100644
--- a/vector/src/main/res/drawable/bg_voice_playback.xml
+++ b/library/ui-styles/src/main/res/drawable/bg_media_pill.xml
@@ -2,9 +2,6 @@
 
     
     
-    
     
 
     
-        
-            
-            
+        
+            
         
     
 
     
         
-            
-                
+            
                 
             
         
diff --git a/library/ui-styles/src/main/res/values/dimens.xml b/library/ui-styles/src/main/res/values/dimens.xml
index 6b6f1a3aef0..3955bf44fd2 100644
--- a/library/ui-styles/src/main/res/values/dimens.xml
+++ b/library/ui-styles/src/main/res/values/dimens.xml
@@ -15,6 +15,8 @@
     72dp
     16dp
 
+    32dp
+
     40dp
     60dp
 
@@ -51,6 +53,7 @@
 
     28dp
     62dp
+    300dp
 
     
     0.05
diff --git a/library/ui-styles/src/main/res/values/styles_progress.xml b/library/ui-styles/src/main/res/values/styles_progress.xml
index 712e7e98b68..04a0e01b588 100644
--- a/library/ui-styles/src/main/res/values/styles_progress.xml
+++ b/library/ui-styles/src/main/res/values/styles_progress.xml
@@ -6,6 +6,7 @@
     
diff --git a/library/ui-styles/src/main/res/values/styles_timeline.xml b/library/ui-styles/src/main/res/values/styles_timeline.xml
index 7fd7eac0ec3..ef2f694d3e3 100644
--- a/library/ui-styles/src/main/res/values/styles_timeline.xml
+++ b/library/ui-styles/src/main/res/values/styles_timeline.xml
@@ -12,4 +12,14 @@
         4dp
     
 
+    
+
 
\ No newline at end of file
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/ContentDownloadStateTrackerBinder.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/ContentDownloadStateTrackerBinder.kt
index caf01311440..e4405570a6f 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/ContentDownloadStateTrackerBinder.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/ContentDownloadStateTrackerBinder.kt
@@ -29,9 +29,7 @@ import org.matrix.android.sdk.api.session.file.ContentDownloadStateTracker
 import javax.inject.Inject
 
 @ActivityScoped
-class ContentDownloadStateTrackerBinder @Inject constructor(private val activeSessionHolder: ActiveSessionHolder,
-                                                            private val messageColorProvider: MessageColorProvider,
-                                                            private val errorFormatter: ErrorFormatter) {
+class ContentDownloadStateTrackerBinder @Inject constructor(private val activeSessionHolder: ActiveSessionHolder){
 
     private val updateListeners = mutableMapOf()
 
@@ -39,7 +37,7 @@ class ContentDownloadStateTrackerBinder @Inject constructor(private val activeSe
              holder: MessageFileItem.Holder) {
         activeSessionHolder.getSafeActiveSession()?.also { session ->
             val downloadStateTracker = session.contentDownloadProgressTracker()
-            val updateListener = ContentDownloadUpdater(holder, messageColorProvider, errorFormatter)
+            val updateListener = ContentDownloadUpdater(holder)
             updateListeners[mxcUrl] = updateListener
             downloadStateTracker.track(mxcUrl, updateListener)
         }
@@ -62,9 +60,7 @@ class ContentDownloadStateTrackerBinder @Inject constructor(private val activeSe
     }
 }
 
-private class ContentDownloadUpdater(private val holder: MessageFileItem.Holder,
-                                     private val messageColorProvider: MessageColorProvider,
-                                     private val errorFormatter: ErrorFormatter) : ContentDownloadStateTracker.UpdateListener {
+private class ContentDownloadUpdater(private val holder: MessageFileItem.Holder) : ContentDownloadStateTracker.UpdateListener {
 
     override fun onDownloadStateUpdate(state: ContentDownloadStateTracker.State) {
         when (state) {
@@ -124,7 +120,7 @@ private class ContentDownloadUpdater(private val holder: MessageFileItem.Holder,
     private fun handleSuccess() {
         stop()
         holder.fileDownloadProgress.isIndeterminate = false
-        holder.fileDownloadProgress.progress = 100
+        holder.fileDownloadProgress.progress = 0
         holder.fileImageView.setImageResource(R.drawable.ic_paperclip)
     }
 }
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 bd35532eaed..e736c0e2da3 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
@@ -16,6 +16,8 @@
 
 package im.vector.app.features.home.room.detail.timeline.item
 
+import android.content.res.ColorStateList
+import android.graphics.Color
 import android.graphics.Paint
 import android.view.ViewGroup
 import android.widget.ImageView
@@ -29,6 +31,8 @@ import im.vector.app.R
 import im.vector.app.core.epoxy.onClick
 import im.vector.app.features.home.room.detail.timeline.helper.ContentDownloadStateTrackerBinder
 import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder
+import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout
+import im.vector.app.features.themes.ThemeUtils
 
 @EpoxyModelClass(layout = R.layout.item_timeline_event_base)
 abstract class MessageFileItem : AbsMessageItem() {
@@ -73,15 +77,19 @@ abstract class MessageFileItem : AbsMessageItem() {
         } else {
             if (izDownloaded) {
                 holder.fileImageView.setImageResource(iconRes)
-                holder.fileDownloadProgress.progress = 100
+                holder.fileDownloadProgress.progress = 0
             } else {
                 contentDownloadStateTrackerBinder.bind(mxcUrl, holder)
                 holder.fileImageView.setImageResource(R.drawable.ic_download)
-                holder.fileDownloadProgress.progress = 0
             }
         }
 //        holder.view.setOnClickListener(clickListener)
-
+        val backgroundTint = if(attributes.informationData.messageLayout is TimelineMessageLayout.Bubble){
+            Color.TRANSPARENT
+        }else {
+            ThemeUtils.getColor(holder.view.context, R.attr.vctr_content_quinary)
+        }
+        holder.mainLayout.backgroundTintList = ColorStateList.valueOf(backgroundTint)
         holder.filenameView.onClick(attributes.itemClickListener)
         holder.filenameView.setOnLongClickListener(attributes.itemLongClickListener)
         holder.fileImageWrapper.onClick(attributes.itemClickListener)
@@ -98,6 +106,7 @@ abstract class MessageFileItem : AbsMessageItem() {
     override fun getViewStubId() = STUB_ID
 
     class Holder : AbsMessageItem.Holder(STUB_ID) {
+        val mainLayout by bind(R.id.messageFileMainLayout)
         val progressLayout by bind(R.id.messageFileUploadProgressLayout)
         val fileLayout by bind(R.id.messageFileLayout)
         val fileImageView by bind(R.id.messageFileIconView)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt
index e20ad48b172..799cb1a002b 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageImageVideoItem.kt
@@ -29,6 +29,7 @@ import im.vector.app.core.epoxy.onClick
 import im.vector.app.core.files.LocalFilesHelper
 import im.vector.app.core.glide.GlideApp
 import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder
+import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout
 import im.vector.app.features.home.room.detail.timeline.view.MessageViewConfiguration
 import im.vector.app.features.media.ImageContentRenderer
 
@@ -71,7 +72,8 @@ abstract class MessageImageVideoItem : AbsMessageItem(R.id.messageThumbnailView)
         val playContentView by bind(R.id.messageMediaPlayView)
         val mediaContentView by bind(R.id.messageContentMedia)
+        val overlayView by bind(R.id.messageMediaOverlayView)
     }
 
     companion object {
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 b6fa684ecad..1058e3c1f93 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
@@ -16,10 +16,14 @@
 
 package im.vector.app.features.home.room.detail.timeline.item
 
+import android.content.res.ColorStateList
+import android.graphics.Color
 import android.text.format.DateUtils
+import android.view.View
 import android.view.ViewGroup
 import android.widget.ImageButton
 import android.widget.TextView
+import androidx.core.content.ContextCompat
 import androidx.core.view.isVisible
 import com.airbnb.epoxy.EpoxyAttribute
 import com.airbnb.epoxy.EpoxyModelClass
@@ -29,6 +33,8 @@ import im.vector.app.core.epoxy.ClickListener
 import im.vector.app.features.home.room.detail.timeline.helper.ContentDownloadStateTrackerBinder
 import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder
 import im.vector.app.features.home.room.detail.timeline.helper.VoiceMessagePlaybackTracker
+import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout
+import im.vector.app.features.themes.ThemeUtils
 
 @EpoxyModelClass(layout = R.layout.item_timeline_event_base)
 abstract class MessageVoiceItem : AbsMessageItem() {
@@ -80,6 +86,12 @@ abstract class MessageVoiceItem : AbsMessageItem() {
             }
         }
 
+        val backgroundTint = if(attributes.informationData.messageLayout is TimelineMessageLayout.Bubble){
+            Color.TRANSPARENT
+        }else {
+            ThemeUtils.getColor(holder.view.context, R.attr.vctr_content_quinary)
+        }
+        holder.voicePlaybackLayout.backgroundTintList = ColorStateList.valueOf(backgroundTint)
         holder.voicePlaybackControlButton.setOnClickListener { playbackControlButtonClickListener?.invoke(it) }
 
         voiceMessagePlaybackTracker.track(attributes.informationData.eventId, object : VoiceMessagePlaybackTracker.Listener {
@@ -123,6 +135,7 @@ abstract class MessageVoiceItem : AbsMessageItem() {
     override fun getViewStubId() = STUB_ID
 
     class Holder : AbsMessageItem.Holder(STUB_ID) {
+        val voicePlaybackLayout by bind(R.id.voicePlaybackLayout)
         val voiceLayout by bind(R.id.voiceLayout)
         val voicePlaybackControlButton by bind(R.id.voicePlaybackControlButton)
         val voicePlaybackTime by bind(R.id.voicePlaybackTime)
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 6c266791055..f5ca97dc1d1 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
@@ -35,11 +35,11 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess
     companion object {
         private val EVENT_TYPES_WITH_BUBBLE_LAYOUT = setOf(
                 EventType.MESSAGE,
+                EventType.POLL_START,
                 EventType.ENCRYPTED,
                 EventType.STICKER
         )
         private val MSG_TYPES_WITHOUT_BUBBLE_LAYOUT = setOf(
-                MessageType.MSGTYPE_POLL_START,
                 MessageType.MSGTYPE_VERIFICATION_REQUEST
         )
     }
@@ -72,21 +72,22 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess
             TimelineLayoutSettings.BUBBLE -> {
                 val type = event.root.getClearType()
                 if (type in EVENT_TYPES_WITH_BUBBLE_LAYOUT) {
-                    val messageContent = if (type == EventType.MESSAGE) params.event.getLastMessageContent() else null
+                    val messageContent = event.getLastMessageContent()
                     if (messageContent?.msgType in MSG_TYPES_WITHOUT_BUBBLE_LAYOUT) {
                         buildModernLayout(showInformation)
-                    }
-                    val isFirstFromThisSender = nextDisplayableEvent?.root?.senderId != event.root.senderId || addDaySeparator
-                    val isLastFromThisSender = prevDisplayableEvent?.root?.senderId != event.root.senderId ||
-                            prevDisplayableEvent?.root?.localDateTime()?.toLocalDate() != date.toLocalDate()
+                    } else {
+                        val isFirstFromThisSender = nextDisplayableEvent?.root?.senderId != event.root.senderId || addDaySeparator
+                        val isLastFromThisSender = prevDisplayableEvent?.root?.senderId != event.root.senderId ||
+                                prevDisplayableEvent?.root?.localDateTime()?.toLocalDate() != date.toLocalDate()
 
-                    TimelineMessageLayout.Bubble(
-                            showAvatar = showInformation && !isSentByMe,
-                            showDisplayName = showInformation && !isSentByMe,
-                            isIncoming = !isSentByMe,
-                            isFirstFromThisSender = isFirstFromThisSender,
-                            isLastFromThisSender = isLastFromThisSender
-                    )
+                        TimelineMessageLayout.Bubble(
+                                showAvatar = showInformation && !isSentByMe,
+                                showDisplayName = showInformation && !isSentByMe,
+                                isIncoming = !isSentByMe,
+                                isFirstFromThisSender = isFirstFromThisSender,
+                                isLastFromThisSender = isLastFromThisSender,
+                        )
+                    }
                 } else {
                     buildModernLayout(showInformation)
                 }
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 68e32861c3b..0a3746f8a41 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
@@ -23,6 +23,7 @@ import android.util.AttributeSet
 import android.view.View
 import android.view.ViewOutlineProvider
 import android.widget.RelativeLayout
+import android.widget.TextView
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.core.content.ContextCompat
@@ -38,7 +39,7 @@ import im.vector.app.features.themes.ThemeUtils
 
 class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null,
                                                   defStyleAttr: Int = 0) :
-    RelativeLayout(context, attrs, defStyleAttr), MessageViewConfiguration {
+        RelativeLayout(context, attrs, defStyleAttr), MessageViewConfiguration {
 
     override var isIncoming: Boolean = false
         set(value) {
@@ -57,7 +58,7 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri
             render()
         }
 
-    override var showTimeAsOverlay: Boolean = true
+    override var showTimeAsOverlay: Boolean = false
         set(value) {
             field = value
             render()
@@ -69,7 +70,7 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri
         inflate(context, R.layout.view_message_bubble, this)
         context.withStyledAttributes(attrs, R.styleable.MessageBubble) {
             isIncoming = getBoolean(R.styleable.MessageBubble_incoming_style, false)
-            showTimeAsOverlay = getBoolean(R.styleable.MessageBubble_show_time_overlay, true)
+            showTimeAsOverlay = getBoolean(R.styleable.MessageBubble_show_time_overlay, false)
             isFirstFromSender = getBoolean(R.styleable.MessageBubble_is_first, false)
             isLastFromSender = getBoolean(R.styleable.MessageBubble_is_last, false)
         }
@@ -95,6 +96,9 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri
             findViewById(R.id.messageEndGuideline).updateLayoutParams {
                 marginEnd = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_end)
             }
+            findViewById(R.id.messageStartGuideline).updateLayoutParams {
+                marginStart = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_start)
+            }
         } else {
             val oppositeLayoutDirection = if (currentLayoutDirection == View.LAYOUT_DIRECTION_LTR) {
                 View.LAYOUT_DIRECTION_RTL
@@ -108,14 +112,23 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri
             findViewById(R.id.messageEndGuideline).updateLayoutParams {
                 marginEnd = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_start)
             }
+            findViewById(R.id.messageStartGuideline).updateLayoutParams {
+                marginStart = resources.getDimensionPixelSize(R.dimen.chat_bubble_margin_end)
+            }
         }
         ConstraintSet().apply {
             clone(bubbleView)
             clear(R.id.viewStubContainer, ConstraintSet.END)
             if (showTimeAsOverlay) {
-                connect(R.id.viewStubContainer, ConstraintSet.END, R.id.messageTimeView, ConstraintSet.START, 0)
-            } else {
+                val timeColor = ContextCompat.getColor(context, R.color.palette_white)
+                findViewById(R.id.messageTimeView).setTextColor(timeColor)
                 connect(R.id.viewStubContainer, ConstraintSet.END, R.id.parent, ConstraintSet.END, 0)
+                val margin = resources.getDimensionPixelSize(R.dimen.layout_horizontal_margin)
+                setMargin(R.id.messageTimeView, ConstraintSet.END, margin)
+            } else {
+                val timeColor = ThemeUtils.getColor(context, R.attr.vctr_content_tertiary)
+                findViewById(R.id.messageTimeView).setTextColor(timeColor)
+                connect(R.id.viewStubContainer, ConstraintSet.END, R.id.messageTimeView, ConstraintSet.START, 0)
             }
             applyTo(bubbleView)
         }
diff --git a/vector/src/main/res/drawable/overlay_bubble_media.xml b/vector/src/main/res/drawable/overlay_bubble_media.xml
new file mode 100644
index 00000000000..ce34a390372
--- /dev/null
+++ b/vector/src/main/res/drawable/overlay_bubble_media.xml
@@ -0,0 +1,8 @@
+
+
+    
+
\ No newline at end of file
diff --git a/vector/src/main/res/layout/item_timeline_event_base.xml b/vector/src/main/res/layout/item_timeline_event_base.xml
index debf578519c..bc9ed682329 100644
--- a/vector/src/main/res/layout/item_timeline_event_base.xml
+++ b/vector/src/main/res/layout/item_timeline_event_base.xml
@@ -78,14 +78,14 @@
 
     
+        android:addStatesFromChildren="true" />
 
     
-
 
-    
-
-    
-    
-
-        
-    
-
-    
-    
-    
-    
-    
-    
-    
-    
-
-    
-    
-
-    
+        tools:viewBindingIgnore="true">
+
+        
+            
+
+        
+
+        
+        
+
+    
 
     
 
-
+
+
\ No newline at end of file
diff --git a/vector/src/main/res/layout/item_timeline_event_media_message_stub.xml b/vector/src/main/res/layout/item_timeline_event_media_message_stub.xml
index 68abfbfda85..bdfa0c51643 100644
--- a/vector/src/main/res/layout/item_timeline_event_media_message_stub.xml
+++ b/vector/src/main/res/layout/item_timeline_event_media_message_stub.xml
@@ -18,12 +18,24 @@
         tools:layout_height="300dp"
         tools:src="@tools:sample/backgrounds/scenic" />
 
+    
+
     
 
     
 
     
+        tools:visibility="gone" />
 
     
 
 
diff --git a/vector/src/main/res/layout/item_timeline_event_voice_stub.xml b/vector/src/main/res/layout/item_timeline_event_voice_stub.xml
index 5cdd5a815a9..a180afbf8e0 100644
--- a/vector/src/main/res/layout/item_timeline_event_voice_stub.xml
+++ b/vector/src/main/res/layout/item_timeline_event_voice_stub.xml
@@ -1,31 +1,24 @@
 
-
 
     
+        style="@style/TimelineContentMediaPillStyle">
 
         
 
-
+
diff --git a/vector/src/main/res/layout/view_file_icon.xml b/vector/src/main/res/layout/view_file_icon.xml
index 1c5268a50b7..db88802ba1a 100644
--- a/vector/src/main/res/layout/view_file_icon.xml
+++ b/vector/src/main/res/layout/view_file_icon.xml
@@ -2,22 +2,22 @@
 
+    android:layout_width="32dp"
+    android:layout_height="32dp"
+    tools:parentTag="android.widget.FrameLayout">
 
     
+        tools:progress="40" />
 
     
+                app:layout_constraintWidth_max="@dimen/chat_bubble_fixed_size" />
 
             
Date: Mon, 17 Jan 2022 14:26:39 +0200
Subject: [PATCH 104/581] Remove duplicate RetryTestRule

---
 .../sdk/session/room/threads/RetryTestRule.kt | 58 -------------------
 1 file changed, 58 deletions(-)
 delete mode 100644 matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/RetryTestRule.kt

diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/RetryTestRule.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/RetryTestRule.kt
deleted file mode 100644
index c06a18aeb3f..00000000000
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/RetryTestRule.kt
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * 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.session.room.threads
-
-import android.util.Log
-import org.junit.rules.TestRule
-import org.junit.runner.Description
-import org.junit.runners.model.Statement
-
-/**
- * Retry test rule used to retry test that failed.
- * Retry failed test 3 times
- */
-class RetryTestRule(val retryCount: Int = 3) : TestRule {
-
-    private val TAG = RetryTestRule::class.java.simpleName
-
-    override fun apply(base: Statement, description: Description): Statement {
-        return statement(base, description)
-    }
-
-    private fun statement(base: Statement, description: Description): Statement {
-        return object : Statement() {
-            @Throws(Throwable::class)
-            override fun evaluate() {
-                var caughtThrowable: Throwable? = null
-
-                // implement retry logic here
-                for (i in 0 until retryCount) {
-                    try {
-                        base.evaluate()
-                        return
-                    } catch (t: Throwable) {
-                        caughtThrowable = t
-                        Log.e(TAG, description.displayName + ": run " + (i + 1) + " failed")
-                    }
-                }
-
-                Log.e(TAG, description.displayName + ": giving up after " + retryCount + " failures")
-                throw caughtThrowable!!
-            }
-        }
-    }
-}

From b343739a71c7968ffe1d0e2087975975a954b981 Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Mon, 17 Jan 2022 14:27:17 +0200
Subject: [PATCH 105/581] Enhance decrypted thread summary to return poll
 questions

---
 .../android/sdk/api/session/events/model/Event.kt   | 13 ++++++++++++-
 1 file changed, 12 insertions(+), 1 deletion(-)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
index 895280732e6..147eff6ab0e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
@@ -25,6 +25,7 @@ import org.matrix.android.sdk.api.session.crypto.MXCryptoError
 import org.matrix.android.sdk.api.session.room.model.Membership
 import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageContent
+import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageStickerContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageType
 import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
@@ -205,7 +206,7 @@ data class Event(
             isAudioMessage()       -> "sent an audio file."
             isImageMessage()       -> "sent an image."
             isVideoMessage()       -> "sent a video."
-            isPoll()               -> "created a poll."
+            isPoll()               -> getPollQuestion() ?: "created a poll."
             else                   -> text
         }
     }
@@ -357,6 +358,12 @@ fun Event.getRelationContent(): RelationDefaultContent? {
     }
 }
 
+/**
+ * Returns the poll question or null otherwise
+ */
+fun Event.getPollQuestion(): String? =
+        getPollContent()?.pollCreationInfo?.question?.question
+
 /**
  * Returns the relation content for a specific type or null otherwise
  */
@@ -381,3 +388,7 @@ fun Event.getPresenceContent(): PresenceContent? {
 
 fun Event.isInvitation(): Boolean = type == EventType.STATE_ROOM_MEMBER &&
         content?.toModel()?.membership == Membership.INVITE
+
+fun Event.getPollContent(): MessagePollContent? {
+    return content.toModel()
+}

From f6067977fea62e011edde4856743ad719bb1610d Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Mon, 17 Jan 2022 14:27:30 +0200
Subject: [PATCH 106/581] Refactor ThreadMessagingTest

---
 .../session/room/threads/ThreadMessagingTest.kt   | 15 ++++++++-------
 1 file changed, 8 insertions(+), 7 deletions(-)

diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/ThreadMessagingTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/ThreadMessagingTest.kt
index 62887beba76..6aa4f4cc32b 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/ThreadMessagingTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/ThreadMessagingTest.kt
@@ -40,15 +40,10 @@ import java.util.concurrent.CountDownLatch
 @FixMethodOrder(MethodSorters.JVM)
 class ThreadMessagingTest : InstrumentedTest {
 
-    private val commonTestHelper = CommonTestHelper(context())
-    private val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
-
-//    @Rule
-//    @JvmField
-//    val mRetryTestRule = RetryTestRule()
-
     @Test
     fun reply_in_thread_should_create_a_thread() {
+        val commonTestHelper = CommonTestHelper(context())
+        val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
         val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceInARoom(false)
 
         val aliceSession = cryptoTestData.firstSession
@@ -105,6 +100,8 @@ class ThreadMessagingTest : InstrumentedTest {
 
     @Test
     fun reply_in_thread_should_create_a_thread_from_other_user() {
+        val commonTestHelper = CommonTestHelper(context())
+        val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
         val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(false)
 
         val aliceSession = cryptoTestData.firstSession
@@ -176,6 +173,8 @@ class ThreadMessagingTest : InstrumentedTest {
 
     @Test
     fun reply_in_thread_to_timeline_message_multiple_times() {
+        val commonTestHelper = CommonTestHelper(context())
+        val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
         val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceInARoom(false)
 
         val aliceSession = cryptoTestData.firstSession
@@ -237,6 +236,8 @@ class ThreadMessagingTest : InstrumentedTest {
 
     @Test
     fun thread_summary_advanced_validation_after_multiple_messages_in_multiple_threads() {
+        val commonTestHelper = CommonTestHelper(context())
+        val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
         val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(false)
 
         val aliceSession = cryptoTestData.firstSession

From 81a1dfd66d4da487b2880ea0de8895095d2747bd Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Mon, 17 Jan 2022 17:28:40 +0200
Subject: [PATCH 107/581] PR Remarks

---
 .../main/res/anim/animation_slide_in_left.xml |   0
 .../res/anim/animation_slide_in_right.xml     |   0
 .../res/anim/animation_slide_out_left.xml     |   0
 .../res/anim/animation_slide_out_right.xml    |   0
 .../sdk/api/session/events/model/Event.kt     |   4 +-
 .../api/session/events/model/RelationType.kt  |   2 +-
 .../session/room/timeline/TimelineService.kt  |   4 +-
 .../threads/ThreadNotificationState.kt        |   2 +-
 .../session/room/timeline/LoadMoreResult.kt   |   1 -
 .../handler/room/ThreadsAwarenessHandler.kt   |   2 +-
 .../im/vector/app/core/di/FragmentModule.kt   |   4 +-
 .../app/core/di/MavericksViewModelModule.kt   |   6 +-
 .../im/vector/app/core/extensions/TextView.kt |   2 +-
 .../detail/JoinReplacementRoomBottomSheet.kt  |   2 +-
 .../room/detail/StartCallActionsHandler.kt    |  16 +-
 .../home/room/detail/TimelineFragment.kt      | 180 +++++++++---------
 ...etailViewModel.kt => TimelineViewModel.kt} |   8 +-
 .../composer/MessageComposerViewModel.kt      |  14 +-
 .../timeline/action/EventSharedAction.kt      |   2 -
 .../action/MessageActionsViewModel.kt         |   7 +-
 .../detail/views/RoomDetailLazyLoadedViews.kt |   6 +-
 .../detail/widget/RoomWidgetsBottomSheet.kt   |  10 +-
 .../{ThreadListModel.kt => ThreadListItem.kt} |   6 +-
 .../list/viewmodel/ThreadListController.kt    |   4 +-
 .../threads/list/views/ThreadListFragment.kt  |   7 +
 .../main/res/layout/fragment_thread_list.xml  |   4 +-
 ..._room_detail.xml => fragment_timeline.xml} |   0
 .../{item_thread_list.xml => item_thread.xml} |   0
 .../view_room_detail_thread_toolbar.xml       |   2 +-
 .../res/layout/view_thread_room_summary.xml   |   2 +-
 vector/src/main/res/menu/menu_timeline.xml    |   2 +-
 vector/src/main/res/values/strings.xml        |   4 +-
 32 files changed, 152 insertions(+), 151 deletions(-)
 rename {vector => library/ui-styles}/src/main/res/anim/animation_slide_in_left.xml (100%)
 rename {vector => library/ui-styles}/src/main/res/anim/animation_slide_in_right.xml (100%)
 rename {vector => library/ui-styles}/src/main/res/anim/animation_slide_out_left.xml (100%)
 rename {vector => library/ui-styles}/src/main/res/anim/animation_slide_out_right.xml (100%)
 rename vector/src/main/java/im/vector/app/features/home/room/detail/{RoomDetailViewModel.kt => TimelineViewModel.kt} (99%)
 rename vector/src/main/java/im/vector/app/features/home/room/threads/list/model/{ThreadListModel.kt => ThreadListItem.kt} (96%)
 rename vector/src/main/res/layout/{fragment_room_detail.xml => fragment_timeline.xml} (100%)
 rename vector/src/main/res/layout/{item_thread_list.xml => item_thread.xml} (100%)

diff --git a/vector/src/main/res/anim/animation_slide_in_left.xml b/library/ui-styles/src/main/res/anim/animation_slide_in_left.xml
similarity index 100%
rename from vector/src/main/res/anim/animation_slide_in_left.xml
rename to library/ui-styles/src/main/res/anim/animation_slide_in_left.xml
diff --git a/vector/src/main/res/anim/animation_slide_in_right.xml b/library/ui-styles/src/main/res/anim/animation_slide_in_right.xml
similarity index 100%
rename from vector/src/main/res/anim/animation_slide_in_right.xml
rename to library/ui-styles/src/main/res/anim/animation_slide_in_right.xml
diff --git a/vector/src/main/res/anim/animation_slide_out_left.xml b/library/ui-styles/src/main/res/anim/animation_slide_out_left.xml
similarity index 100%
rename from vector/src/main/res/anim/animation_slide_out_left.xml
rename to library/ui-styles/src/main/res/anim/animation_slide_out_left.xml
diff --git a/vector/src/main/res/anim/animation_slide_out_right.xml b/library/ui-styles/src/main/res/anim/animation_slide_out_right.xml
similarity index 100%
rename from vector/src/main/res/anim/animation_slide_out_right.xml
rename to library/ui-styles/src/main/res/anim/animation_slide_out_right.xml
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
index 147eff6ab0e..047aefe88d3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
@@ -222,8 +222,8 @@ data class Event(
     private fun getDecryptedValue(key: String = "body"): String? {
         return if (isEncrypted()) {
             @Suppress("UNCHECKED_CAST")
-            val content = mxDecryptionResult?.payload?.get("content") as? JsonDict
-            content?.get(key) as? String
+            val decryptedContent = mxDecryptionResult?.payload?.get("content") as? JsonDict
+            decryptedContent?.get(key) as? String
         } else {
             content?.get(key) as? String
         }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/RelationType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/RelationType.kt
index 18bb946462a..fb26264ad77 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/RelationType.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/RelationType.kt
@@ -28,7 +28,7 @@ object RelationType {
     /** Lets you define an event which references an existing event.*/
     const val REFERENCE = "m.reference"
 
-    /** Lets you define an event which is a reply to an existing event.*/
+    /** Lets you define an event which is a thread reply to an existing event.*/
     const val THREAD = "m.thread"
     const val IO_THREAD = "io.element.thread"
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineService.kt
index bf483539186..aefda755f19 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineService.kt
@@ -57,13 +57,13 @@ interface TimelineService {
     fun getAttachmentMessages(): List
 
     /**
-     * Get a live list of all the thread for the specified roomId
+     * Get a live list of all the TimelineEvents which have thread replies for the specified roomId
      * @return the [LiveData] of [TimelineEvent]
      */
     fun getAllThreadsLive(): LiveData>
 
     /**
-     * Get a list of all the thread for the specified roomId
+     * Get a list of all the TimelineEvents which have thread replies for the specified roomId
      * @return the [LiveData] of [TimelineEvent]
      */
     fun getAllThreads(): List
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadNotificationState.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadNotificationState.kt
index 58cc3a0706d..8566d68aa52 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadNotificationState.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/threads/ThreadNotificationState.kt
@@ -27,7 +27,7 @@ enum class ThreadNotificationState {
     // There is at least one new message
     NEW_MESSAGE,
 
-    // The is at least one new message that should bi highlighted
+    // The is at least one new message that should be highlighted
     // ex. "Hello @aris.kotsomitopoulos"
     NEW_HIGHLIGHTED_MESSAGE;
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadMoreResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadMoreResult.kt
index 2949e35bd3a..c419e8325ee 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadMoreResult.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadMoreResult.kt
@@ -20,5 +20,4 @@ internal enum class LoadMoreResult {
     REACHED_END,
     SUCCESS,
     FAILURE
-    // evenIDS
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt
index 24854b601fc..ee606d1fac4 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt
@@ -223,7 +223,7 @@ internal class ThreadsAwarenessHandler @Inject constructor(
                 body)
 
         val messageTextContent = MessageTextContent(
-                msgType = "m.text",
+                msgType = MessageType.MSGTYPE_TEXT,
                 format = MessageFormat.FORMAT_MATRIX_HTML,
                 body = body,
                 formattedBody = replyFormatted
diff --git a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt
index 83ebefb6587..b954634129e 100644
--- a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt
+++ b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt
@@ -203,7 +203,7 @@ interface FragmentModule {
     @Binds
     @IntoMap
     @FragmentKey(TimelineFragment::class)
-    fun bindRoomDetailFragment(fragment: TimelineFragment): Fragment
+    fun bindTimelineFragment(fragment: TimelineFragment): Fragment
 
     @Binds
     @IntoMap
@@ -933,7 +933,7 @@ interface FragmentModule {
     @Binds
     @IntoMap
     @FragmentKey(ThreadListFragment::class)
-    fun bindRoomThreadDetailFragment(fragment: ThreadListFragment): Fragment
+    fun bindThreadListFragment(fragment: ThreadListFragment): Fragment
 
     @Binds
     @IntoMap
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 cc31a7dca68..98b1a5d048a 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
@@ -44,7 +44,7 @@ import im.vector.app.features.home.UnknownDeviceDetectorSharedViewModel
 import im.vector.app.features.home.UnreadMessagesSharedViewModel
 import im.vector.app.features.home.UserColorAccountDataViewModel
 import im.vector.app.features.home.room.breadcrumbs.BreadcrumbsViewModel
-import im.vector.app.features.home.room.detail.RoomDetailViewModel
+import im.vector.app.features.home.room.detail.TimelineViewModel
 import im.vector.app.features.home.room.detail.composer.MessageComposerViewModel
 import im.vector.app.features.home.room.detail.search.SearchViewModel
 import im.vector.app.features.home.room.detail.timeline.action.MessageActionsViewModel
@@ -536,8 +536,8 @@ interface MavericksViewModelModule {
 
     @Binds
     @IntoMap
-    @MavericksViewModelKey(RoomDetailViewModel::class)
-    fun roomDetailViewModelFactory(factory: RoomDetailViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
+    @MavericksViewModelKey(TimelineViewModel::class)
+    fun roomDetailViewModelFactory(factory: TimelineViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
 
     @Binds
     @IntoMap
diff --git a/vector/src/main/java/im/vector/app/core/extensions/TextView.kt b/vector/src/main/java/im/vector/app/core/extensions/TextView.kt
index c0911aec8b5..8e7b6a4a80d 100644
--- a/vector/src/main/java/im/vector/app/core/extensions/TextView.kt
+++ b/vector/src/main/java/im/vector/app/core/extensions/TextView.kt
@@ -126,7 +126,7 @@ fun TextView.setLeftDrawable(@DrawableRes iconRes: Int, @AttrRes tintColor: Int?
 }
 
 fun TextView.clearDrawables() {
-    this.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0)
+    setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0)
 }
 
 /**
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/JoinReplacementRoomBottomSheet.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/JoinReplacementRoomBottomSheet.kt
index ba559677c9f..99843084ec1 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/JoinReplacementRoomBottomSheet.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/JoinReplacementRoomBottomSheet.kt
@@ -44,7 +44,7 @@ class JoinReplacementRoomBottomSheet :
     @Inject
     lateinit var errorFormatter: ErrorFormatter
 
-    private val viewModel: RoomDetailViewModel by parentFragmentViewModel()
+    private val viewModel: TimelineViewModel by parentFragmentViewModel()
 
     override val showExpanded: Boolean
         get() = true
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/StartCallActionsHandler.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/StartCallActionsHandler.kt
index 6b5ed3ba663..193dc42f339 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/StartCallActionsHandler.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/StartCallActionsHandler.kt
@@ -32,7 +32,7 @@ class StartCallActionsHandler(
         private val fragment: Fragment,
         private val callManager: WebRtcCallManager,
         private val vectorPreferences: VectorPreferences,
-        private val roomDetailViewModel: RoomDetailViewModel,
+        private val timelineViewModel: TimelineViewModel,
         private val startCallActivityResultLauncher: ActivityResultLauncher>,
         private val showDialogWithMessage: (String) -> Unit,
         private val onTapToReturnToCall: () -> Unit) {
@@ -45,7 +45,7 @@ class StartCallActionsHandler(
         handleCallRequest(false)
     }
 
-    private fun handleCallRequest(isVideoCall: Boolean) = withState(roomDetailViewModel) { state ->
+    private fun handleCallRequest(isVideoCall: Boolean) = withState(timelineViewModel) { state ->
         val roomSummary = state.asyncRoomSummary.invoke() ?: return@withState
         when (roomSummary.joinedMembersCount) {
             1    -> {
@@ -95,7 +95,7 @@ class StartCallActionsHandler(
                                 .setMessage(R.string.audio_video_meeting_description)
                                 .setPositiveButton(fragment.getString(R.string.create)) { _, _ ->
                                     // create the widget, then navigate to it..
-                                    roomDetailViewModel.handle(RoomDetailAction.AddJitsiWidget(isVideoCall))
+                                    timelineViewModel.handle(RoomDetailAction.AddJitsiWidget(isVideoCall))
                                 }
                                 .setNegativeButton(fragment.getString(R.string.action_cancel), null)
                                 .show()
@@ -121,22 +121,22 @@ class StartCallActionsHandler(
 
     private fun safeStartCall2(isVideoCall: Boolean) {
         val startCallAction = RoomDetailAction.StartCall(isVideoCall)
-        roomDetailViewModel.pendingAction = startCallAction
+        timelineViewModel.pendingAction = startCallAction
         if (isVideoCall) {
             if (checkPermissions(PERMISSIONS_FOR_VIDEO_IP_CALL,
                             fragment.requireActivity(),
                             startCallActivityResultLauncher,
                             R.string.permissions_rationale_msg_camera_and_audio)) {
-                roomDetailViewModel.pendingAction = null
-                roomDetailViewModel.handle(startCallAction)
+                timelineViewModel.pendingAction = null
+                timelineViewModel.handle(startCallAction)
             }
         } else {
             if (checkPermissions(PERMISSIONS_FOR_AUDIO_IP_CALL,
                             fragment.requireActivity(),
                             startCallActivityResultLauncher,
                             R.string.permissions_rationale_msg_record_audio)) {
-                roomDetailViewModel.pendingAction = null
-                roomDetailViewModel.handle(startCallAction)
+                timelineViewModel.pendingAction = null
+                timelineViewModel.handle(startCallAction)
             }
         }
     }
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 2c4ac2d9d77..1401a045fc8 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
@@ -117,7 +117,7 @@ import im.vector.app.core.utils.shareText
 import im.vector.app.core.utils.startInstallFromSourceIntent
 import im.vector.app.core.utils.toast
 import im.vector.app.databinding.DialogReportContentBinding
-import im.vector.app.databinding.FragmentRoomDetailBinding
+import im.vector.app.databinding.FragmentTimelineBinding
 import im.vector.app.features.attachments.AttachmentTypeSelectorView
 import im.vector.app.features.attachments.AttachmentsHelper
 import im.vector.app.features.attachments.ContactAttachment
@@ -255,7 +255,7 @@ class TimelineFragment @Inject constructor(
         private val voiceMessagePlaybackTracker: VoiceMessagePlaybackTracker,
         private val clock: Clock
 ) :
-        VectorBaseFragment(),
+        VectorBaseFragment(),
         TimelineEventController.Callback,
         VectorInviteView.Callback,
         AttachmentTypeSelectorView.Callback,
@@ -295,15 +295,15 @@ class TimelineFragment @Inject constructor(
         autoCompleterFactory.create(timelineArgs.roomId, isThreadTimeLine())
     }
 
-    private val roomDetailViewModel: RoomDetailViewModel by fragmentViewModel()
+    private val timelineViewModel: TimelineViewModel by fragmentViewModel()
     private val messageComposerViewModel: MessageComposerViewModel by fragmentViewModel()
     private val debouncer = Debouncer(createUIHandler())
 
     private lateinit var scrollOnNewMessageCallback: ScrollOnNewMessageCallback
     private lateinit var scrollOnHighlightedEventCallback: ScrollOnHighlightedEventCallback
 
-    override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRoomDetailBinding {
-        return FragmentRoomDetailBinding.inflate(inflater, container, false)
+    override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentTimelineBinding {
+        return FragmentTimelineBinding.inflate(inflater, container, false)
     }
 
     override fun getMenuRes() = R.menu.menu_timeline
@@ -335,7 +335,7 @@ class TimelineFragment @Inject constructor(
         super.onCreate(savedInstanceState)
         setFragmentResultListener(MigrateRoomBottomSheet.REQUEST_KEY) { _, bundle ->
             bundle.getString(MigrateRoomBottomSheet.BUNDLE_KEY_REPLACEMENT_ROOM)?.let { replacementRoomId ->
-                roomDetailViewModel.handle(RoomDetailAction.RoomUpgradeSuccess(replacementRoomId))
+                timelineViewModel.handle(RoomDetailAction.RoomUpgradeSuccess(replacementRoomId))
             }
         }
     }
@@ -351,7 +351,7 @@ class TimelineFragment @Inject constructor(
                 roomId = timelineArgs.roomId,
                 fragment = this,
                 vectorPreferences = vectorPreferences,
-                roomDetailViewModel = roomDetailViewModel,
+                timelineViewModel = timelineViewModel,
                 callManager = callManager,
                 startCallActivityResultLauncher = startCallActivityResultLauncher,
                 showDialogWithMessage = ::showDialogWithMessage,
@@ -388,7 +388,7 @@ class TimelineFragment @Inject constructor(
                     invalidateOptionsMenu()
                 }
 
-        roomDetailViewModel.onEach(RoomDetailViewState::canShowJumpToReadMarker, RoomDetailViewState::unreadState) { _, _ ->
+        timelineViewModel.onEach(RoomDetailViewState::canShowJumpToReadMarker, RoomDetailViewState::unreadState) { _, _ ->
             updateJumpToReadMarkerViewVisibility()
         }
 
@@ -405,7 +405,7 @@ class TimelineFragment @Inject constructor(
             }
         }
 
-        roomDetailViewModel.onEach(
+        timelineViewModel.onEach(
                 RoomDetailViewState::syncState,
                 RoomDetailViewState::incrementalSyncStatus,
                 RoomDetailViewState::pushCounter
@@ -435,7 +435,7 @@ class TimelineFragment @Inject constructor(
             }.exhaustive
         }
 
-        roomDetailViewModel.observeViewEvents {
+        timelineViewModel.observeViewEvents {
             when (it) {
                 is RoomDetailViewEvents.Failure                          -> showErrorInSnackbar(it.throwable)
                 is RoomDetailViewEvents.OnNewTimelineEvents              -> scrollOnNewMessageCallback.addNewTimelineEventIds(it.eventIds)
@@ -501,12 +501,12 @@ class TimelineFragment @Inject constructor(
 
     private fun setupRemoveJitsiWidgetView() {
         views.removeJitsiWidgetView.onCompleteSliding = {
-            withState(roomDetailViewModel) {
+            withState(timelineViewModel) {
                 val jitsiWidgetId = it.jitsiState.widgetId ?: return@withState
                 if (it.jitsiState.hasJoined) {
                     leaveJitsiConference()
                 }
-                roomDetailViewModel.handle(RoomDetailAction.RemoveWidget(jitsiWidgetId))
+                timelineViewModel.handle(RoomDetailAction.RemoveWidget(jitsiWidgetId))
             }
         }
     }
@@ -516,7 +516,7 @@ class TimelineFragment @Inject constructor(
     }
 
     private fun onBroadcastJitsiEvent(conferenceEvent: ConferenceEvent) {
-        roomDetailViewModel.handle(RoomDetailAction.UpdateJoinJitsiCallStatus(conferenceEvent))
+        timelineViewModel.handle(RoomDetailAction.UpdateJoinJitsiCallStatus(conferenceEvent))
     }
 
     private fun onCannotRecord() {
@@ -577,7 +577,7 @@ class TimelineFragment @Inject constructor(
 
     override fun onImageReady(uri: Uri?) {
         uri ?: return
-        roomDetailViewModel.handle(
+        timelineViewModel.handle(
                 RoomDetailAction.SetAvatarAction(
                         newAvatarUri = uri,
                         newAvatarFileName = getFilenameFromUri(requireContext(), uri) ?: UUID.randomUUID().toString()
@@ -616,7 +616,7 @@ class TimelineFragment @Inject constructor(
             ).apply {
                 directListener = { granted ->
                     if (granted) {
-                        roomDetailViewModel.handle(RoomDetailAction.EnsureNativeWidgetAllowed(
+                        timelineViewModel.handle(RoomDetailAction.EnsureNativeWidgetAllowed(
                                 widget = it.widget,
                                 userJustAccepted = true,
                                 grantedEvents = it.grantedEvents
@@ -696,13 +696,13 @@ class TimelineFragment @Inject constructor(
                         .setMessage(getString(R.string.event_status_delete_all_failed_dialog_message))
                         .setNegativeButton(R.string.no, null)
                         .setPositiveButton(R.string.yes) { _, _ ->
-                            roomDetailViewModel.handle(RoomDetailAction.RemoveAllFailedMessages)
+                            timelineViewModel.handle(RoomDetailAction.RemoveAllFailedMessages)
                         }
                         .show()
             }
 
             override fun onRetryClicked() {
-                roomDetailViewModel.handle(RoomDetailAction.ResendAll)
+                timelineViewModel.handle(RoomDetailAction.ResendAll)
             }
         }
     }
@@ -799,7 +799,7 @@ class TimelineFragment @Inject constructor(
         val safeContext = context ?: return
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
             if (!safeContext.packageManager.canRequestPackageInstalls()) {
-                roomDetailViewModel.pendingEvent = action
+                timelineViewModel.pendingEvent = action
                 startInstallFromSourceIntent(safeContext, installApkActivityResultLauncher)
             } else {
                 openFile(action)
@@ -811,7 +811,7 @@ class TimelineFragment @Inject constructor(
 
     private val installApkActivityResultLauncher = registerStartForActivityResult { activityResult ->
         if (activityResult.resultCode == Activity.RESULT_OK) {
-            roomDetailViewModel.pendingEvent?.let {
+            timelineViewModel.pendingEvent?.let {
                 if (it is RoomDetailViewEvents.OpenFile) {
                     openFile(it)
                 }
@@ -819,7 +819,7 @@ class TimelineFragment @Inject constructor(
         } else {
             // User cancelled
         }
-        roomDetailViewModel.pendingEvent = null
+        timelineViewModel.pendingEvent = null
     }
 
     private fun displayPromptForIntegrationManager() {
@@ -879,18 +879,18 @@ class TimelineFragment @Inject constructor(
     }
 
     override fun onDestroy() {
-        roomDetailViewModel.handle(RoomDetailAction.ExitTrackingUnreadMessagesState)
+        timelineViewModel.handle(RoomDetailAction.ExitTrackingUnreadMessagesState)
         super.onDestroy()
     }
 
     private fun setupJumpToBottomView() {
         views.jumpToBottomView.visibility = View.INVISIBLE
         views.jumpToBottomView.debouncedClicks {
-            roomDetailViewModel.handle(RoomDetailAction.ExitTrackingUnreadMessagesState)
+            timelineViewModel.handle(RoomDetailAction.ExitTrackingUnreadMessagesState)
             views.jumpToBottomView.visibility = View.INVISIBLE
-            if (!roomDetailViewModel.timeline.isLive) {
+            if (!timelineViewModel.timeline.isLive) {
                 scrollOnNewMessageCallback.forceScrollOnNextUpdate()
-                roomDetailViewModel.timeline.restartWithEventId(null)
+                timelineViewModel.timeline.restartWithEventId(null)
             } else {
                 layoutManager.scrollToPosition(0)
             }
@@ -909,7 +909,7 @@ class TimelineFragment @Inject constructor(
             onJumpToReadMarkerClicked()
         }
         views.jumpToReadMarkerView.setOnCloseIconClickListener {
-            roomDetailViewModel.handle(RoomDetailAction.MarkAllAsRead)
+            timelineViewModel.handle(RoomDetailAction.MarkAllAsRead)
         }
     }
 
@@ -952,11 +952,11 @@ class TimelineFragment @Inject constructor(
     private fun setupNotificationView() {
         views.notificationAreaView.delegate = object : NotificationAreaView.Delegate {
             override fun onTombstoneEventClicked() {
-                roomDetailViewModel.handle(RoomDetailAction.JoinAndOpenReplacementRoom)
+                timelineViewModel.handle(RoomDetailAction.JoinAndOpenReplacementRoom)
             }
 
             override fun onMisconfiguredEncryptionClicked() {
-                roomDetailViewModel.handle(RoomDetailAction.OnClickMisconfiguredEncryption)
+                timelineViewModel.handle(RoomDetailAction.OnClickMisconfiguredEncryption)
             }
         }
     }
@@ -971,7 +971,7 @@ class TimelineFragment @Inject constructor(
         }
         val joinConfItem = menu.findItem(R.id.join_conference)
         (joinConfItem.actionView as? JoinConferenceView)?.onJoinClicked = {
-            roomDetailViewModel.handle(RoomDetailAction.JoinJitsiCall)
+            timelineViewModel.handle(RoomDetailAction.JoinJitsiCall)
         }
 
         // Custom thread notification menu item
@@ -984,10 +984,10 @@ class TimelineFragment @Inject constructor(
 
     override fun onPrepareOptionsMenu(menu: Menu) {
         menu.forEach {
-            it.isVisible = roomDetailViewModel.isMenuItemVisible(it.itemId)
+            it.isVisible = timelineViewModel.isMenuItemVisible(it.itemId)
         }
 
-        withState(roomDetailViewModel) { state ->
+        withState(timelineViewModel) { state ->
             // Set the visual state of the call buttons (voice/video) to enabled/disabled according to user permissions
             val hasCallInRoom = callManager.getCallsByRoomId(state.roomId).isNotEmpty() || state.jitsiState.hasJoined
             val callButtonsEnabled = !hasCallInRoom && when (state.asyncRoomSummary.invoke()?.joinedMembersCount) {
@@ -1035,7 +1035,7 @@ class TimelineFragment @Inject constructor(
                 true
             }
             R.id.open_matrix_apps                  -> {
-                roomDetailViewModel.handle(RoomDetailAction.ManageIntegrations)
+                timelineViewModel.handle(RoomDetailAction.ManageIntegrations)
                 true
             }
             R.id.voice_call                        -> {
@@ -1123,8 +1123,8 @@ class TimelineFragment @Inject constructor(
             navigator.openSearch(
                     context = requireContext(),
                     roomId = timelineArgs.roomId,
-                    roomDisplayName = roomDetailViewModel.getRoomSummary()?.displayName,
-                    roomAvatarUrl = roomDetailViewModel.getRoomSummary()?.avatarUrl
+                    roomDisplayName = timelineViewModel.getRoomSummary()?.displayName,
+                    roomAvatarUrl = timelineViewModel.getRoomSummary()?.avatarUrl
             )
         } else {
             showDialogWithMessage(getString(R.string.search_is_not_supported_in_e2e_room))
@@ -1219,11 +1219,11 @@ class TimelineFragment @Inject constructor(
     private fun handlePendingAction(roomDetailPendingAction: RoomDetailPendingAction) {
         when (roomDetailPendingAction) {
             is RoomDetailPendingAction.JumpToReadReceipt ->
-                roomDetailViewModel.handle(RoomDetailAction.JumpToReadReceipt(roomDetailPendingAction.userId))
+                timelineViewModel.handle(RoomDetailAction.JumpToReadReceipt(roomDetailPendingAction.userId))
             is RoomDetailPendingAction.MentionUser       ->
                 insertUserDisplayNameInTextEditor(roomDetailPendingAction.userId)
             is RoomDetailPendingAction.OpenOrCreateDm    ->
-                roomDetailViewModel.handle(RoomDetailAction.OpenOrCreateDm(roomDetailPendingAction.userId))
+                timelineViewModel.handle(RoomDetailAction.OpenOrCreateDm(roomDetailPendingAction.userId))
             is RoomDetailPendingAction.OpenRoom          ->
                 handleOpenRoom(RoomDetailViewEvents.OpenRoom(roomDetailPendingAction.roomId, roomDetailPendingAction.closeCurrentRoom))
         }.exhaustive
@@ -1276,7 +1276,7 @@ class TimelineFragment @Inject constructor(
         if (activityResult.resultCode == Activity.RESULT_OK) {
             val sendData = AttachmentsPreviewActivity.getOutput(data)
             val keepOriginalSize = AttachmentsPreviewActivity.getKeepOriginalSize(data)
-            roomDetailViewModel.handle(RoomDetailAction.SendMedia(sendData, !keepOriginalSize))
+            timelineViewModel.handle(RoomDetailAction.SendMedia(sendData, !keepOriginalSize))
         }
     }
 
@@ -1285,7 +1285,7 @@ class TimelineFragment @Inject constructor(
             val eventId = EmojiReactionPickerActivity.getOutputEventId(activityResult.data)
             val reaction = EmojiReactionPickerActivity.getOutputReaction(activityResult.data)
             if (eventId != null && reaction != null) {
-                roomDetailViewModel.handle(RoomDetailAction.SendReaction(eventId, reaction))
+                timelineViewModel.handle(RoomDetailAction.SendReaction(eventId, reaction))
             }
         }
     }
@@ -1295,16 +1295,16 @@ class TimelineFragment @Inject constructor(
         if (activityResult.resultCode == Activity.RESULT_OK) {
             WidgetActivity.getOutput(data).toModel()
                     ?.let { content ->
-                        roomDetailViewModel.handle(RoomDetailAction.SendSticker(content))
+                        timelineViewModel.handle(RoomDetailAction.SendSticker(content))
                     }
         }
     }
 
     private val startCallActivityResultLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
         if (allGranted) {
-            (roomDetailViewModel.pendingAction as? RoomDetailAction.StartCall)?.let {
-                roomDetailViewModel.pendingAction = null
-                roomDetailViewModel.handle(it)
+            (timelineViewModel.pendingAction as? RoomDetailAction.StartCall)?.let {
+                timelineViewModel.pendingAction = null
+                timelineViewModel.handle(it)
             }
         } else {
             if (deniedPermanently) {
@@ -1318,7 +1318,7 @@ class TimelineFragment @Inject constructor(
 
     private fun setupRecyclerView() {
         timelineEventController.callback = this
-        timelineEventController.timeline = roomDetailViewModel.timeline
+        timelineEventController.timeline = timelineViewModel.timeline
 
         views.timelineRecyclerView.trackItemsVisibilityChange()
         layoutManager = object : LinearLayoutManager(context, RecyclerView.VERTICAL, true) {
@@ -1388,7 +1388,7 @@ class TimelineFragment @Inject constructor(
     private fun updateJumpToReadMarkerViewVisibility() {
         if (isThreadTimeLine()) return
         viewLifecycleOwner.lifecycleScope.launchWhenResumed {
-            val state = roomDetailViewModel.awaitState()
+            val state = timelineViewModel.awaitState()
             val showJumpToUnreadBanner = when (state.unreadState) {
                 UnreadState.Unknown,
                 UnreadState.HasNoUnread            -> false
@@ -1500,7 +1500,7 @@ class TimelineFragment @Inject constructor(
 
         views.composerLayout.views.composerEditText.focusChanges()
                 .onEach {
-                    roomDetailViewModel.handle(RoomDetailAction.ComposerFocusChange(it))
+                    timelineViewModel.handle(RoomDetailAction.ComposerFocusChange(it))
                 }
                 .launchIn(viewLifecycleOwner.lifecycleScope)
     }
@@ -1514,7 +1514,7 @@ class TimelineFragment @Inject constructor(
         return isHandled
     }
 
-    override fun invalidate() = withState(roomDetailViewModel, messageComposerViewModel) { mainState, messageComposerState ->
+    override fun invalidate() = withState(timelineViewModel, messageComposerViewModel) { mainState, messageComposerState ->
         invalidateOptionsMenu()
         val summary = mainState.asyncRoomSummary()
         renderToolbar(summary, mainState.formattedTypingUsers)
@@ -1568,7 +1568,7 @@ class TimelineFragment @Inject constructor(
         }
     }
 
-    private fun FragmentRoomDetailBinding.hideComposerViews() {
+    private fun FragmentTimelineBinding.hideComposerViews() {
         composerLayout.isVisible = false
         voiceMessageRecorderView.isVisible = false
     }
@@ -1679,7 +1679,7 @@ class TimelineFragment @Inject constructor(
                 .setView(layout)
                 .setPositiveButton(R.string.report_content_custom_submit) { _, _ ->
                     val reason = views.dialogReportContentInput.text.toString()
-                    roomDetailViewModel.handle(RoomDetailAction.ReportContent(action.eventId, action.senderId, reason))
+                    timelineViewModel.handle(RoomDetailAction.ReportContent(action.eventId, action.senderId, reason))
                 }
                 .setNegativeButton(R.string.action_cancel, null)
                 .show()
@@ -1695,7 +1695,7 @@ class TimelineFragment @Inject constructor(
                         reasonHintRes = R.string.delete_event_dialog_reason_hint,
                         titleRes = action.dialogTitleRes
                 ) { reason ->
-                    roomDetailViewModel.handle(RoomDetailAction.RedactAction(action.eventId, reason))
+                    timelineViewModel.handle(RoomDetailAction.RedactAction(action.eventId, reason))
                 }
     }
 
@@ -1717,7 +1717,7 @@ class TimelineFragment @Inject constructor(
                                 .setMessage(R.string.content_reported_as_spam_content)
                                 .setPositiveButton(R.string.ok, null)
                                 .setNegativeButton(R.string.block_user) { _, _ ->
-                                    roomDetailViewModel.handle(RoomDetailAction.IgnoreUser(data.senderId))
+                                    timelineViewModel.handle(RoomDetailAction.IgnoreUser(data.senderId))
                                 }
                                 .show()
                     }
@@ -1727,7 +1727,7 @@ class TimelineFragment @Inject constructor(
                                 .setMessage(R.string.content_reported_as_inappropriate_content)
                                 .setPositiveButton(R.string.ok, null)
                                 .setNegativeButton(R.string.block_user) { _, _ ->
-                                    roomDetailViewModel.handle(RoomDetailAction.IgnoreUser(data.senderId))
+                                    timelineViewModel.handle(RoomDetailAction.IgnoreUser(data.senderId))
                                 }
                                 .show()
                     }
@@ -1737,7 +1737,7 @@ class TimelineFragment @Inject constructor(
                                 .setMessage(R.string.content_reported_content)
                                 .setPositiveButton(R.string.ok, null)
                                 .setNegativeButton(R.string.block_user) { _, _ ->
-                                    roomDetailViewModel.handle(RoomDetailAction.IgnoreUser(data.senderId))
+                                    timelineViewModel.handle(RoomDetailAction.IgnoreUser(data.senderId))
                                 }
                                 .show()
                     }
@@ -1791,7 +1791,7 @@ class TimelineFragment @Inject constructor(
                                     true
                                 } else {
                                     // Highlight and scroll to this event
-                                    roomDetailViewModel.handle(RoomDetailAction.NavigateToEvent(eventId, true))
+                                    timelineViewModel.handle(RoomDetailAction.NavigateToEvent(eventId, true))
                                     true
                                 }
                             } else {
@@ -1800,7 +1800,7 @@ class TimelineFragment @Inject constructor(
                                     true
                                 } else if (rootThreadEventId == getRootThreadEventId() && eventId != null) {
                                     // we are in the same thread
-                                    roomDetailViewModel.handle(RoomDetailAction.NavigateToEvent(eventId, true))
+                                    timelineViewModel.handle(RoomDetailAction.NavigateToEvent(eventId, true))
                                     true
                                 } else {
                                     false
@@ -1847,11 +1847,11 @@ class TimelineFragment @Inject constructor(
     }
 
     override fun onEventVisible(event: TimelineEvent) {
-        roomDetailViewModel.handle(RoomDetailAction.TimelineEventTurnsVisible(event))
+        timelineViewModel.handle(RoomDetailAction.TimelineEventTurnsVisible(event))
     }
 
     override fun onEventInvisible(event: TimelineEvent) {
-        roomDetailViewModel.handle(RoomDetailAction.TimelineEventTurnsInvisible(event))
+        timelineViewModel.handle(RoomDetailAction.TimelineEventTurnsInvisible(event))
     }
 
     override fun onEncryptedMessageClicked(informationData: MessageInformationData, view: View) {
@@ -1896,7 +1896,7 @@ class TimelineFragment @Inject constructor(
 
     private fun cleanUpAfterPermissionNotGranted() {
         // Reset all pending data
-        roomDetailViewModel.pendingAction = null
+        timelineViewModel.pendingAction = null
         attachmentsHelper.pendingType = null
     }
 
@@ -1905,23 +1905,23 @@ class TimelineFragment @Inject constructor(
 //    }
 
     override fun onLoadMore(direction: Timeline.Direction) {
-        roomDetailViewModel.handle(RoomDetailAction.LoadMoreTimelineEvents(direction))
+        timelineViewModel.handle(RoomDetailAction.LoadMoreTimelineEvents(direction))
     }
 
     override fun onEventCellClicked(informationData: MessageInformationData, messageContent: Any?, view: View, isRootThreadEvent: Boolean) {
         when (messageContent) {
             is MessageVerificationRequestContent -> {
-                roomDetailViewModel.handle(RoomDetailAction.ResumeVerification(informationData.eventId, null))
+                timelineViewModel.handle(RoomDetailAction.ResumeVerification(informationData.eventId, null))
             }
             is MessageWithAttachmentContent      -> {
                 val action = RoomDetailAction.DownloadOrOpen(informationData.eventId, informationData.senderId, messageContent)
-                roomDetailViewModel.handle(action)
+                timelineViewModel.handle(action)
             }
             is EncryptedEventContent             -> {
                 if (isRootThreadEvent) {
                     onThreadSummaryClicked(informationData.eventId, isRootThreadEvent)
                 } else {
-                    roomDetailViewModel.handle(RoomDetailAction.TapOnFailedToDecrypt(informationData.eventId))
+                    timelineViewModel.handle(RoomDetailAction.TapOnFailedToDecrypt(informationData.eventId))
                 }
             }
             else                                 -> {
@@ -1944,14 +1944,14 @@ class TimelineFragment @Inject constructor(
 
     private fun handleCancelSend(action: EventSharedAction.Cancel) {
         if (action.force) {
-            roomDetailViewModel.handle(RoomDetailAction.CancelSend(action.eventId, true))
+            timelineViewModel.handle(RoomDetailAction.CancelSend(action.eventId, true))
         } else {
             MaterialAlertDialogBuilder(requireContext())
                     .setTitle(R.string.dialog_title_confirmation)
                     .setMessage(getString(R.string.event_status_cancel_sending_dialog_message))
                     .setNegativeButton(R.string.no, null)
                     .setPositiveButton(R.string.yes) { _, _ ->
-                        roomDetailViewModel.handle(RoomDetailAction.CancelSend(action.eventId, false))
+                        timelineViewModel.handle(RoomDetailAction.CancelSend(action.eventId, false))
                     }
                     .show()
         }
@@ -1979,10 +1979,10 @@ class TimelineFragment @Inject constructor(
     override fun onClickOnReactionPill(informationData: MessageInformationData, reaction: String, on: Boolean) {
         if (on) {
             // we should test the current real state of reaction on this event
-            roomDetailViewModel.handle(RoomDetailAction.SendReaction(informationData.eventId, reaction))
+            timelineViewModel.handle(RoomDetailAction.SendReaction(informationData.eventId, reaction))
         } else {
             // I need to redact a reaction
-            roomDetailViewModel.handle(RoomDetailAction.UndoReaction(informationData.eventId, reaction))
+            timelineViewModel.handle(RoomDetailAction.UndoReaction(informationData.eventId, reaction))
         }
     }
 
@@ -1997,11 +1997,11 @@ class TimelineFragment @Inject constructor(
     }
 
     override fun onTimelineItemAction(itemAction: RoomDetailAction) {
-        roomDetailViewModel.handle(itemAction)
+        timelineViewModel.handle(itemAction)
     }
 
     override fun getPreviewUrlRetriever(): PreviewUrlRetriever {
-        return roomDetailViewModel.previewUrlRetriever
+        return timelineViewModel.previewUrlRetriever
     }
 
     override fun onRoomCreateLinkClicked(url: String) {
@@ -2022,7 +2022,7 @@ class TimelineFragment @Inject constructor(
     }
 
     override fun onReadMarkerVisible() {
-        roomDetailViewModel.handle(RoomDetailAction.EnterTrackingUnreadMessagesState)
+        timelineViewModel.handle(RoomDetailAction.EnterTrackingUnreadMessagesState)
     }
 
     override fun onPreviewUrlClicked(url: String) {
@@ -2030,7 +2030,7 @@ class TimelineFragment @Inject constructor(
     }
 
     override fun onPreviewUrlCloseClicked(eventId: String, url: String) {
-        roomDetailViewModel.handle(RoomDetailAction.DoNotShowPreviewUrlFor(eventId, url))
+        timelineViewModel.handle(RoomDetailAction.DoNotShowPreviewUrlFor(eventId, url))
     }
 
     override fun onPreviewUrlImageClicked(sharedView: View?, mxcUrl: String?, title: String?) {
@@ -2140,7 +2140,7 @@ class TimelineFragment @Inject constructor(
             }
             is EventSharedAction.QuickReact                 -> {
                 // eventId,ClickedOn,Add
-                roomDetailViewModel.handle(RoomDetailAction.UpdateQuickReactAction(action.eventId, action.clickedOn, action.add))
+                timelineViewModel.handle(RoomDetailAction.UpdateQuickReactAction(action.eventId, action.clickedOn, action.add))
             }
             is EventSharedAction.Edit                       -> {
                 if (withState(messageComposerViewModel) { it.isVoiceMessageIdle }) {
@@ -2179,20 +2179,20 @@ class TimelineFragment @Inject constructor(
                 showSnackWithMessage(getString(R.string.copied_to_clipboard))
             }
             is EventSharedAction.Resend                     -> {
-                roomDetailViewModel.handle(RoomDetailAction.ResendMessage(action.eventId))
+                timelineViewModel.handle(RoomDetailAction.ResendMessage(action.eventId))
             }
             is EventSharedAction.Remove                     -> {
-                roomDetailViewModel.handle(RoomDetailAction.RemoveFailedEcho(action.eventId))
+                timelineViewModel.handle(RoomDetailAction.RemoveFailedEcho(action.eventId))
             }
             is EventSharedAction.Cancel                     -> {
                 handleCancelSend(action)
             }
             is EventSharedAction.ReportContentSpam          -> {
-                roomDetailViewModel.handle(RoomDetailAction.ReportContent(
+                timelineViewModel.handle(RoomDetailAction.ReportContent(
                         action.eventId, action.senderId, "This message is spam", spam = true))
             }
             is EventSharedAction.ReportContentInappropriate -> {
-                roomDetailViewModel.handle(RoomDetailAction.ReportContent(
+                timelineViewModel.handle(RoomDetailAction.ReportContent(
                         action.eventId, action.senderId, "This message is inappropriate", inappropriate = true))
             }
             is EventSharedAction.ReportContentCustom        -> {
@@ -2208,7 +2208,7 @@ class TimelineFragment @Inject constructor(
                 onUrlLongClicked(action.url)
             }
             is EventSharedAction.ReRequestKey               -> {
-                roomDetailViewModel.handle(RoomDetailAction.ReRequestKeys(action.eventId))
+                timelineViewModel.handle(RoomDetailAction.ReRequestKeys(action.eventId))
             }
             is EventSharedAction.UseKeyBackup               -> {
                 context?.let {
@@ -2227,7 +2227,7 @@ class TimelineFragment @Inject constructor(
                 .setMessage(R.string.end_poll_confirmation_description)
                 .setNegativeButton(R.string.action_cancel, null)
                 .setPositiveButton(R.string.end_poll_confirmation_approve_button) { _, _ ->
-                    roomDetailViewModel.handle(RoomDetailAction.EndPoll(eventId))
+                    timelineViewModel.handle(RoomDetailAction.EndPoll(eventId))
                 }
                 .show()
     }
@@ -2238,7 +2238,7 @@ class TimelineFragment @Inject constructor(
                 .setMessage(R.string.room_participants_action_ignore_prompt_msg)
                 .setNegativeButton(R.string.action_cancel, null)
                 .setPositiveButton(R.string.room_participants_action_ignore) { _, _ ->
-                    roomDetailViewModel.handle(RoomDetailAction.IgnoreUser(senderId))
+                    timelineViewModel.handle(RoomDetailAction.IgnoreUser(senderId))
                 }
                 .show()
     }
@@ -2258,7 +2258,7 @@ class TimelineFragment @Inject constructor(
             views.composerLayout.views.composerEditText.setText(Command.EMOTE.command + " ")
             views.composerLayout.views.composerEditText.setSelection(Command.EMOTE.length)
         } else {
-            val roomMember = roomDetailViewModel.getMember(userId)
+            val roomMember = timelineViewModel.getMember(userId)
             // TODO move logic outside of fragment
             (roomMember?.displayName ?: userId)
                     .let { sanitizeDisplayName(it) }
@@ -2320,9 +2320,9 @@ class TimelineFragment @Inject constructor(
         context?.let {
             val roomThreadDetailArgs = ThreadTimelineArgs(
                     roomId = timelineArgs.roomId,
-                    displayName = roomDetailViewModel.getRoomSummary()?.displayName,
-                    avatarUrl = roomDetailViewModel.getRoomSummary()?.avatarUrl,
-                    roomEncryptionTrustLevel = roomDetailViewModel.getRoomSummary()?.roomEncryptionTrustLevel,
+                    displayName = timelineViewModel.getRoomSummary()?.displayName,
+                    avatarUrl = timelineViewModel.getRoomSummary()?.avatarUrl,
+                    roomEncryptionTrustLevel = timelineViewModel.getRoomSummary()?.roomEncryptionTrustLevel,
                     rootThreadEventId = rootThreadEventId)
             navigator.openThread(it, roomThreadDetailArgs)
         }
@@ -2337,9 +2337,9 @@ class TimelineFragment @Inject constructor(
         context?.let {
             val roomThreadDetailArgs = ThreadTimelineArgs(
                     roomId = timelineArgs.roomId,
-                    displayName = roomDetailViewModel.getRoomSummary()?.displayName,
-                    roomEncryptionTrustLevel = roomDetailViewModel.getRoomSummary()?.roomEncryptionTrustLevel,
-                    avatarUrl = roomDetailViewModel.getRoomSummary()?.avatarUrl)
+                    displayName = timelineViewModel.getRoomSummary()?.displayName,
+                    roomEncryptionTrustLevel = timelineViewModel.getRoomSummary()?.roomEncryptionTrustLevel,
+                    avatarUrl = timelineViewModel.getRoomSummary()?.avatarUrl)
             navigator.openThreadList(it, roomThreadDetailArgs)
         }
     }
@@ -2348,20 +2348,20 @@ class TimelineFragment @Inject constructor(
 
     override fun onAcceptInvite() {
         notificationDrawerManager.updateEvents { it.clearMemberShipNotificationForRoom(timelineArgs.roomId) }
-        roomDetailViewModel.handle(RoomDetailAction.AcceptInvite)
+        timelineViewModel.handle(RoomDetailAction.AcceptInvite)
     }
 
     override fun onRejectInvite() {
         notificationDrawerManager.updateEvents { it.clearMemberShipNotificationForRoom(timelineArgs.roomId) }
-        roomDetailViewModel.handle(RoomDetailAction.RejectInvite)
+        timelineViewModel.handle(RoomDetailAction.RejectInvite)
     }
 
-    private fun onJumpToReadMarkerClicked() = withState(roomDetailViewModel) {
+    private fun onJumpToReadMarkerClicked() = withState(timelineViewModel) {
         if (it.unreadState is UnreadState.HasUnread) {
-            roomDetailViewModel.handle(RoomDetailAction.NavigateToEvent(it.unreadState.firstUnreadEventId, false))
+            timelineViewModel.handle(RoomDetailAction.NavigateToEvent(it.unreadState.firstUnreadEventId, false))
         }
         if (it.unreadState is UnreadState.ReadMarkerNotLoaded) {
-            roomDetailViewModel.handle(RoomDetailAction.NavigateToEvent(it.unreadState.readMarkerId, false))
+            timelineViewModel.handle(RoomDetailAction.NavigateToEvent(it.unreadState.readMarkerId, false))
         }
     }
 
@@ -2401,7 +2401,7 @@ class TimelineFragment @Inject constructor(
             AttachmentTypeSelectorView.Type.FILE    -> attachmentsHelper.selectFile(attachmentFileActivityResultLauncher)
             AttachmentTypeSelectorView.Type.GALLERY -> attachmentsHelper.selectGallery(attachmentMediaActivityResultLauncher)
             AttachmentTypeSelectorView.Type.CONTACT -> attachmentsHelper.selectContact(attachmentContactActivityResultLauncher)
-            AttachmentTypeSelectorView.Type.STICKER -> roomDetailViewModel.handle(RoomDetailAction.SelectStickerAttachment)
+            AttachmentTypeSelectorView.Type.STICKER -> timelineViewModel.handle(RoomDetailAction.SelectStickerAttachment)
             AttachmentTypeSelectorView.Type.POLL    -> navigator.openCreatePoll(requireContext(), timelineArgs.roomId)
         }.exhaustive
     }
@@ -2412,7 +2412,7 @@ class TimelineFragment @Inject constructor(
         val grouped = attachments.toGroupedContentAttachmentData()
         if (grouped.notPreviewables.isNotEmpty()) {
             // Send the not previewable attachments right now (?)
-            roomDetailViewModel.handle(RoomDetailAction.SendMedia(grouped.notPreviewables, false))
+            timelineViewModel.handle(RoomDetailAction.SendMedia(grouped.notPreviewables, false))
         }
         if (grouped.previewables.isNotEmpty()) {
             val intent = AttachmentsPreviewActivity.newIntent(requireContext(), AttachmentsPreviewArgs(grouped.previewables))
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt
similarity index 99%
rename from vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
rename to vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt
index ade037b4e41..6e51fc7aa00 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt
@@ -103,7 +103,7 @@ import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode
 import timber.log.Timber
 import java.util.concurrent.atomic.AtomicBoolean
 
-class RoomDetailViewModel @AssistedInject constructor(
+class TimelineViewModel @AssistedInject constructor(
         @Assisted private val initialState: RoomDetailViewState,
         private val vectorPreferences: VectorPreferences,
         private val vectorDataStore: VectorDataStore,
@@ -144,11 +144,11 @@ class RoomDetailViewModel @AssistedInject constructor(
     private var prepareToEncrypt: Async = Uninitialized
 
     @AssistedFactory
-    interface Factory : MavericksAssistedViewModelFactory {
-        override fun create(initialState: RoomDetailViewState): RoomDetailViewModel
+    interface Factory : MavericksAssistedViewModelFactory {
+        override fun create(initialState: RoomDetailViewState): TimelineViewModel
     }
 
-    companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() {
+    companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() {
         const val PAGINATION_COUNT = 50
     }
 
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 9d85ec0a077..62598551ded 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
@@ -215,7 +215,7 @@ class MessageComposerViewModel @AssistedInject constructor(
                             if (state.rootThreadEventId != null) {
                                 room.replyInThread(
                                         rootThreadEventId = state.rootThreadEventId,
-                                        replyInThreadText = action.text.toString(),
+                                        replyInThreadText = slashCommandResult.message,
                                         autoMarkdown = false)
                             } else {
                                 room.sendTextMessage(slashCommandResult.message, autoMarkdown = false)
@@ -270,7 +270,7 @@ class MessageComposerViewModel @AssistedInject constructor(
                         is ParsedCommand.SendEmote                         -> {
                             state.rootThreadEventId?.let {
                                 room.replyInThread(
-                                        rootThreadEventId = state.rootThreadEventId,
+                                        rootThreadEventId = it,
                                         replyInThreadText = slashCommandResult.message,
                                         msgType = MessageType.MSGTYPE_EMOTE,
                                         autoMarkdown = action.autoMarkdown)
@@ -282,7 +282,7 @@ class MessageComposerViewModel @AssistedInject constructor(
                             val message = slashCommandResult.message.toString()
                             state.rootThreadEventId?.let {
                                 room.replyInThread(
-                                        rootThreadEventId = state.rootThreadEventId,
+                                        rootThreadEventId = it,
                                         replyInThreadText = slashCommandResult.message,
                                         formattedText = rainbowGenerator.generate(message))
                             } ?: room.sendFormattedTextMessage(message, rainbowGenerator.generate(message))
@@ -293,7 +293,7 @@ class MessageComposerViewModel @AssistedInject constructor(
                             val message = slashCommandResult.message.toString()
                             state.rootThreadEventId?.let {
                                 room.replyInThread(
-                                        rootThreadEventId = state.rootThreadEventId,
+                                        rootThreadEventId = it,
                                         replyInThreadText = slashCommandResult.message,
                                         msgType = MessageType.MSGTYPE_EMOTE,
                                         formattedText = rainbowGenerator.generate(message))
@@ -307,7 +307,7 @@ class MessageComposerViewModel @AssistedInject constructor(
                             val formattedText = "${slashCommandResult.message}"
                             state.rootThreadEventId?.let {
                                 room.replyInThread(
-                                        rootThreadEventId = state.rootThreadEventId,
+                                        rootThreadEventId = it,
                                         replyInThreadText = text,
                                         formattedText = formattedText)
                             } ?: room.sendFormattedTextMessage(
@@ -479,9 +479,9 @@ class MessageComposerViewModel @AssistedInject constructor(
                 }
                 is SendMode.Reply   -> {
                     val timelineEvent = state.sendMode.timelineEvent
-                    state.rootThreadEventId?.let { rootThreadEventId ->
+                    state.rootThreadEventId?.let {
                         room.replyInThread(
-                                rootThreadEventId = rootThreadEventId,
+                                rootThreadEventId = it,
                                 replyInThreadText = action.text.toString(),
                                 autoMarkdown = action.autoMarkdown,
                                 eventReplied = timelineEvent)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/EventSharedAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/EventSharedAction.kt
index d611639463e..cc2f8a2ac53 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/EventSharedAction.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/EventSharedAction.kt
@@ -48,11 +48,9 @@ sealed class EventSharedAction(@StringRes val titleRes: Int,
     data class Reply(val eventId: String) :
             EventSharedAction(R.string.reply, R.drawable.ic_reply)
 
-    // TODO add translations
     data class ReplyInThread(val eventId: String) :
             EventSharedAction(R.string.reply_in_thread, R.drawable.ic_reply_in_thread)
 
-    // TODO add translations
     object ViewInRoom :
             EventSharedAction(R.string.view_in_room, R.drawable.ic_thread_view_in_room_menu_item)
 
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 fe615d8c01f..e979704d6a4 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
@@ -441,13 +441,12 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
     }
 
     /**
-     * Determine whether or not the Reply In Thread bottom sheet setting will be visible
+     * Determine whether or not the Reply In Thread bottom sheet action will be visible
      * to the user
      */
     private fun canReplyInThread(event: TimelineEvent,
                                  messageContent: MessageContent?,
                                  actionPermissions: ActionPermissions): Boolean {
-        // Only event of type EventType.MESSAGE are supported for the moment
         if (!BuildConfig.THREADING_ENABLED) return false
         if (initialState.isFromThreadTimeline) return false
         if (event.root.getClearType() != EventType.MESSAGE &&
@@ -468,13 +467,11 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
     }
 
     /**
-     * Determine whether or no the selected event is a root thread event from within
-     * a thread timeline
+     * Determine whether or not the view in room action will be available for the current event
      */
     private fun canViewInRoom(event: TimelineEvent,
                               messageContent: MessageContent?,
                               actionPermissions: ActionPermissions): Boolean {
-        // Only event of type EventType.MESSAGE are supported for the moment
         if (!BuildConfig.THREADING_ENABLED) return false
         if (!initialState.isFromThreadTimeline) return false
         if (event.root.getClearType() != EventType.MESSAGE &&
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/views/RoomDetailLazyLoadedViews.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/views/RoomDetailLazyLoadedViews.kt
index fafb49ad5c5..a7eb6ee78f6 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/views/RoomDetailLazyLoadedViews.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/views/RoomDetailLazyLoadedViews.kt
@@ -19,7 +19,7 @@ package im.vector.app.features.home.room.detail.views
 import android.view.View
 import android.view.ViewStub
 import im.vector.app.core.ui.views.FailedMessagesWarningView
-import im.vector.app.databinding.FragmentRoomDetailBinding
+import im.vector.app.databinding.FragmentTimelineBinding
 import im.vector.app.features.invite.VectorInviteView
 import kotlin.reflect.KMutableProperty0
 
@@ -29,12 +29,12 @@ import kotlin.reflect.KMutableProperty0
  */
 class RoomDetailLazyLoadedViews {
 
-    private var roomDetailBinding: FragmentRoomDetailBinding? = null
+    private var roomDetailBinding: FragmentTimelineBinding? = null
 
     private var failedMessagesWarningView: FailedMessagesWarningView? = null
     private var inviteView: VectorInviteView? = null
 
-    fun bind(roomDetailBinding: FragmentRoomDetailBinding) {
+    fun bind(roomDetailBinding: FragmentTimelineBinding) {
         this.roomDetailBinding = roomDetailBinding
     }
 
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/widget/RoomWidgetsBottomSheet.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/widget/RoomWidgetsBottomSheet.kt
index aa6966254f3..65f3d16ad41 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/widget/RoomWidgetsBottomSheet.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/widget/RoomWidgetsBottomSheet.kt
@@ -30,8 +30,8 @@ import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
 import im.vector.app.core.resources.ColorProvider
 import im.vector.app.databinding.BottomSheetGenericListWithTitleBinding
 import im.vector.app.features.home.room.detail.RoomDetailAction
-import im.vector.app.features.home.room.detail.RoomDetailViewModel
 import im.vector.app.features.home.room.detail.RoomDetailViewState
+import im.vector.app.features.home.room.detail.TimelineViewModel
 import im.vector.app.features.navigation.Navigator
 import org.matrix.android.sdk.api.session.widgets.model.Widget
 import javax.inject.Inject
@@ -48,7 +48,7 @@ class RoomWidgetsBottomSheet :
     @Inject lateinit var colorProvider: ColorProvider
     @Inject lateinit var navigator: Navigator
 
-    private val roomDetailViewModel: RoomDetailViewModel by parentFragmentViewModel()
+    private val timelineViewModel: TimelineViewModel by parentFragmentViewModel()
 
     override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetGenericListWithTitleBinding {
         return BottomSheetGenericListWithTitleBinding.inflate(inflater, container, false)
@@ -61,7 +61,7 @@ class RoomWidgetsBottomSheet :
         views.bottomSheetTitle.textSize = 20f
         views.bottomSheetTitle.setTextColor(colorProvider.getColorFromAttribute(R.attr.vctr_content_primary))
         epoxyController.listener = this
-        roomDetailViewModel.onAsync(RoomDetailViewState::activeRoomWidgets) {
+        timelineViewModel.onAsync(RoomDetailViewState::activeRoomWidgets) {
             epoxyController.setData(it)
         }
     }
@@ -72,13 +72,13 @@ class RoomWidgetsBottomSheet :
         super.onDestroyView()
     }
 
-    override fun didSelectWidget(widget: Widget) = withState(roomDetailViewModel) {
+    override fun didSelectWidget(widget: Widget) = withState(timelineViewModel) {
         navigator.openRoomWidget(requireContext(), it.roomId, widget)
         dismiss()
     }
 
     override fun didSelectManageWidgets() {
-        roomDetailViewModel.handle(RoomDetailAction.OpenIntegrationManager)
+        timelineViewModel.handle(RoomDetailAction.OpenIntegrationManager)
         dismiss()
     }
 
diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadListModel.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadListItem.kt
similarity index 96%
rename from vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadListModel.kt
rename to vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadListItem.kt
index 72ba673972f..2364e86166a 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadListModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/model/ThreadListItem.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2019 New Vector Ltd
+ * Copyright 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.
@@ -36,8 +36,8 @@ import im.vector.app.features.home.AvatarRenderer
 import org.matrix.android.sdk.api.session.threads.ThreadNotificationState
 import org.matrix.android.sdk.api.util.MatrixItem
 
-@EpoxyModelClass(layout = R.layout.item_thread_list)
-abstract class ThreadListModel : VectorEpoxyModel() {
+@EpoxyModelClass(layout = R.layout.item_thread)
+abstract class ThreadListItem : VectorEpoxyModel() {
 
     @EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
     @EpoxyAttribute lateinit var matrixItem: MatrixItem
diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListController.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListController.kt
index 984c8e8f7ed..c123bceafbc 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListController.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/viewmodel/ThreadListController.kt
@@ -22,7 +22,7 @@ import im.vector.app.core.date.DateFormatKind
 import im.vector.app.core.date.VectorDateFormatter
 import im.vector.app.core.resources.StringProvider
 import im.vector.app.features.home.AvatarRenderer
-import im.vector.app.features.home.room.threads.list.model.threadList
+import im.vector.app.features.home.room.threads.list.model.threadListItem
 import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
 import org.matrix.android.sdk.api.session.threads.ThreadNotificationState
 import org.matrix.android.sdk.api.util.toMatrixItem
@@ -60,7 +60,7 @@ class ThreadListController @Inject constructor(
                 ?.forEach { timelineEvent ->
                     val date = dateFormatter.format(timelineEvent.root.threadDetails?.lastMessageTimestamp, DateFormatKind.ROOM_LIST)
                     val decryptionErrorMessage = stringProvider.getString(R.string.encrypted_message)
-                    threadList {
+                    threadListItem {
                         id(timelineEvent.eventId)
                         avatarRenderer(host.avatarRenderer)
                         matrixItem(timelineEvent.senderInfo.toMatrixItem())
diff --git a/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt
index 2a14ce3aa01..f388ce14109 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/threads/list/views/ThreadListFragment.kt
@@ -75,6 +75,7 @@ class ThreadListFragment @Inject constructor(
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
         initToolbar()
+        initTextConstants()
         views.threadListRecyclerView.configureWith(threadListController, TimelineItemAnimator(), hasFixedSize = false)
         threadListController.listener = this
     }
@@ -90,6 +91,12 @@ class ThreadListFragment @Inject constructor(
         renderToolbar()
     }
 
+    private fun initTextConstants() {
+        views.threadListEmptyNoticeTextView.text = String.format(
+                resources.getString(R.string.thread_list_empty_notice),
+                resources.getString(R.string.reply_in_thread))
+    }
+
     override fun invalidate() = withState(threadListViewModel) { state ->
         renderEmptyStateIfNeeded(state)
         threadListController.update(state)
diff --git a/vector/src/main/res/layout/fragment_thread_list.xml b/vector/src/main/res/layout/fragment_thread_list.xml
index 77f46bf3eea..7e7c79f8c36 100644
--- a/vector/src/main/res/layout/fragment_thread_list.xml
+++ b/vector/src/main/res/layout/fragment_thread_list.xml
@@ -34,7 +34,7 @@
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toBottomOf="@id/threadListAppBarLayout"
-        tools:listitem="@layout/item_thread_list"
+        tools:listitem="@layout/item_thread"
         tools:visibility="gone" />
 
     
+            tools:text="@string/thread_list_empty_notice" />
 
 
     
diff --git a/vector/src/main/res/layout/fragment_room_detail.xml b/vector/src/main/res/layout/fragment_timeline.xml
similarity index 100%
rename from vector/src/main/res/layout/fragment_room_detail.xml
rename to vector/src/main/res/layout/fragment_timeline.xml
diff --git a/vector/src/main/res/layout/item_thread_list.xml b/vector/src/main/res/layout/item_thread.xml
similarity index 100%
rename from vector/src/main/res/layout/item_thread_list.xml
rename to vector/src/main/res/layout/item_thread.xml
diff --git a/vector/src/main/res/layout/view_room_detail_thread_toolbar.xml b/vector/src/main/res/layout/view_room_detail_thread_toolbar.xml
index 6a72422e20f..e16912246e1 100644
--- a/vector/src/main/res/layout/view_room_detail_thread_toolbar.xml
+++ b/vector/src/main/res/layout/view_room_detail_thread_toolbar.xml
@@ -56,7 +56,7 @@
         app:layout_constraintBottom_toBottomOf="@id/roomToolbarThreadImageView"
         app:layout_constraintStart_toEndOf="@id/roomToolbarThreadImageView"
         app:layout_constraintTop_toTopOf="@id/roomToolbarThreadImageView"
-        tools:text="RoomName"
+        tools:text="@sample/rooms.json/data/name"
         tools:visibility="visible" />
 
 
diff --git a/vector/src/main/res/layout/view_thread_room_summary.xml b/vector/src/main/res/layout/view_thread_room_summary.xml
index b1c490e5b30..0f184edef3a 100644
--- a/vector/src/main/res/layout/view_thread_room_summary.xml
+++ b/vector/src/main/res/layout/view_thread_room_summary.xml
@@ -55,5 +55,5 @@
         app:layout_constraintHorizontal_bias="0"
         app:layout_constraintStart_toEndOf="@id/messageThreadSummaryAvatarImageView"
         app:layout_constraintTop_toTopOf="parent"
-        tools:text="Hello There, whats up! Its a large sentence whats up! Its a large centence" />
+        tools:text="@sample/messages.json/data/message" />
 
diff --git a/vector/src/main/res/menu/menu_timeline.xml b/vector/src/main/res/menu/menu_timeline.xml
index 10f4fb05756..962c505e4e6 100644
--- a/vector/src/main/res/menu/menu_timeline.xml
+++ b/vector/src/main/res/menu/menu_timeline.xml
@@ -90,7 +90,7 @@
             
         
diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml
index b86b2dd27f6..4e3b132be74 100644
--- a/vector/src/main/res/values/strings.xml
+++ b/vector/src/main/res/values/strings.xml
@@ -472,7 +472,6 @@
     
     View in room
     Copy link to thread
-    Share
 
     
     Confirmation
@@ -1048,7 +1047,8 @@
     Shows all threads you’ve participated in
     Keep discussions organised with threads
     Threads help keep your conversations on-topic and easy to track.
-    Tip: Long tap a message and use “Reply in thread”.
+    
+    Tip: Long tap a message and use “%s”.
 
     
     Reason for reporting this content

From 5e23947419e7a90d448c43474e3aaaa8c6de4674 Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Mon, 17 Jan 2022 19:22:22 +0200
Subject: [PATCH 108/581] Enhance filtering to support threads

---
 .../sdk/internal/session/search/SearchTask.kt | 35 ++++++++++-
 .../detail/search/SearchResultController.kt   |  1 +
 .../room/detail/search/SearchResultItem.kt    | 41 ++++++++++++
 .../main/res/layout/item_search_result.xml    | 62 +++++++++++++++++++
 vector/src/main/res/values/strings.xml        |  1 +
 5 files changed, 138 insertions(+), 2 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchTask.kt
index 8de762ee1b6..3ba7d11c3df 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchTask.kt
@@ -19,6 +19,10 @@ package org.matrix.android.sdk.internal.session.search
 import org.matrix.android.sdk.api.session.search.EventAndSender
 import org.matrix.android.sdk.api.session.search.SearchResult
 import org.matrix.android.sdk.api.util.MatrixItem
+import org.matrix.android.sdk.internal.database.RealmSessionProvider
+import org.matrix.android.sdk.internal.database.mapper.asDomain
+import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
+import org.matrix.android.sdk.internal.database.query.where
 import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.search.request.SearchRequestBody
@@ -28,6 +32,7 @@ import org.matrix.android.sdk.internal.session.search.request.SearchRequestFilte
 import org.matrix.android.sdk.internal.session.search.request.SearchRequestOrder
 import org.matrix.android.sdk.internal.session.search.request.SearchRequestRoomEvents
 import org.matrix.android.sdk.internal.session.search.response.SearchResponse
+import org.matrix.android.sdk.internal.session.search.response.SearchResponseItem
 import org.matrix.android.sdk.internal.task.Task
 import javax.inject.Inject
 
@@ -47,7 +52,8 @@ internal interface SearchTask : Task {
 
 internal class DefaultSearchTask @Inject constructor(
         private val searchAPI: SearchAPI,
-        private val globalErrorReceiver: GlobalErrorReceiver
+        private val globalErrorReceiver: GlobalErrorReceiver,
+        private val realmSessionProvider: RealmSessionProvider
 ) : SearchTask {
 
     override suspend fun execute(params: SearchTask.Params): SearchResult {
@@ -74,12 +80,22 @@ internal class DefaultSearchTask @Inject constructor(
     }
 
     private fun SearchResponse.toDomain(): SearchResult {
+        val localTimelineEvents = findRootThreadEventsFromDB(searchCategories.roomEvents?.results)
         return SearchResult(
                 nextBatch = searchCategories.roomEvents?.nextBatch,
                 highlights = searchCategories.roomEvents?.highlights,
                 results = searchCategories.roomEvents?.results?.map { searchResponseItem ->
+
+                    val localThreadEventDetails = localTimelineEvents
+                            ?.firstOrNull { it.eventId ==  searchResponseItem.event.eventId }
+                            ?.root
+                            ?.asDomain()
+                            ?.threadDetails
+
                     EventAndSender(
-                            searchResponseItem.event,
+                            searchResponseItem.event.apply {
+                                threadDetails = localThreadEventDetails
+                            },
                             searchResponseItem.event.senderId?.let { senderId ->
                                 searchResponseItem.context?.profileInfo?.get(senderId)
                                         ?.let {
@@ -94,4 +110,19 @@ internal class DefaultSearchTask @Inject constructor(
                 }?.reversed()
         )
     }
+
+    /**
+     * Find local events if exists in order to enhance the result with thread summary
+     */
+    private fun findRootThreadEventsFromDB(searchResponseItemList: List?): List? {
+        return realmSessionProvider.withRealm { realm ->
+            searchResponseItemList?.mapNotNull {
+                it.event.roomId ?: return@mapNotNull null
+                it.event.eventId ?: return@mapNotNull null
+                TimelineEventEntity.where(realm, it.event.roomId, it.event.eventId).findFirst()
+            }?.filter {
+                it.root?.isRootThread == true || it.root?.isThread() == true
+            }
+        }
+    }
 }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultController.kt
index 4c5a52864dc..5c0f47a4522 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultController.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultController.kt
@@ -122,6 +122,7 @@ class SearchResultController @Inject constructor(
                     .spannable(spannable.toEpoxyCharSequence())
                     .sender(eventAndSender.sender
                             ?: eventAndSender.event.senderId?.let { session.getRoomMember(it, data.roomId) }?.toMatrixItem())
+                    .threadDetails(event.threadDetails)
                     .listener { listener?.onItemClicked(eventAndSender.event) }
                     .let { result.add(it) }
         }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultItem.kt
index 9d146792d92..fcddc2286b4 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchResultItem.kt
@@ -18,8 +18,11 @@ package im.vector.app.features.home.room.detail.search
 
 import android.widget.ImageView
 import android.widget.TextView
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.view.isVisible
 import com.airbnb.epoxy.EpoxyAttribute
 import com.airbnb.epoxy.EpoxyModelClass
+import im.vector.app.BuildConfig
 import im.vector.app.R
 import im.vector.app.core.epoxy.ClickListener
 import im.vector.app.core.epoxy.VectorEpoxyHolder
@@ -29,6 +32,7 @@ import im.vector.app.core.epoxy.onClick
 import im.vector.app.core.extensions.setTextOrHide
 import im.vector.app.features.displayname.getBestName
 import im.vector.app.features.home.AvatarRenderer
+import org.matrix.android.sdk.api.session.threads.ThreadDetails
 import org.matrix.android.sdk.api.util.MatrixItem
 
 @EpoxyModelClass(layout = R.layout.item_search_result)
@@ -38,6 +42,8 @@ abstract class SearchResultItem : VectorEpoxyModel() {
     @EpoxyAttribute var formattedDate: String? = null
     @EpoxyAttribute lateinit var spannable: EpoxyCharSequence
     @EpoxyAttribute var sender: MatrixItem? = null
+    @EpoxyAttribute var threadDetails: ThreadDetails? = null
+
     @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var listener: ClickListener? = null
 
     override fun bind(holder: Holder) {
@@ -48,6 +54,36 @@ abstract class SearchResultItem : VectorEpoxyModel() {
         holder.memberNameView.setTextOrHide(sender?.getBestName())
         holder.timeView.text = formattedDate
         holder.contentView.text = spannable.charSequence
+
+        if (BuildConfig.THREADING_ENABLED) {
+            threadDetails?.let {
+                if (it.isRootThread) {
+                    showThreadSummary(holder)
+                    holder.threadSummaryCounterTextView.text = it.numberOfThreads.toString()
+                    holder.threadSummaryInfoTextView.text = it.threadSummaryLatestTextMessage.orEmpty()
+
+                    val userId = it.threadSummarySenderInfo?.userId ?: return@let
+                    val displayName = it.threadSummarySenderInfo?.displayName
+                    val avatarUrl = it.threadSummarySenderInfo?.avatarUrl
+                    avatarRenderer.render(MatrixItem.UserItem(userId, displayName, avatarUrl), holder.threadSummaryAvatarImageView)
+                } else {
+                    showFromThread(holder)
+                }
+            } ?: run {
+                holder.threadSummaryConstraintLayout.isVisible = false
+                holder.fromThreadConstraintLayout.isVisible = false
+            }
+        }
+    }
+
+    private fun showThreadSummary(holder: Holder, show: Boolean = true) {
+        holder.threadSummaryConstraintLayout.isVisible = show
+        holder.fromThreadConstraintLayout.isVisible = !show
+    }
+
+    private fun showFromThread(holder: Holder, show: Boolean = true) {
+        holder.threadSummaryConstraintLayout.isVisible = !show
+        holder.fromThreadConstraintLayout.isVisible = show
     }
 
     class Holder : VectorEpoxyHolder() {
@@ -55,5 +91,10 @@ abstract class SearchResultItem : VectorEpoxyModel() {
         val memberNameView by bind(R.id.messageMemberNameView)
         val timeView by bind(R.id.messageTimeView)
         val contentView by bind(R.id.messageContentView)
+        val threadSummaryConstraintLayout by bind(R.id.searchThreadSummaryConstraintLayout)
+        val threadSummaryCounterTextView by bind(R.id.messageThreadSummaryCounterTextView)
+        val threadSummaryAvatarImageView by bind(R.id.messageThreadSummaryAvatarImageView)
+        val threadSummaryInfoTextView by bind(R.id.messageThreadSummaryInfoTextView)
+        val fromThreadConstraintLayout by bind(R.id.searchFromThreadConstraintLayout)
     }
 }
diff --git a/vector/src/main/res/layout/item_search_result.xml b/vector/src/main/res/layout/item_search_result.xml
index bdd1294b885..3264a7d2308 100644
--- a/vector/src/main/res/layout/item_search_result.xml
+++ b/vector/src/main/res/layout/item_search_result.xml
@@ -62,4 +62,66 @@
         app:layout_constraintTop_toBottomOf="@id/messageMemberNameView"
         tools:text="@sample/messages.json/data/message" />
 
+    
+
+        
+    
+
+    
+
+        
+
+        
+
+    
 
diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml
index 4e3b132be74..f6868adc34a 100644
--- a/vector/src/main/res/values/strings.xml
+++ b/vector/src/main/res/values/strings.xml
@@ -1049,6 +1049,7 @@
     Threads help keep your conversations on-topic and easy to track.
     
     Tip: Long tap a message and use “%s”.
+    From a Thread
 
     
     Reason for reporting this content

From 10599aa7286a86105943336dd29823e6b1034682 Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Tue, 18 Jan 2022 13:11:52 +0200
Subject: [PATCH 109/581] ktlint format

---
 .../src/main/java/org/matrix/android/sdk/rx/RxRoom.kt           | 0
 .../home/room/detail/composer/MessageComposerViewModel.kt       | 2 +-
 2 files changed, 1 insertion(+), 1 deletion(-)
 delete mode 100644 matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxRoom.kt

diff --git a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxRoom.kt b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxRoom.kt
deleted file mode 100644
index e69de29bb2d..00000000000
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 60622b3fe7f..00755a78e77 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
@@ -183,7 +183,7 @@ class MessageComposerViewModel @AssistedInject constructor(
         withState { state ->
             when (state.sendMode) {
                 is SendMode.Regular -> {
-                    when (val slashCommandResult = CommandParser.parseSplashCommand(
+                    when (val slashCommandResult = CommandParser.parseSlashCommand(
                             textMessage = action.text,
                             isInThreadTimeline =  state.isInThreadTimeline())) {
                         is ParsedCommand.ErrorNotACommand                  -> {

From 707397cb9d41a331b83d0a8b02c836aa467d0f1f Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Tue, 18 Jan 2022 15:28:44 +0200
Subject: [PATCH 110/581] cleanup

---
 .../room/relation/DefaultRelationService.kt        |  2 ++
 .../session/room/send/LocalEchoEventFactory.kt     |  8 ++++++++
 .../session/room/timeline/LoadTimelineStrategy.kt  |  1 -
 .../session/room/timeline/TimelineChunk.kt         | 14 +-------------
 .../sync/handler/room/ThreadsAwarenessHandler.kt   |  2 +-
 .../java/im/vector/app/features/command/Command.kt |  4 ++--
 6 files changed, 14 insertions(+), 17 deletions(-)

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 47794e424f6..b5b9aa5afb8 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
@@ -176,6 +176,7 @@ internal class DefaultRelationService @AssistedInject constructor(
             formattedText: String?,
             eventReplied: TimelineEvent?): Cancelable? {
         val event = if (eventReplied != null) {
+            // Reply within a thread
             eventFactory.createReplyTextEvent(
                     roomId = roomId,
                     eventReplied = eventReplied,
@@ -187,6 +188,7 @@ internal class DefaultRelationService @AssistedInject constructor(
                     }
                     ?: return null
         } else {
+            // Normal thread reply
             eventFactory.createThreadTextEvent(
                     rootThreadEventId = rootThreadEventId,
                     roomId = roomId,
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 86da1862226..53ea19e7610 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
@@ -457,6 +457,14 @@ internal class LocalEchoEventFactory @Inject constructor(
     /**
      * Generates the appropriate relatesTo object for a reply event.
      * It can either be a regular reply or a reply within a thread
+     * "m.relates_to": {
+     *      "rel_type": "m.thread",
+     *      "event_id": "$thread_root",
+     *      "m.in_reply_to": {
+     *          "event_id": "$event_target",
+     *          "render_in": ["m.thread"]
+     *        }
+     *   }
      */
     private fun generateReplyRelationContent(eventId: String, rootThreadEventId: String? = null): RelationDefaultContent =
             rootThreadEventId?.let {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt
index 1d92d89a3aa..efc11a8bde1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt
@@ -171,7 +171,6 @@ internal class LoadTimelineStrategy(
     }
 
     suspend fun loadMore(count: Int, direction: Timeline.Direction, fetchOnServerIfNeeded: Boolean = true): LoadMoreResult {
-        // /
         if (mode is Mode.Permalink && timelineChunk == null) {
             val params = GetContextOfEventTask.Params(roomId, mode.originEventId)
             try {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt
index 20942de4088..33f8d88d73e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt
@@ -289,22 +289,10 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity,
         val displayIndex = getNextDisplayIndex(direction) ?: return LoadedFromStorage()
         val baseQuery = timelineEventEntities.where()
 
-//        val timelineEvents = if (timelineSettings.rootThreadEventId != null) {
-//            baseQuery
-//                    .beginGroup()
-//                    .equalTo(TimelineEventEntityFields.ROOT.ROOT_THREAD_EVENT_ID, timelineSettings.rootThreadEventId)
-//                    .or()
-//                    .equalTo(TimelineEventEntityFields.ROOT.EVENT_ID, timelineSettings.rootThreadEventId)
-//                    .endGroup()
-//                    .offsets(direction, count, displayIndex)
-//                    .findAll()
-//                    .orEmpty()
-//        } else {
         val timelineEvents = baseQuery
                 .offsets(direction, count, displayIndex)
                 .findAll()
                 .orEmpty()
-//        }
 
         if (timelineEvents.isEmpty()) return LoadedFromStorage()
         fetchRootThreadEventsIfNeeded(timelineEvents)
@@ -331,7 +319,7 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity,
     }
 
     /**
-     * Returns whether or not the the thread has reached end. It returned false if the current timeline
+     * Returns whether or not the the thread has reached end. It returns false if the current timeline
      * is not a thread timeline
      */
     private fun threadReachedEnd(timelineEvents: List): Boolean =
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt
index ee606d1fac4..d093aab21f3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt
@@ -171,7 +171,7 @@ internal class ThreadsAwarenessHandler @Inject constructor(
     /**
      * If the event is a thread event then transform/enhance it to a visual Reply Event,
      * If the event is not a thread event, null value will be returned
-     * If there is an error (ex. the root/origin thread event is not found), null willl be returend
+     * If there is an error (ex. the root/origin thread event is not found), null will be returned
      */
     private fun transformThreadToReplyIfNeeded(realm: Realm, roomId: String?, event: Event, decryptedResult: JsonDict?): JsonDict? {
         roomId ?: return null
diff --git a/vector/src/main/java/im/vector/app/features/command/Command.kt b/vector/src/main/java/im/vector/app/features/command/Command.kt
index 5725703c7ed..57f228e75dd 100644
--- a/vector/src/main/java/im/vector/app/features/command/Command.kt
+++ b/vector/src/main/java/im/vector/app/features/command/Command.kt
@@ -45,8 +45,8 @@ enum class Command(val command: String,
     REMOVE_USER("/remove", arrayOf("/kick"), " [reason]", R.string.command_description_kick_user, false, false),
     CHANGE_DISPLAY_NAME("/nick", null, "", R.string.command_description_nick, false, false),
     CHANGE_DISPLAY_NAME_FOR_ROOM("/myroomnick", arrayOf("/roomnick"), "", R.string.command_description_nick_for_room, false, false),
-    ROOM_AVATAR("/roomavatar", null, "", R.string.command_description_room_avatar, true /* Since user has to know the mxc url */, false),
-    CHANGE_AVATAR_FOR_ROOM("/myroomavatar", null, "", R.string.command_description_avatar_for_room, true /* Since user has to know the mxc url */, false),
+    ROOM_AVATAR("/roomavatar", null, "", R.string.command_description_room_avatar, true /* User has to know the mxc url */, false),
+    CHANGE_AVATAR_FOR_ROOM("/myroomavatar", null, "", R.string.command_description_avatar_for_room, true /* User has to know the mxc url */, false),
     MARKDOWN("/markdown", null, "", R.string.command_description_markdown, false, false),
     RAINBOW("/rainbow", null, "", R.string.command_description_rainbow, false, true),
     RAINBOW_EMOTE("/rainbowme", null, "", R.string.command_description_rainbow_emote, false, true),

From 4cff3938e7e1f0d9d8ee235ffa7c03cf1e6d4a68 Mon Sep 17 00:00:00 2001
From: ariskotsomitopoulos 
Date: Tue, 18 Jan 2022 16:05:41 +0200
Subject: [PATCH 111/581] - Hide read receipts from thread timeline - Enhance
 FetchThreadTimelineTask

---
 .../room/model/relation/RelationService.kt    |   2 +-
 .../database/helper/ThreadEventsHelper.kt     |   9 +-
 .../room/relation/DefaultRelationService.kt   |   2 +-
 .../threads/FetchThreadTimelineTask.kt        | 165 +++++++++++++++++-
 .../timeline/TimelineEventController.kt       |   7 +-
 .../factory/ReadReceiptsItemFactory.kt        |   7 +-
 .../detail/timeline/item/ReadReceiptsItem.kt  |   3 +
 7 files changed, 185 insertions(+), 10 deletions(-)

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 183cd481d27..e49b1f0a737 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
@@ -153,5 +153,5 @@ interface RelationService {
      * from the backend
      * @param rootThreadEventId the root thread eventId
      */
-    suspend fun fetchThreadTimeline(rootThreadEventId: String): List
+    suspend fun fetchThreadTimeline(rootThreadEventId: String): Boolean
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt
index 557bb4bdf1f..57e65941e7c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt
@@ -36,7 +36,10 @@ import org.matrix.android.sdk.internal.database.query.whereRoomId
  * Finds the root thread event and update it with the latest message summary along with the number
  * of threads included. If there is no root thread event no action is done
  */
-internal fun Map.updateThreadSummaryIfNeeded(roomId: String, realm: Realm, currentUserId: String) {
+internal fun Map.updateThreadSummaryIfNeeded(
+        roomId: String,
+        realm: Realm, currentUserId: String,
+        shouldUpdateNotifications: Boolean = true) {
     if (!BuildConfig.THREADING_ENABLED) return
 
     for ((rootThreadEventId, eventEntity) in this) {
@@ -55,7 +58,9 @@ internal fun Map.updateThreadSummaryIfNeeded(roomId: String
         }
     }
 
-    updateNotificationsNew(roomId, realm, currentUserId)
+    if(shouldUpdateNotifications) {
+        updateNotificationsNew(roomId, realm, currentUserId)
+    }
 }
 
 /**
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 b5b9aa5afb8..eee553ab80c 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
@@ -203,7 +203,7 @@ internal class DefaultRelationService @AssistedInject constructor(
         return eventSenderProcessor.postEvent(event, cryptoSessionInfoProvider.isRoomEncrypted(roomId))
     }
 
-    override suspend fun fetchThreadTimeline(rootThreadEventId: String): List {
+    override suspend fun fetchThreadTimeline(rootThreadEventId: String): Boolean {
         return fetchThreadTimelineTask.execute(FetchThreadTimelineTask.Params(roomId, rootThreadEventId))
     }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt
index d62ce4158f2..f50a691d433 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt
@@ -15,17 +15,45 @@
  */
 package org.matrix.android.sdk.internal.session.room.relation.threads
 
+import com.zhuinden.monarchy.Monarchy
+import io.realm.Realm
+import org.matrix.android.sdk.api.session.crypto.MXCryptoError
 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.room.model.RoomMemberContent
+import org.matrix.android.sdk.api.session.room.send.SendState
 import org.matrix.android.sdk.internal.crypto.CryptoSessionInfoProvider
+import org.matrix.android.sdk.internal.crypto.DefaultCryptoService
+import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult
+import org.matrix.android.sdk.internal.database.helper.addTimelineEvent
+import org.matrix.android.sdk.internal.database.helper.updateThreadSummaryIfNeeded
+import org.matrix.android.sdk.internal.database.mapper.asDomain
+import org.matrix.android.sdk.internal.database.mapper.toEntity
+import org.matrix.android.sdk.internal.database.model.ChunkEntity
+import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity
+import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity
+import org.matrix.android.sdk.internal.database.model.EventEntity
+import org.matrix.android.sdk.internal.database.model.EventInsertType
+import org.matrix.android.sdk.internal.database.model.ReactionAggregatedSummaryEntity
+import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore
+import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfRoom
+import org.matrix.android.sdk.internal.database.query.getOrCreate
+import org.matrix.android.sdk.internal.database.query.getOrNull
+import org.matrix.android.sdk.internal.database.query.where
+import org.matrix.android.sdk.internal.di.SessionDatabase
+import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
+import org.matrix.android.sdk.internal.session.events.getFixedRoomMemberContent
 import org.matrix.android.sdk.internal.session.room.RoomAPI
+import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection
 import org.matrix.android.sdk.internal.task.Task
+import org.matrix.android.sdk.internal.util.awaitTransaction
+import timber.log.Timber
 import javax.inject.Inject
 
-internal interface FetchThreadTimelineTask : Task> {
+internal interface FetchThreadTimelineTask : Task {
     data class Params(
             val roomId: String,
             val rootThreadEventId: String
@@ -35,10 +63,13 @@ internal interface FetchThreadTimelineTask : Task {
+    override suspend fun execute(params: FetchThreadTimelineTask.Params): Boolean {
         val isRoomEncrypted = cryptoSessionInfoProvider.isRoomEncrypted(params.roomId)
         val response = executeRequest(globalErrorReceiver) {
             roomAPI.getRelations(
@@ -50,6 +81,132 @@ internal class DefaultFetchThreadTimelineTask @Inject constructor(
             )
         }
 
-        return response.chunks + listOfNotNull(response.originalEvent)
+        val threadList = response.chunks + listOfNotNull(response.originalEvent)
+
+
+        return storeNewEventsIfNeeded(threadList, params.roomId)
+    }
+
+
+    /**
+     * Store new events if they are not already received, and returns weather or not,
+     * a timeline update should be made
+     * @param threadList is the list containing the thread replies
+     * @param roomId the roomId of the the thread
+     * @return
+     */
+    private suspend fun storeNewEventsIfNeeded(threadList: List, roomId: String): Boolean {
+        var eventsSkipped = 0
+        monarchy
+                .awaitTransaction { realm ->
+                    val chunk = ChunkEntity.findLastForwardChunkOfRoom(realm, roomId)
+
+                    val optimizedThreadSummaryMap = hashMapOf()
+                    val roomMemberContentsByUser = HashMap()
+
+                    for (event in threadList.reversed()) {
+
+                        if (event.eventId == null || event.senderId == null || event.type == null) {
+                            eventsSkipped++
+                            continue
+                        }
+
+                        if (EventEntity.where(realm, event.eventId).findFirst() != null) {
+                            //  Skip if event already exists
+                            eventsSkipped++
+                            continue
+                        }
+                        if (event.isEncrypted()) {
+                            // Decrypt events that will be stored
+                            decryptIfNeeded(event, roomId)
+                        }
+
+                        handleReaction(realm, event, roomId)
+
+                        val ageLocalTs = event.unsignedData?.age?.let { System.currentTimeMillis() - it }
+                        val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.INCREMENTAL_SYNC)
+
+                        // Sender info
+                        roomMemberContentsByUser.getOrPut(event.senderId) {
+                            // If we don't have any new state on this user, get it from db
+                            val rootStateEvent = CurrentStateEventEntity.getOrNull(realm, roomId, event.senderId, EventType.STATE_ROOM_MEMBER)?.root
+                            rootStateEvent?.asDomain()?.getFixedRoomMemberContent()
+                        }
+
+                        chunk?.addTimelineEvent(roomId, eventEntity, PaginationDirection.FORWARDS, roomMemberContentsByUser)
+                        eventEntity.rootThreadEventId?.let {
+                            // This is a thread event
+                            optimizedThreadSummaryMap[it] = eventEntity
+                        } ?: run {
+                            // This is a normal event or a root thread one
+                            optimizedThreadSummaryMap[eventEntity.eventId] = eventEntity
+                        }
+                    }
+
+                    optimizedThreadSummaryMap.updateThreadSummaryIfNeeded(
+                            roomId = roomId,
+                            realm = realm,
+                            currentUserId = userId,
+                            shouldUpdateNotifications = false
+                    )
+                }
+        Timber.i("----> size: ${threadList.size} | skipped: $eventsSkipped | threads: ${threadList.map { it.eventId }}")
+
+        return eventsSkipped == threadList.size
+    }
+
+    /**
+     * Invoke the event decryption mechanism for a specific event
+     */
+
+    private fun decryptIfNeeded(event: Event, roomId: String) {
+        try {
+            // Event from sync does not have roomId, so add it to the event first
+            val result = cryptoService.decryptEvent(event.copy(roomId = roomId), "")
+            event.mxDecryptionResult = OlmDecryptionResult(
+                    payload = result.clearEvent,
+                    senderKey = result.senderCurve25519Key,
+                    keysClaimed = result.claimedEd25519Key?.let { k -> mapOf("ed25519" to k) },
+                    forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
+            )
+        } catch (e: MXCryptoError) {
+            if (e is MXCryptoError.Base) {
+                event.mCryptoError = e.errorType
+                event.mCryptoErrorReason = e.technicalMessage.takeIf { it.isNotEmpty() } ?: e.detailedErrorDescription
+            }
+        }
+    }
+
+    private fun handleReaction(realm: Realm,
+                               event: Event,
+                               roomId: String) {
+
+        val unsignedData = event.unsignedData ?: return
+        val relatedEventId = event.eventId ?: return
+
+        unsignedData.relations?.annotations?.chunk?.forEach { relationChunk ->
+
+            if (relationChunk.type == EventType.REACTION) {
+                val reaction = relationChunk.key
+                Timber.i("----> Annotation found in ${event.eventId} ${relationChunk.key} ")
+
+                val eventSummary = EventAnnotationsSummaryEntity.getOrCreate(realm, roomId, relatedEventId)
+                var sum = eventSummary.reactionsSummary.find { it.key == reaction }
+
+                if (sum == null) {
+                    sum = realm.createObject(ReactionAggregatedSummaryEntity::class.java)
+                    sum.key = reaction
+                    sum.firstTimestamp = event.originServerTs ?: 0
+                    Timber.v("Adding synced reaction $reaction")
+                    sum.count = 1
+                    // reactionEventId not included in the /relations API
+//                    sum.sourceEvents.add(reactionEventId)
+                    eventSummary.reactionsSummary.add(sum)
+                } else {
+                    sum.count += 1
+                }
+            }
+
+        }
     }
 }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt
index bd181d71bad..b6b985632c3 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt
@@ -443,7 +443,12 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
         }
         val readReceipts = receiptsByEvents[event.eventId].orEmpty()
         return copy(
-                readReceiptsItem = readReceiptsItemFactory.create(event.eventId, readReceipts, callback),
+                readReceiptsItem = readReceiptsItemFactory.create(
+                        event.eventId,
+                        readReceipts,
+                        callback,
+                        partialState.isFromThreadTimeline()
+                ),
                 formattedDayModel = formattedDayModel,
                 mergedHeaderModel = mergedHeaderModel
         )
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/ReadReceiptsItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/ReadReceiptsItemFactory.kt
index 8a74a6d2078..d477a3d40ed 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/ReadReceiptsItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/ReadReceiptsItemFactory.kt
@@ -26,7 +26,11 @@ import javax.inject.Inject
 
 class ReadReceiptsItemFactory @Inject constructor(private val avatarRenderer: AvatarRenderer) {
 
-    fun create(eventId: String, readReceipts: List, callback: TimelineEventController.Callback?): ReadReceiptsItem? {
+    fun create(
+            eventId: String,
+            readReceipts: List,
+            callback: TimelineEventController.Callback?,
+            isFromThreadTimeLine: Boolean): ReadReceiptsItem? {
         if (readReceipts.isEmpty()) {
             return null
         }
@@ -41,6 +45,7 @@ class ReadReceiptsItemFactory @Inject constructor(private val avatarRenderer: Av
                 .eventId(eventId)
                 .readReceipts(readReceiptsData)
                 .avatarRenderer(avatarRenderer)
+                .shouldHideReadReceipts(isFromThreadTimeLine)
                 .clickListener {
                     callback?.onReadReceiptsClicked(readReceiptsData)
                 }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/ReadReceiptsItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/ReadReceiptsItem.kt
index 650c804cfab..4f292532647 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/ReadReceiptsItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/ReadReceiptsItem.kt
@@ -16,6 +16,7 @@
 
 package im.vector.app.features.home.room.detail.timeline.item
 
+import androidx.core.view.isVisible
 import com.airbnb.epoxy.EpoxyAttribute
 import com.airbnb.epoxy.EpoxyModelClass
 import com.airbnb.epoxy.EpoxyModelWithHolder
@@ -31,6 +32,7 @@ abstract class ReadReceiptsItem : EpoxyModelWithHolder(
 
     @EpoxyAttribute lateinit var eventId: String
     @EpoxyAttribute lateinit var readReceipts: List
+    @EpoxyAttribute var shouldHideReadReceipts: Boolean = false
     @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) lateinit var avatarRenderer: AvatarRenderer
     @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) lateinit var clickListener: ClickListener
 
@@ -42,6 +44,7 @@ abstract class ReadReceiptsItem : EpoxyModelWithHolder(
         super.bind(holder)
         holder.readReceiptsView.onClick(clickListener)
         holder.readReceiptsView.render(readReceipts, avatarRenderer)
+        holder.readReceiptsView.isVisible = !shouldHideReadReceipts
     }
 
     override fun unbind(holder: Holder) {

From a9e7c45074343210d73bee0f927521fe9de7aa4d Mon Sep 17 00:00:00 2001
From: ganfra 
Date: Tue, 18 Jan 2022 19:26:23 +0100
Subject: [PATCH 112/581] Fix url preview sizing

---
 ...ageViewConfiguration.kt => TimelineMessageLayoutRenderer.kt} | 2 ++
 vector/src/main/res/layout/view_url_preview.xml                 | 2 ++
 2 files changed, 4 insertions(+)
 rename vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/{MessageViewConfiguration.kt => TimelineMessageLayoutRenderer.kt} (94%)

diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageViewConfiguration.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/TimelineMessageLayoutRenderer.kt
similarity index 94%
rename from vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageViewConfiguration.kt
rename to vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/TimelineMessageLayoutRenderer.kt
index bfb5884674d..23dd9860f86 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/MessageViewConfiguration.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/view/TimelineMessageLayoutRenderer.kt
@@ -21,4 +21,6 @@ interface MessageViewConfiguration {
     var isFirstFromSender: Boolean
     var isLastFromSender: Boolean
     var showTimeAsOverlay: Boolean
+    var showNoBubble: Boolean
+    fun render()
 }
diff --git a/vector/src/main/res/layout/view_url_preview.xml b/vector/src/main/res/layout/view_url_preview.xml
index 93ea4ea2bb5..ff4ab1ed9ac 100644
--- a/vector/src/main/res/layout/view_url_preview.xml
+++ b/vector/src/main/res/layout/view_url_preview.xml
@@ -9,6 +9,7 @@
     
 
         

From 5ee4984ec843e073630f659f2d54f29e86110e03 Mon Sep 17 00:00:00 2001
From: ganfra 
Date: Tue, 18 Jan 2022 19:27:12 +0100
Subject: [PATCH 113/581] Bubbles: handle images and make small refactoring

---
 .../ui-styles/src/main/res/values/dimens.xml  |   1 +
 .../src/main/res/values/styles_timeline.xml   |   6 -
 .../timeline/item/AbsBaseMessageItem.kt       |   7 +-
 .../timeline/item/MessageImageVideoItem.kt    |  24 +++-
 .../timeline/style/TimelineMessageLayout.kt   |   3 +-
 .../style/TimelineMessageLayoutFactory.kt     |   8 ++
 .../detail/timeline/view/MessageBubbleView.kt | 128 +++++++++---------
 .../view/TimelineMessageLayoutRenderer.kt     |  11 +-
 .../features/media/ImageContentRenderer.kt    |   6 +-
 .../res/drawable/overlay_bubble_media.xml     |   1 +
 ...em_timeline_event_view_stubs_container.xml |  25 ++--
 .../main/res/layout/view_message_bubble.xml   |   4 +-
 12 files changed, 121 insertions(+), 103 deletions(-)

diff --git a/library/ui-styles/src/main/res/values/dimens.xml b/library/ui-styles/src/main/res/values/dimens.xml
index d89b3f477ff..15e2fc2fbb8 100644
--- a/library/ui-styles/src/main/res/values/dimens.xml
+++ b/library/ui-styles/src/main/res/values/dimens.xml
@@ -54,6 +54,7 @@
     28dp
     62dp
     300dp
+    12dp
 
     
     0.05
diff --git a/library/ui-styles/src/main/res/values/styles_timeline.xml b/library/ui-styles/src/main/res/values/styles_timeline.xml
index ef2f694d3e3..127d9f3dc75 100644
--- a/library/ui-styles/src/main/res/values/styles_timeline.xml
+++ b/library/ui-styles/src/main/res/values/styles_timeline.xml
@@ -4,12 +4,6 @@
     
 
     
 
+    
+
     
 
+    
+
+
 
\ No newline at end of file
diff --git a/vector/src/main/java/im/vector/app/core/extensions/Context.kt b/vector/src/main/java/im/vector/app/core/extensions/Context.kt
index b8b367b740f..b1e24c95020 100644
--- a/vector/src/main/java/im/vector/app/core/extensions/Context.kt
+++ b/vector/src/main/java/im/vector/app/core/extensions/Context.kt
@@ -18,6 +18,9 @@ package im.vector.app.core.extensions
 
 import android.content.Context
 import android.graphics.drawable.Drawable
+import android.text.Spannable
+import android.text.SpannableString
+import android.text.style.ImageSpan
 import androidx.annotation.ColorInt
 import androidx.annotation.ColorRes
 import androidx.annotation.DrawableRes
@@ -34,6 +37,16 @@ fun Context.singletonEntryPoint(): SingletonEntryPoint {
     return EntryPoints.get(applicationContext, SingletonEntryPoint::class.java)
 }
 
+fun Context.getDrawableAsSpannable(@DrawableRes drawableRes: Int, alignment: Int = ImageSpan.ALIGN_BOTTOM): Spannable {
+    return SpannableString(" ").apply {
+        val span = ContextCompat.getDrawable(this@getDrawableAsSpannable, drawableRes)?.let {
+            it.setBounds(0, 0, it.intrinsicWidth, it.intrinsicHeight)
+            ImageSpan(it, alignment)
+        }
+        setSpan(span, 0, 1, Spannable.SPAN_INCLUSIVE_EXCLUSIVE)
+    }
+}
+
 fun Context.getResTintedDrawable(@DrawableRes drawableRes: Int, @ColorRes tint: Int, @FloatRange(from = 0.0, to = 1.0) alpha: Float = 1f): Drawable? {
     return getTintedDrawable(drawableRes, ContextCompat.getColor(this, tint), alpha)
 }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt
index 21b58e0f4b8..23f2aceff54 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt
@@ -17,17 +17,20 @@
 package im.vector.app.features.home.room.detail.timeline.item
 
 import android.annotation.SuppressLint
-import android.view.Gravity
+import android.graphics.Typeface
 import android.view.View
 import android.view.ViewGroup
 import android.widget.ImageView
 import android.widget.TextView
 import androidx.annotation.IdRes
+import androidx.appcompat.view.ContextThemeWrapper
 import androidx.core.content.ContextCompat.getDrawable
 import androidx.core.view.isVisible
+import androidx.core.widget.TextViewCompat
 import im.vector.app.R
 import im.vector.app.core.epoxy.ClickListener
 import im.vector.app.core.epoxy.onClick
+import im.vector.app.core.extensions.getDrawableAsSpannable
 import im.vector.app.core.ui.views.ShieldImageView
 import im.vector.app.core.utils.DimensionConverter
 import im.vector.app.features.home.AvatarRenderer
@@ -35,6 +38,7 @@ import im.vector.app.features.home.room.detail.timeline.MessageColorProvider
 import im.vector.app.features.home.room.detail.timeline.TimelineEventController
 import im.vector.app.features.home.room.detail.timeline.view.TimelineMessageLayoutRenderer
 import im.vector.app.features.reactions.widget.ReactionButton
+import im.vector.app.features.themes.ThemeUtils
 import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
 import org.matrix.android.sdk.api.session.room.send.SendState
 
@@ -114,7 +118,7 @@ abstract class AbsBaseMessageItem : BaseEventItem
                 holder.reactionsContainer.addView(reactionButton)
             }
             if (reactions.count() > MAX_REACTIONS_TO_SHOW) {
-                val showReactionsTextView = createReactionTextView(holder, 6)
+                val showReactionsTextView = createReactionTextView(holder)
                 if (reactionsSummary.showAll) {
                     showReactionsTextView.setText(R.string.message_reaction_show_less)
                     showReactionsTextView.onClick { reactionsSummary.onShowLessClicked() }
@@ -125,23 +129,21 @@ abstract class AbsBaseMessageItem : BaseEventItem
                 }
                 holder.reactionsContainer.addView(showReactionsTextView)
             }
-            val addMoreReactionsTextView = createReactionTextView(holder, 14)
-            addMoreReactionsTextView.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_add_reaction_small, 0, 0, 0)
+            val addMoreReactionsTextView = createReactionTextView(holder)
+
+            addMoreReactionsTextView.text = holder.view.context.getDrawableAsSpannable(R.drawable.ic_add_reaction_small)
             addMoreReactionsTextView.onClick { reactionsSummary.onAddMoreClicked() }
             holder.reactionsContainer.addView(addMoreReactionsTextView)
             holder.reactionsContainer.setOnLongClickListener(baseAttributes.itemLongClickListener)
         }
     }
 
-    private fun createReactionTextView(holder: H, horizontalPaddingDp: Int): TextView {
-        return TextView(holder.view.context).apply {
-            textSize = 10f
-            gravity = Gravity.CENTER
-            minimumHeight = resources.getDimensionPixelSize(R.dimen.chat_reaction_min_height)
-            minimumWidth = resources.getDimensionPixelSize(R.dimen.chat_reaction_min_width)
+    private fun createReactionTextView(holder: H): TextView {
+        return TextView(ContextThemeWrapper(holder.view.context, R.style.TimelineReactionView)).apply {
             background = getDrawable(context, R.drawable.reaction_rounded_rect_shape_off)
-            val padding = holder.dimensionConverter.dpToPx(horizontalPaddingDp)
-            setPadding(padding, 0, padding, 0)
+            TextViewCompat.setTextAppearance(this, R.style.TextAppearance_Vector_Micro)
+            setTypeface(typeface, Typeface.BOLD)
+            setTextColor(ThemeUtils.getColor(context, R.attr.vctr_content_secondary))
         }
     }
 
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 bf5bb8d302f..422dfb0dbd7 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
@@ -31,7 +31,6 @@ import androidx.core.content.withStyledAttributes
 import androidx.core.graphics.drawable.DrawableCompat
 import androidx.core.view.isVisible
 import androidx.core.view.updateLayoutParams
-import com.google.android.flexbox.FlexDirection
 import com.google.android.material.shape.MaterialShapeDrawable
 import im.vector.app.R
 import im.vector.app.core.resources.LocaleProvider
@@ -80,7 +79,6 @@ class MessageBubbleView @JvmOverloads constructor(context: Context, attrs: Attri
         views.messageThreadSummaryContainer.layoutDirection = layoutDirectionToSet
         views.bubbleWrapper.layoutDirection = layoutDirectionToSet
         views.bubbleView.layoutDirection = currentLayoutDirection
-        views.reactionsContainer.flexDirection = if (isIncoming) FlexDirection.ROW else FlexDirection.ROW_REVERSE
 
         bubbleDrawable = MaterialShapeDrawable()
         rippleMaskDrawable = MaterialShapeDrawable()
diff --git a/vector/src/main/java/im/vector/app/features/reactions/widget/ReactionButton.kt b/vector/src/main/java/im/vector/app/features/reactions/widget/ReactionButton.kt
index 219ca166bb7..7340953f320 100644
--- a/vector/src/main/java/im/vector/app/features/reactions/widget/ReactionButton.kt
+++ b/vector/src/main/java/im/vector/app/features/reactions/widget/ReactionButton.kt
@@ -18,7 +18,6 @@ package im.vector.app.features.reactions.widget
 import android.content.Context
 import android.graphics.drawable.Drawable
 import android.util.AttributeSet
-import android.view.Gravity
 import android.view.View
 import android.widget.LinearLayout
 import androidx.core.content.ContextCompat
@@ -37,8 +36,9 @@ import javax.inject.Inject
 @AndroidEntryPoint
 class ReactionButton @JvmOverloads constructor(context: Context,
                                                attrs: AttributeSet? = null,
-                                               defStyleAttr: Int = 0) :
-        LinearLayout(context, attrs, defStyleAttr), View.OnClickListener, View.OnLongClickListener {
+                                               defStyleAttr: Int = 0,
+                                               defStyleRes: Int = R.style.TimelineReactionView) :
+        LinearLayout(context, attrs, defStyleAttr, defStyleRes), View.OnClickListener, View.OnLongClickListener {
 
     @Inject lateinit var emojiSpanify: EmojiSpanify
 
@@ -67,9 +67,6 @@ class ReactionButton @JvmOverloads constructor(context: Context,
     init {
         inflate(context, R.layout.reaction_button, this)
         orientation = HORIZONTAL
-        minimumHeight = resources.getDimensionPixelSize(R.dimen.chat_reaction_min_height)
-        minimumWidth = resources.getDimensionPixelSize(R.dimen.chat_reaction_min_width)
-        gravity = Gravity.CENTER
         layoutDirection = View.LAYOUT_DIRECTION_LOCALE
         views = ReactionButtonBinding.bind(this)
         views.reactionCount.text = TextUtils.formatCountToShortDecimal(reactionCount)
diff --git a/vector/src/main/res/layout/reaction_button.xml b/vector/src/main/res/layout/reaction_button.xml
index f0a80112207..ca1b17984b6 100644
--- a/vector/src/main/res/layout/reaction_button.xml
+++ b/vector/src/main/res/layout/reaction_button.xml
@@ -3,11 +3,10 @@
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="wrap_content"
-    android:layout_height="@dimen/chat_reaction_min_height"
+    android:layout_height="wrap_content"
     android:background="@drawable/reaction_rounded_rect_shape"
     android:clipChildren="false"
     android:gravity="center"
-    android:minWidth="@dimen/chat_reaction_min_width"
     tools:parentTag="android.widget.LinearLayout">
 
     
@@ -21,7 +20,6 @@
         style="@style/Widget.Vector.TextView.Caption"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_marginStart="6dp"
         android:ellipsize="middle"
         android:gravity="center"
         android:maxEms="10"
@@ -35,7 +33,6 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_marginStart="2dp"
-        android:layout_marginEnd="6dp"
         android:gravity="center"
         android:maxLines="1"
         android:textColor="?vctr_content_secondary"

From d8d6358d15cc2aefd454d6f21ec76b9bd64fc008 Mon Sep 17 00:00:00 2001
From: Adam Brown 
Date: Fri, 11 Feb 2022 11:57:45 +0000
Subject: [PATCH 353/581] adding support for the homeserver display name and
 avatar capabilities - MSC3283
 https://github.com/matrix-org/synapse/pull/11933 - includes session database
 migration

---
 .../homeserver/HomeServerCapabilities.kt      | 13 ++++++++
 .../database/RealmSessionStoreMigration.kt    |  2 ++
 .../mapper/HomeServerCapabilitiesMapper.kt    |  3 ++
 .../database/migration/MigrateSessionTo025.kt | 31 +++++++++++++++++++
 .../model/HomeServerCapabilitiesEntity.kt     |  3 ++
 .../homeserver/GetCapabilitiesResult.kt       | 25 +++++++++++----
 .../GetHomeServerCapabilitiesTask.kt          | 12 +++++--
 7 files changed, 81 insertions(+), 8 deletions(-)
 create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo025.kt

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt
index 3ed6a7ebb2d..2256dfb8f0c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt
@@ -21,6 +21,18 @@ data class HomeServerCapabilities(
          * True if it is possible to change the password of the account.
          */
         val canChangePassword: Boolean = true,
+        /**
+         * True if it is possible to change the display name of the account.
+         */
+        val canChangeDisplayName: Boolean = true,
+        /**
+         * True if it is possible to change the avatar of the account.
+         */
+        val canChangeAvatar: Boolean = true,
+        /**
+         * True if it is possible to change the 3pid associations of the account.
+         */
+        val canChange3pid: Boolean = true,
         /**
          * Max size of file which can be uploaded to the homeserver in bytes. [MAX_UPLOAD_FILE_SIZE_UNKNOWN] if unknown or not retrieved yet
          */
@@ -76,6 +88,7 @@ data class HomeServerCapabilities(
             }
         }
     }
+
     fun isFeatureSupported(feature: String, byRoomVersion: String): Boolean {
         if (roomVersions?.capabilities == null) return false
         val info = roomVersions.capabilities[feature] ?: return false
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
index f4a8ae2c67c..4bf352c06c2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
@@ -42,6 +42,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo021
 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo022
 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo023
 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo024
+import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo025
 import org.matrix.android.sdk.internal.util.Normalizer
 import timber.log.Timber
 import javax.inject.Inject
@@ -85,5 +86,6 @@ internal class RealmSessionStoreMigration @Inject constructor(
         if (oldVersion < 22) MigrateSessionTo022(realm).perform()
         if (oldVersion < 23) MigrateSessionTo023(realm).perform()
         if (oldVersion < 24) MigrateSessionTo024(realm).perform()
+        if (oldVersion < 25) MigrateSessionTo025(realm).perform()
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt
index 8b6d263f8c7..7869506015e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt
@@ -35,6 +35,9 @@ internal object HomeServerCapabilitiesMapper {
     fun map(entity: HomeServerCapabilitiesEntity): HomeServerCapabilities {
         return HomeServerCapabilities(
                 canChangePassword = entity.canChangePassword,
+                canChangeDisplayName = entity.canChangeDisplayName,
+                canChangeAvatar = entity.canChangeAvatar,
+                canChange3pid = entity.canChange3pid,
                 maxUploadFileSize = entity.maxUploadFileSize,
                 lastVersionIdentityServerSupported = entity.lastVersionIdentityServerSupported,
                 defaultIdentityServerUrl = entity.defaultIdentityServerUrl,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo025.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo025.kt
new file mode 100644
index 00000000000..35267d2a166
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo025.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.database.migration
+
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+
+class MigrateSessionTo025(realm: DynamicRealm) : RealmMigrator(realm, 25) {
+
+    override fun doMigrate(realm: DynamicRealm) {
+        realm.schema.get("HomeServerCapabilitiesEntity")
+                ?.addField(HomeServerCapabilitiesEntityFields.CAN_CHANGE_DISPLAY_NAME, Boolean::class.java)
+                ?.addField(HomeServerCapabilitiesEntityFields.CAN_CHANGE_AVATAR, Boolean::class.java)
+                ?.addField(HomeServerCapabilitiesEntityFields.CAN_CHANGE3PID, Boolean::class.java)
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt
index 980449ddfbe..08ecd5995ec 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt
@@ -21,6 +21,9 @@ import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
 
 internal open class HomeServerCapabilitiesEntity(
         var canChangePassword: Boolean = true,
+        var canChangeDisplayName: Boolean = true,
+        var canChangeAvatar: Boolean = true,
+        var canChange3pid: Boolean = true,
         var roomVersionsJson: String? = null,
         var maxUploadFileSize: Long = HomeServerCapabilities.MAX_UPLOAD_FILE_SIZE_UNKNOWN,
         var lastVersionIdentityServerSupported: Boolean = false,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetCapabilitiesResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetCapabilitiesResult.kt
index 9e68309dad1..830a58cd128 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetCapabilitiesResult.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetCapabilitiesResult.kt
@@ -18,7 +18,6 @@ package org.matrix.android.sdk.internal.session.homeserver
 
 import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
-import org.matrix.android.sdk.api.extensions.orTrue
 import org.matrix.android.sdk.api.util.JsonDict
 
 /**
@@ -42,6 +41,25 @@ internal data class Capabilities(
         @Json(name = "m.change_password")
         val changePassword: BooleanCapability? = null,
 
+        /**
+         * Capability to indicate if the user can change their display name.
+         * True if the user can change their display name, false otherwise.
+         */
+        @Json(name = "m.set_displayname")
+        val changeDisplayName: BooleanCapability? = null,
+
+        /**
+         * Capability to indicate if the user can change their avatar.
+         * True if the user can change their avatar, false otherwise.
+         */
+        @Json(name = "m.set_avatar_url")
+        val changeAvatar: BooleanCapability? = null,
+        /**
+         * Capability to indicate if the user can change add, remove or change 3PID associations.
+         * True if the user can change their 3PID associations, false otherwise.
+         */
+        @Json(name = "m.3pid_changes")
+        val change3pid: BooleanCapability? = null,
         /**
          * This capability describes the default and available room versions a server supports, and at what level of stability.
          * Clients should make use of this capability to determine if users need to be encouraged to upgrade their rooms.
@@ -88,8 +106,3 @@ internal data class RoomVersions(
         @Json(name = "org.matrix.msc3244.room_capabilities")
         val roomCapabilities: JsonDict? = null
 )
-
-// The spec says: If not present, the client should assume that password changes are possible via the API
-internal fun GetCapabilitiesResult.canChangePassword(): Boolean {
-    return capabilities?.changePassword?.enabled.orTrue()
-}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt
index 612b98f8638..e822cbdcdbf 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt
@@ -20,6 +20,7 @@ import com.zhuinden.monarchy.Monarchy
 import org.matrix.android.sdk.api.MatrixPatterns.getDomain
 import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
 import org.matrix.android.sdk.api.auth.wellknown.WellknownResult
+import org.matrix.android.sdk.api.extensions.orTrue
 import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
 import org.matrix.android.sdk.internal.auth.version.Versions
 import org.matrix.android.sdk.internal.auth.version.isLoginAndRegistrationSupportedBySdk
@@ -108,9 +109,16 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
             val homeServerCapabilitiesEntity = HomeServerCapabilitiesEntity.getOrCreate(realm)
 
             if (getCapabilitiesResult != null) {
-                homeServerCapabilitiesEntity.canChangePassword = getCapabilitiesResult.canChangePassword()
+                val capabilities = getCapabilitiesResult.capabilities
 
-                homeServerCapabilitiesEntity.roomVersionsJson = getCapabilitiesResult.capabilities?.roomVersions?.let {
+                // The spec says: If not present, the client should assume that
+                // password, display name, avatar changes and 3pid changes are possible via the API
+                homeServerCapabilitiesEntity.canChangePassword = capabilities?.changePassword?.enabled.orTrue()
+                homeServerCapabilitiesEntity.canChangeDisplayName = capabilities?.changeDisplayName?.enabled.orTrue()
+                homeServerCapabilitiesEntity.canChangeAvatar = capabilities?.changeAvatar?.enabled.orTrue()
+                homeServerCapabilitiesEntity.canChange3pid = capabilities?.change3pid?.enabled.orTrue()
+
+                homeServerCapabilitiesEntity.roomVersionsJson = capabilities?.roomVersions?.let {
                     MoshiProvider.providesMoshi().adapter(RoomVersions::class.java).toJson(it)
                 }
             }

From 83cc88060a540a6b077ad78f84b99db2e16d7040 Mon Sep 17 00:00:00 2001
From: Adam Brown 
Date: Fri, 11 Feb 2022 12:01:10 +0000
Subject: [PATCH 354/581] adding changelog entry

---
 changelog.d/5207.sdk | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 changelog.d/5207.sdk

diff --git a/changelog.d/5207.sdk b/changelog.d/5207.sdk
new file mode 100644
index 00000000000..3ba3e06fb76
--- /dev/null
+++ b/changelog.d/5207.sdk
@@ -0,0 +1 @@
+Adds support for MSC3283, additional homeserver capabilities
\ No newline at end of file

From edd95464ea351a023d40c7b1d756e5a92f6844c6 Mon Sep 17 00:00:00 2001
From: Michael Kaye <1917473+michaelkaye@users.noreply.github.com>
Date: Fri, 11 Feb 2022 14:31:28 +0000
Subject: [PATCH 355/581] changelog.d

---
 changelog.d/5209.misc | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 changelog.d/5209.misc

diff --git a/changelog.d/5209.misc b/changelog.d/5209.misc
new file mode 100644
index 00000000000..a238da9d223
--- /dev/null
+++ b/changelog.d/5209.misc
@@ -0,0 +1 @@
+Reduce verbosity of debug logging,

From c224a4b8137f8d7d027f9adac61be4df8e025fe5 Mon Sep 17 00:00:00 2001
From: Michael Kaye <1917473+michaelkaye@users.noreply.github.com>
Date: Fri, 11 Feb 2022 15:28:16 +0000
Subject: [PATCH 356/581] Make sanity_test.yml use same configuration as
 integration_test.yml

This makes it easier to reason about emulator failures.
---
 .github/workflows/sanity_test.yml | 17 +++++++++--------
 1 file changed, 9 insertions(+), 8 deletions(-)

diff --git a/.github/workflows/sanity_test.yml b/.github/workflows/sanity_test.yml
index 213c43b716f..93e4686fe74 100644
--- a/.github/workflows/sanity_test.yml
+++ b/.github/workflows/sanity_test.yml
@@ -8,7 +8,7 @@ on:
 # Enrich gradle.properties for CI/CD
 env:
   CI_GRADLE_ARG_PROPERTIES: >
-    -Porg.gradle.jvmargs=-Xmx2g
+    -Porg.gradle.jvmargs=-Xmx4g
     -Porg.gradle.parallel=false
 
 jobs:
@@ -18,7 +18,7 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        api-level: [ 29 ]
+        api-level: [ 28 ]
     steps:
       - uses: actions/checkout@v2
         with:
@@ -57,9 +57,11 @@ jobs:
       - name: Run sanity tests on API ${{ matrix.api-level }}
         uses: reactivecircus/android-emulator-runner@v2
         with:
-          emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
           api-level: ${{ matrix.api-level }}
-          profile: 24 # Pixel 5
+          arch: x86
+          profile: Nexus 5X
+          force-avd-creation: false
+          emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
           emulator-build: 7425822  # workaround to emulator bug: https://github.com/ReactiveCircus/android-emulator-runner/issues/160
           script: |
             adb root
@@ -67,12 +69,11 @@ jobs:
             touch emulator.log
             chmod 777 emulator.log
             adb logcat >> emulator.log &
-            ./gradlew $CI_GRADLE_ARG_PROPERTIES -PallWarningsAsErrors=false connectedGplayDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=im.vector.app.ui.UiAllScreensSanityTest || adb pull storage/emulated/0/Pictures/failure_screenshots && exit 1
-      - name: Upload Failing Test Report Log
+            ./gradlew $CI_GRADLE_ARG_PROPERTIES -PallWarningsAsErrors=false connectedGplayDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=im.vector.app.ui.UiAllScreensSanityTest || adb pull storage/emulated/0/Pictures/failure_screenshots
+      - name: Upload Test Report Log
         uses: actions/upload-artifact@v2
-        if: failure()
         with:
           name: sanity-error-results
           path: |
             emulator.log
-            failure_screenshots/
\ No newline at end of file
+            failure_screenshots/

From d173a8d15faede5e4566e220fe7988ec10d6d47e Mon Sep 17 00:00:00 2001
From: Michael Kaye <1917473+michaelkaye@users.noreply.github.com>
Date: Fri, 11 Feb 2022 15:32:33 +0000
Subject: [PATCH 357/581] changelog.d

---
 changelog.d/5210.misc | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 changelog.d/5210.misc

diff --git a/changelog.d/5210.misc b/changelog.d/5210.misc
new file mode 100644
index 00000000000..0b68e8b23a9
--- /dev/null
+++ b/changelog.d/5210.misc
@@ -0,0 +1 @@
+Standardise emulator versions of GHA integration tests.

From 91a419553742131c876b528273cc410d9233998e Mon Sep 17 00:00:00 2001
From: Sylvia van Os 
Date: Sat, 12 Feb 2022 15:18:53 +0100
Subject: [PATCH 358/581] Increase max heap size for build

The F-Droid build ran OOM during R8 shrinking with 3GB, but worked fine with 4GB

Signed-off-by: Sylvia van Os 
---
 gradle.properties | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gradle.properties b/gradle.properties
index 5c99297107f..6de52be6073 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -8,7 +8,7 @@
 # The setting is particularly useful for tweaking memory settings.
 
 # Build Time Optimizations
-org.gradle.jvmargs=-Xmx3g -Xms512M -XX:MaxPermSize=2048m -XX:MaxMetaspaceSize=1g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:+UseParallelGC
+org.gradle.jvmargs=-Xmx4g -Xms512M -XX:MaxPermSize=2048m -XX:MaxMetaspaceSize=1g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:+UseParallelGC
 org.gradle.configureondemand=true
 org.gradle.parallel=true
 org.gradle.vfs.watch=true

From ff1c307ca06b0137fd9b465122e59193ab91d42d Mon Sep 17 00:00:00 2001
From: bmarty 
Date: Mon, 14 Feb 2022 00:03:37 +0000
Subject: [PATCH 359/581] Sync Emojis

---
 .../emoji_picker_datasource_formatted.json    | 37 +++++++++++++------
 .../main/res/raw/emoji_picker_datasource.json |  2 +-
 2 files changed, 26 insertions(+), 13 deletions(-)

diff --git a/tools/emojis/emoji_picker_datasource_formatted.json b/tools/emojis/emoji_picker_datasource_formatted.json
index 341cdc0c544..c1aa5900035 100644
--- a/tools/emojis/emoji_picker_datasource_formatted.json
+++ b/tools/emojis/emoji_picker_datasource_formatted.json
@@ -2475,9 +2475,11 @@
             "b": "1F636-200D-1F32B-FE0F",
             "j": [
                 "absentminded",
-                "face in clouds",
                 "face in the fog",
-                "head in clouds"
+                "head in clouds",
+                "shower",
+                "steam",
+                "dream"
             ]
         },
         "smirking-face": {
@@ -2536,12 +2538,14 @@
             "b": "1F62E-200D-1F4A8",
             "j": [
                 "exhale",
-                "face exhaling",
                 "gasp",
                 "groan",
                 "relief",
                 "whisper",
-                "whistle"
+                "whistle",
+                "relieve",
+                "tired",
+                "sigh"
             ]
         },
         "lying-face": {
@@ -2745,11 +2749,15 @@
             "b": "1F635-200D-1F4AB",
             "j": [
                 "dizzy",
-                "face with spiral eyes",
                 "hypnotized",
                 "spiral",
                 "trouble",
-                "whoa"
+                "whoa",
+                "sick",
+                "ill",
+                "confused",
+                "nauseous",
+                "nausea"
             ]
         },
         "exploding-head": {
@@ -3704,10 +3712,11 @@
             "j": [
                 "burn",
                 "heart",
-                "heart on fire",
                 "love",
                 "lust",
-                "sacred heart"
+                "sacred heart",
+                "passionate",
+                "enthusiastic"
             ]
         },
         "mending-heart": {
@@ -3717,10 +3726,12 @@
                 "healthier",
                 "improving",
                 "mending",
-                "mending heart",
                 "recovering",
                 "recuperating",
-                "well"
+                "well",
+                "broken heart",
+                "bandage",
+                "wounded"
             ]
         },
         "red-heart": {
@@ -4748,7 +4759,8 @@
             "j": [
                 "beard",
                 "man",
-                "man: beard"
+                "man: beard",
+                "facial hair"
             ]
         },
         "woman-beard": {
@@ -4757,7 +4769,8 @@
             "j": [
                 "beard",
                 "woman",
-                "woman: beard"
+                "woman: beard",
+                "facial hair"
             ]
         },
         "man-red-hair": {
diff --git a/vector/src/main/res/raw/emoji_picker_datasource.json b/vector/src/main/res/raw/emoji_picker_datasource.json
index 80131cefac0..f4fa7f812ab 100644
--- a/vector/src/main/res/raw/emoji_picker_datasource.json
+++ b/vector/src/main/res/raw/emoji_picker_datasource.json
@@ -1 +1 @@
-{"compressed":true,"categories":[{"id":"smileys_&_emotion","name":"Smileys & Emotion","emojis":["grinning-face","grinning-face-with-big-eyes","grinning-face-with-smiling-eyes","beaming-face-with-smiling-eyes","grinning-squinting-face","grinning-face-with-sweat","rolling-on-the-floor-laughing","face-with-tears-of-joy","slightly-smiling-face","upsidedown-face","melting-face","winking-face","smiling-face-with-smiling-eyes","smiling-face-with-halo","smiling-face-with-hearts","smiling-face-with-hearteyes","starstruck","face-blowing-a-kiss","kissing-face","smiling-face","kissing-face-with-closed-eyes","kissing-face-with-smiling-eyes","smiling-face-with-tear","face-savoring-food","face-with-tongue","winking-face-with-tongue","zany-face","squinting-face-with-tongue","moneymouth-face","smiling-face-with-open-hands","face-with-hand-over-mouth","face-with-open-eyes-and-hand-over-mouth","face-with-peeking-eye","shushing-face","thinking-face","saluting-face","zippermouth-face","face-with-raised-eyebrow","neutral-face","expressionless-face","face-without-mouth","dotted-line-face","face-in-clouds","smirking-face","unamused-face","face-with-rolling-eyes","grimacing-face","face-exhaling","lying-face","relieved-face","pensive-face","sleepy-face","drooling-face","sleeping-face","face-with-medical-mask","face-with-thermometer","face-with-headbandage","nauseated-face","face-vomiting","sneezing-face","hot-face","cold-face","woozy-face","face-with-crossedout-eyes","face-with-spiral-eyes","exploding-head","cowboy-hat-face","partying-face","disguised-face","smiling-face-with-sunglasses","nerd-face","face-with-monocle","confused-face","face-with-diagonal-mouth","worried-face","slightly-frowning-face","frowning-face","face-with-open-mouth","hushed-face","astonished-face","flushed-face","pleading-face","face-holding-back-tears","frowning-face-with-open-mouth","anguished-face","fearful-face","anxious-face-with-sweat","sad-but-relieved-face","crying-face","loudly-crying-face","face-screaming-in-fear","confounded-face","persevering-face","disappointed-face","downcast-face-with-sweat","weary-face","tired-face","yawning-face","face-with-steam-from-nose","pouting-face","angry-face","face-with-symbols-on-mouth","smiling-face-with-horns","angry-face-with-horns","skull","skull-and-crossbones","pile-of-poo","clown-face","ogre","goblin","ghost","alien","alien-monster","robot","grinning-cat","grinning-cat-with-smiling-eyes","cat-with-tears-of-joy","smiling-cat-with-hearteyes","cat-with-wry-smile","kissing-cat","weary-cat","crying-cat","pouting-cat","seenoevil-monkey","hearnoevil-monkey","speaknoevil-monkey","kiss-mark","love-letter","heart-with-arrow","heart-with-ribbon","sparkling-heart","growing-heart","beating-heart","revolving-hearts","two-hearts","heart-decoration","heart-exclamation","broken-heart","heart-on-fire","mending-heart","red-heart","orange-heart","yellow-heart","green-heart","blue-heart","purple-heart","brown-heart","black-heart","white-heart","hundred-points","anger-symbol","collision","dizzy","sweat-droplets","dashing-away","hole","bomb","speech-balloon","eye-in-speech-bubble","left-speech-bubble","right-anger-bubble","thought-balloon","zzz"]},{"id":"people_&_body","name":"People & Body","emojis":["waving-hand","raised-back-of-hand","hand-with-fingers-splayed","raised-hand","vulcan-salute","rightwards-hand","leftwards-hand","palm-down-hand","palm-up-hand","ok-hand","pinched-fingers","pinching-hand","victory-hand","crossed-fingers","hand-with-index-finger-and-thumb-crossed","loveyou-gesture","sign-of-the-horns","call-me-hand","backhand-index-pointing-left","backhand-index-pointing-right","backhand-index-pointing-up","middle-finger","backhand-index-pointing-down","index-pointing-up","index-pointing-at-the-viewer","thumbs-up","thumbs-down","raised-fist","oncoming-fist","leftfacing-fist","rightfacing-fist","clapping-hands","raising-hands","heart-hands","open-hands","palms-up-together","handshake","folded-hands","writing-hand","nail-polish","selfie","flexed-biceps","mechanical-arm","mechanical-leg","leg","foot","ear","ear-with-hearing-aid","nose","brain","anatomical-heart","lungs","tooth","bone","eyes","eye","tongue","mouth","biting-lip","baby","child","boy","girl","person","person-blond-hair","man","person-beard","man-beard","woman-beard","man-red-hair","man-curly-hair","man-white-hair","man-bald","woman","woman-red-hair","person-red-hair","woman-curly-hair","person-curly-hair","woman-white-hair","person-white-hair","woman-bald","person-bald","woman-blond-hair","man-blond-hair","older-person","old-man","old-woman","person-frowning","man-frowning","woman-frowning","person-pouting","man-pouting","woman-pouting","person-gesturing-no","man-gesturing-no","woman-gesturing-no","person-gesturing-ok","man-gesturing-ok","woman-gesturing-ok","person-tipping-hand","man-tipping-hand","woman-tipping-hand","person-raising-hand","man-raising-hand","woman-raising-hand","deaf-person","deaf-man","deaf-woman","person-bowing","man-bowing","woman-bowing","person-facepalming","man-facepalming","woman-facepalming","person-shrugging","man-shrugging","woman-shrugging","health-worker","man-health-worker","woman-health-worker","student","man-student","woman-student","teacher","man-teacher","woman-teacher","judge","man-judge","woman-judge","farmer","man-farmer","woman-farmer","cook","man-cook","woman-cook","mechanic","man-mechanic","woman-mechanic","factory-worker","man-factory-worker","woman-factory-worker","office-worker","man-office-worker","woman-office-worker","scientist","man-scientist","woman-scientist","technologist","man-technologist","woman-technologist","singer","man-singer","woman-singer","artist","man-artist","woman-artist","pilot","man-pilot","woman-pilot","astronaut","man-astronaut","woman-astronaut","firefighter","man-firefighter","woman-firefighter","police-officer","man-police-officer","woman-police-officer","detective","man-detective","woman-detective","guard","man-guard","woman-guard","ninja","construction-worker","man-construction-worker","woman-construction-worker","person-with-crown","prince","princess","person-wearing-turban","man-wearing-turban","woman-wearing-turban","person-with-skullcap","woman-with-headscarf","person-in-tuxedo","man-in-tuxedo","woman-in-tuxedo","person-with-veil","man-with-veil","woman-with-veil","pregnant-woman","pregnant-man","pregnant-person","breastfeeding","woman-feeding-baby","man-feeding-baby","person-feeding-baby","baby-angel","santa-claus","mrs-claus","mx-claus","superhero","man-superhero","woman-superhero","supervillain","man-supervillain","woman-supervillain","mage","man-mage","woman-mage","fairy","man-fairy","woman-fairy","vampire","man-vampire","woman-vampire","merperson","merman","mermaid","elf","man-elf","woman-elf","genie","man-genie","woman-genie","zombie","man-zombie","woman-zombie","troll","person-getting-massage","man-getting-massage","woman-getting-massage","person-getting-haircut","man-getting-haircut","woman-getting-haircut","person-walking","man-walking","woman-walking","person-standing","man-standing","woman-standing","person-kneeling","man-kneeling","woman-kneeling","person-with-white-cane","man-with-white-cane","woman-with-white-cane","person-in-motorized-wheelchair","man-in-motorized-wheelchair","woman-in-motorized-wheelchair","person-in-manual-wheelchair","man-in-manual-wheelchair","woman-in-manual-wheelchair","person-running","man-running","woman-running","woman-dancing","man-dancing","person-in-suit-levitating","people-with-bunny-ears","men-with-bunny-ears","women-with-bunny-ears","person-in-steamy-room","man-in-steamy-room","woman-in-steamy-room","person-climbing","man-climbing","woman-climbing","person-fencing","horse-racing","skier","snowboarder","person-golfing","man-golfing","woman-golfing","person-surfing","man-surfing","woman-surfing","person-rowing-boat","man-rowing-boat","woman-rowing-boat","person-swimming","man-swimming","woman-swimming","person-bouncing-ball","man-bouncing-ball","woman-bouncing-ball","person-lifting-weights","man-lifting-weights","woman-lifting-weights","person-biking","man-biking","woman-biking","person-mountain-biking","man-mountain-biking","woman-mountain-biking","person-cartwheeling","man-cartwheeling","woman-cartwheeling","people-wrestling","men-wrestling","women-wrestling","person-playing-water-polo","man-playing-water-polo","woman-playing-water-polo","person-playing-handball","man-playing-handball","woman-playing-handball","person-juggling","man-juggling","woman-juggling","person-in-lotus-position","man-in-lotus-position","woman-in-lotus-position","person-taking-bath","person-in-bed","people-holding-hands","women-holding-hands","woman-and-man-holding-hands","men-holding-hands","kiss","kiss-woman-man","kiss-man-man","kiss-woman-woman","couple-with-heart","couple-with-heart-woman-man","couple-with-heart-man-man","couple-with-heart-woman-woman","family","family-man-woman-boy","family-man-woman-girl","family-man-woman-girl-boy","family-man-woman-boy-boy","family-man-woman-girl-girl","family-man-man-boy","family-man-man-girl","family-man-man-girl-boy","family-man-man-boy-boy","family-man-man-girl-girl","family-woman-woman-boy","family-woman-woman-girl","family-woman-woman-girl-boy","family-woman-woman-boy-boy","family-woman-woman-girl-girl","family-man-boy","family-man-boy-boy","family-man-girl","family-man-girl-boy","family-man-girl-girl","family-woman-boy","family-woman-boy-boy","family-woman-girl","family-woman-girl-boy","family-woman-girl-girl","speaking-head","bust-in-silhouette","busts-in-silhouette","people-hugging","footprints"]},{"id":"animals_&_nature","name":"Animals & Nature","emojis":["monkey-face","monkey","gorilla","orangutan","dog-face","dog","guide-dog","service-dog","poodle","wolf","fox","raccoon","cat-face","cat","black-cat","lion","tiger-face","tiger","leopard","horse-face","horse","unicorn","zebra","deer","bison","cow-face","ox","water-buffalo","cow","pig-face","pig","boar","pig-nose","ram","ewe","goat","camel","twohump-camel","llama","giraffe","elephant","mammoth","rhinoceros","hippopotamus","mouse-face","mouse","rat","hamster","rabbit-face","rabbit","chipmunk","beaver","hedgehog","bat","bear","polar-bear","koala","panda","sloth","otter","skunk","kangaroo","badger","paw-prints","turkey","chicken","rooster","hatching-chick","baby-chick","frontfacing-baby-chick","bird","penguin","dove","eagle","duck","swan","owl","dodo","feather","flamingo","peacock","parrot","frog","crocodile","turtle","lizard","snake","dragon-face","dragon","sauropod","trex","spouting-whale","whale","dolphin","seal","fish","tropical-fish","blowfish","shark","octopus","spiral-shell","coral","snail","butterfly","bug","ant","honeybee","beetle","lady-beetle","cricket","cockroach","spider","spider-web","scorpion","mosquito","fly","worm","microbe","bouquet","cherry-blossom","white-flower","lotus","rosette","rose","wilted-flower","hibiscus","sunflower","blossom","tulip","seedling","potted-plant","evergreen-tree","deciduous-tree","palm-tree","cactus","sheaf-of-rice","herb","shamrock","four-leaf-clover","maple-leaf","fallen-leaf","leaf-fluttering-in-wind","empty-nest","nest-with-eggs"]},{"id":"food_&_drink","name":"Food & Drink","emojis":["grapes","melon","watermelon","tangerine","lemon","banana","pineapple","mango","red-apple","green-apple","pear","peach","cherries","strawberry","blueberries","kiwi-fruit","tomato","olive","coconut","avocado","eggplant","potato","carrot","ear-of-corn","hot-pepper","bell-pepper","cucumber","leafy-green","broccoli","garlic","onion","mushroom","peanuts","beans","chestnut","bread","croissant","baguette-bread","flatbread","pretzel","bagel","pancakes","waffle","cheese-wedge","meat-on-bone","poultry-leg","cut-of-meat","bacon","hamburger","french-fries","pizza","hot-dog","sandwich","taco","burrito","tamale","stuffed-flatbread","falafel","egg","cooking","shallow-pan-of-food","pot-of-food","fondue","bowl-with-spoon","green-salad","popcorn","butter","salt","canned-food","bento-box","rice-cracker","rice-ball","cooked-rice","curry-rice","steaming-bowl","spaghetti","roasted-sweet-potato","oden","sushi","fried-shrimp","fish-cake-with-swirl","moon-cake","dango","dumpling","fortune-cookie","takeout-box","crab","lobster","shrimp","squid","oyster","soft-ice-cream","shaved-ice","ice-cream","doughnut","cookie","birthday-cake","shortcake","cupcake","pie","chocolate-bar","candy","lollipop","custard","honey-pot","baby-bottle","glass-of-milk","hot-beverage","teapot","teacup-without-handle","sake","bottle-with-popping-cork","wine-glass","cocktail-glass","tropical-drink","beer-mug","clinking-beer-mugs","clinking-glasses","tumbler-glass","pouring-liquid","cup-with-straw","bubble-tea","beverage-box","mate","ice","chopsticks","fork-and-knife-with-plate","fork-and-knife","spoon","kitchen-knife","jar","amphora"]},{"id":"travel_&_places","name":"Travel & Places","emojis":["globe-showing-europeafrica","globe-showing-americas","globe-showing-asiaaustralia","globe-with-meridians","world-map","map-of-japan","compass","snowcapped-mountain","mountain","volcano","mount-fuji","camping","beach-with-umbrella","desert","desert-island","national-park","stadium","classical-building","building-construction","brick","rock","wood","hut","houses","derelict-house","house","house-with-garden","office-building","japanese-post-office","post-office","hospital","bank","hotel","love-hotel","convenience-store","school","department-store","factory","japanese-castle","castle","wedding","tokyo-tower","statue-of-liberty","church","mosque","hindu-temple","synagogue","shinto-shrine","kaaba","fountain","tent","foggy","night-with-stars","cityscape","sunrise-over-mountains","sunrise","cityscape-at-dusk","sunset","bridge-at-night","hot-springs","carousel-horse","playground-slide","ferris-wheel","roller-coaster","barber-pole","circus-tent","locomotive","railway-car","highspeed-train","bullet-train","train","metro","light-rail","station","tram","monorail","mountain-railway","tram-car","bus","oncoming-bus","trolleybus","minibus","ambulance","fire-engine","police-car","oncoming-police-car","taxi","oncoming-taxi","automobile","oncoming-automobile","sport-utility-vehicle","pickup-truck","delivery-truck","articulated-lorry","tractor","racing-car","motorcycle","motor-scooter","manual-wheelchair","motorized-wheelchair","auto-rickshaw","bicycle","kick-scooter","skateboard","roller-skate","bus-stop","motorway","railway-track","oil-drum","fuel-pump","wheel","police-car-light","horizontal-traffic-light","vertical-traffic-light","stop-sign","construction","anchor","ring-buoy","sailboat","canoe","speedboat","passenger-ship","ferry","motor-boat","ship","airplane","small-airplane","airplane-departure","airplane-arrival","parachute","seat","helicopter","suspension-railway","mountain-cableway","aerial-tramway","satellite","rocket","flying-saucer","bellhop-bell","luggage","hourglass-done","hourglass-not-done","watch","alarm-clock","stopwatch","timer-clock","mantelpiece-clock","twelve-oclock","twelvethirty","one-oclock","onethirty","two-oclock","twothirty","three-oclock","threethirty","four-oclock","fourthirty","five-oclock","fivethirty","six-oclock","sixthirty","seven-oclock","seventhirty","eight-oclock","eightthirty","nine-oclock","ninethirty","ten-oclock","tenthirty","eleven-oclock","eleventhirty","new-moon","waxing-crescent-moon","first-quarter-moon","waxing-gibbous-moon","full-moon","waning-gibbous-moon","last-quarter-moon","waning-crescent-moon","crescent-moon","new-moon-face","first-quarter-moon-face","last-quarter-moon-face","thermometer","sun","full-moon-face","sun-with-face","ringed-planet","star","glowing-star","shooting-star","milky-way","cloud","sun-behind-cloud","cloud-with-lightning-and-rain","sun-behind-small-cloud","sun-behind-large-cloud","sun-behind-rain-cloud","cloud-with-rain","cloud-with-snow","cloud-with-lightning","tornado","fog","wind-face","cyclone","rainbow","closed-umbrella","umbrella","umbrella-with-rain-drops","umbrella-on-ground","high-voltage","snowflake","snowman","snowman-without-snow","comet","fire","droplet","water-wave"]},{"id":"activities","name":"Activities","emojis":["jackolantern","christmas-tree","fireworks","sparkler","firecracker","sparkles","balloon","party-popper","confetti-ball","tanabata-tree","pine-decoration","japanese-dolls","carp-streamer","wind-chime","moon-viewing-ceremony","red-envelope","ribbon","wrapped-gift","reminder-ribbon","admission-tickets","ticket","military-medal","trophy","sports-medal","1st-place-medal","2nd-place-medal","3rd-place-medal","soccer-ball","baseball","softball","basketball","volleyball","american-football","rugby-football","tennis","flying-disc","bowling","cricket-game","field-hockey","ice-hockey","lacrosse","ping-pong","badminton","boxing-glove","martial-arts-uniform","goal-net","flag-in-hole","ice-skate","fishing-pole","diving-mask","running-shirt","skis","sled","curling-stone","bullseye","yoyo","kite","pool-8-ball","crystal-ball","magic-wand","nazar-amulet","hamsa","video-game","joystick","slot-machine","game-die","puzzle-piece","teddy-bear","piata","mirror-ball","nesting-dolls","spade-suit","heart-suit","diamond-suit","club-suit","chess-pawn","joker","mahjong-red-dragon","flower-playing-cards","performing-arts","framed-picture","artist-palette","thread","sewing-needle","yarn","knot"]},{"id":"objects","name":"Objects","emojis":["glasses","sunglasses","goggles","lab-coat","safety-vest","necktie","tshirt","jeans","scarf","gloves","coat","socks","dress","kimono","sari","onepiece-swimsuit","briefs","shorts","bikini","womans-clothes","purse","handbag","clutch-bag","shopping-bags","backpack","thong-sandal","mans-shoe","running-shoe","hiking-boot","flat-shoe","highheeled-shoe","womans-sandal","ballet-shoes","womans-boot","crown","womans-hat","top-hat","graduation-cap","billed-cap","military-helmet","rescue-workers-helmet","prayer-beads","lipstick","ring","gem-stone","muted-speaker","speaker-low-volume","speaker-medium-volume","speaker-high-volume","loudspeaker","megaphone","postal-horn","bell","bell-with-slash","musical-score","musical-note","musical-notes","studio-microphone","level-slider","control-knobs","microphone","headphone","radio","saxophone","accordion","guitar","musical-keyboard","trumpet","violin","banjo","drum","long-drum","mobile-phone","mobile-phone-with-arrow","telephone","telephone-receiver","pager","fax-machine","battery","low-battery","electric-plug","laptop","desktop-computer","printer","keyboard","computer-mouse","trackball","computer-disk","floppy-disk","optical-disk","dvd","abacus","movie-camera","film-frames","film-projector","clapper-board","television","camera","camera-with-flash","video-camera","videocassette","magnifying-glass-tilted-left","magnifying-glass-tilted-right","candle","light-bulb","flashlight","red-paper-lantern","diya-lamp","notebook-with-decorative-cover","closed-book","open-book","green-book","blue-book","orange-book","books","notebook","ledger","page-with-curl","scroll","page-facing-up","newspaper","rolledup-newspaper","bookmark-tabs","bookmark","label","money-bag","coin","yen-banknote","dollar-banknote","euro-banknote","pound-banknote","money-with-wings","credit-card","receipt","chart-increasing-with-yen","envelope","email","incoming-envelope","envelope-with-arrow","outbox-tray","inbox-tray","package","closed-mailbox-with-raised-flag","closed-mailbox-with-lowered-flag","open-mailbox-with-raised-flag","open-mailbox-with-lowered-flag","postbox","ballot-box-with-ballot","pencil","black-nib","fountain-pen","pen","paintbrush","crayon","memo","briefcase","file-folder","open-file-folder","card-index-dividers","calendar","tearoff-calendar","spiral-notepad","spiral-calendar","card-index","chart-increasing","chart-decreasing","bar-chart","clipboard","pushpin","round-pushpin","paperclip","linked-paperclips","straight-ruler","triangular-ruler","scissors","card-file-box","file-cabinet","wastebasket","locked","unlocked","locked-with-pen","locked-with-key","key","old-key","hammer","axe","pick","hammer-and-pick","hammer-and-wrench","dagger","crossed-swords","water-pistol","boomerang","bow-and-arrow","shield","carpentry-saw","wrench","screwdriver","nut-and-bolt","gear","clamp","balance-scale","white-cane","link","chains","hook","toolbox","magnet","ladder","alembic","test-tube","petri-dish","dna","microscope","telescope","satellite-antenna","syringe","drop-of-blood","pill","adhesive-bandage","crutch","stethoscope","xray","door","elevator","mirror","window","bed","couch-and-lamp","chair","toilet","plunger","shower","bathtub","mouse-trap","razor","lotion-bottle","safety-pin","broom","basket","roll-of-paper","bucket","soap","bubbles","toothbrush","sponge","fire-extinguisher","shopping-cart","cigarette","coffin","headstone","funeral-urn","moai","placard","identification-card"]},{"id":"symbols","name":"Symbols","emojis":["atm-sign","litter-in-bin-sign","potable-water","wheelchair-symbol","mens-room","womens-room","restroom","baby-symbol","water-closet","passport-control","customs","baggage-claim","left-luggage","warning","children-crossing","no-entry","prohibited","no-bicycles","no-smoking","no-littering","nonpotable-water","no-pedestrians","no-mobile-phones","no-one-under-eighteen","radioactive","biohazard","up-arrow","upright-arrow","right-arrow","downright-arrow","down-arrow","downleft-arrow","left-arrow","upleft-arrow","updown-arrow","leftright-arrow","right-arrow-curving-left","left-arrow-curving-right","right-arrow-curving-up","right-arrow-curving-down","clockwise-vertical-arrows","counterclockwise-arrows-button","back-arrow","end-arrow","on-arrow","soon-arrow","top-arrow","place-of-worship","atom-symbol","om","star-of-david","wheel-of-dharma","yin-yang","latin-cross","orthodox-cross","star-and-crescent","peace-symbol","menorah","dotted-sixpointed-star","aries","taurus","gemini","cancer","leo","virgo","libra","scorpio","sagittarius","capricorn","aquarius","pisces","ophiuchus","shuffle-tracks-button","repeat-button","repeat-single-button","play-button","fastforward-button","next-track-button","play-or-pause-button","reverse-button","fast-reverse-button","last-track-button","upwards-button","fast-up-button","downwards-button","fast-down-button","pause-button","stop-button","record-button","eject-button","cinema","dim-button","bright-button","antenna-bars","vibration-mode","mobile-phone-off","female-sign","male-sign","transgender-symbol","multiply","plus","minus","divide","heavy-equals-sign","infinity","double-exclamation-mark","exclamation-question-mark","red-question-mark","white-question-mark","white-exclamation-mark","red-exclamation-mark","wavy-dash","currency-exchange","heavy-dollar-sign","medical-symbol","recycling-symbol","fleurdelis","trident-emblem","name-badge","japanese-symbol-for-beginner","hollow-red-circle","check-mark-button","check-box-with-check","check-mark","cross-mark","cross-mark-button","curly-loop","double-curly-loop","part-alternation-mark","eightspoked-asterisk","eightpointed-star","sparkle","copyright","registered","trade-mark","keycap","keycap","keycap-0","keycap-1","keycap-2","keycap-3","keycap-4","keycap-5","keycap-6","keycap-7","keycap-8","keycap-9","keycap-10","input-latin-uppercase","input-latin-lowercase","input-numbers","input-symbols","input-latin-letters","a-button-blood-type","ab-button-blood-type","b-button-blood-type","cl-button","cool-button","free-button","information","id-button","circled-m","new-button","ng-button","o-button-blood-type","ok-button","p-button","sos-button","up-button","vs-button","japanese-here-button","japanese-service-charge-button","japanese-monthly-amount-button","japanese-not-free-of-charge-button","japanese-reserved-button","japanese-bargain-button","japanese-discount-button","japanese-free-of-charge-button","japanese-prohibited-button","japanese-acceptable-button","japanese-application-button","japanese-passing-grade-button","japanese-vacancy-button","japanese-congratulations-button","japanese-secret-button","japanese-open-for-business-button","japanese-no-vacancy-button","red-circle","orange-circle","yellow-circle","green-circle","blue-circle","purple-circle","brown-circle","black-circle","white-circle","red-square","orange-square","yellow-square","green-square","blue-square","purple-square","brown-square","black-large-square","white-large-square","black-medium-square","white-medium-square","black-mediumsmall-square","white-mediumsmall-square","black-small-square","white-small-square","large-orange-diamond","large-blue-diamond","small-orange-diamond","small-blue-diamond","red-triangle-pointed-up","red-triangle-pointed-down","diamond-with-a-dot","radio-button","white-square-button","black-square-button"]},{"id":"flags","name":"Flags","emojis":["chequered-flag","triangular-flag","crossed-flags","black-flag","white-flag","rainbow-flag","transgender-flag","pirate-flag","flag-ascension-island","flag-andorra","flag-united-arab-emirates","flag-afghanistan","flag-antigua--barbuda","flag-anguilla","flag-albania","flag-armenia","flag-angola","flag-antarctica","flag-argentina","flag-american-samoa","flag-austria","flag-australia","flag-aruba","flag-land-islands","flag-azerbaijan","flag-bosnia--herzegovina","flag-barbados","flag-bangladesh","flag-belgium","flag-burkina-faso","flag-bulgaria","flag-bahrain","flag-burundi","flag-benin","flag-st-barthlemy","flag-bermuda","flag-brunei","flag-bolivia","flag-caribbean-netherlands","flag-brazil","flag-bahamas","flag-bhutan","flag-bouvet-island","flag-botswana","flag-belarus","flag-belize","flag-canada","flag-cocos-keeling-islands","flag-congo--kinshasa","flag-central-african-republic","flag-congo--brazzaville","flag-switzerland","flag-cte-divoire","flag-cook-islands","flag-chile","flag-cameroon","flag-china","flag-colombia","flag-clipperton-island","flag-costa-rica","flag-cuba","flag-cape-verde","flag-curaao","flag-christmas-island","flag-cyprus","flag-czechia","flag-germany","flag-diego-garcia","flag-djibouti","flag-denmark","flag-dominica","flag-dominican-republic","flag-algeria","flag-ceuta--melilla","flag-ecuador","flag-estonia","flag-egypt","flag-western-sahara","flag-eritrea","flag-spain","flag-ethiopia","flag-european-union","flag-finland","flag-fiji","flag-falkland-islands","flag-micronesia","flag-faroe-islands","flag-france","flag-gabon","flag-united-kingdom","flag-grenada","flag-georgia","flag-french-guiana","flag-guernsey","flag-ghana","flag-gibraltar","flag-greenland","flag-gambia","flag-guinea","flag-guadeloupe","flag-equatorial-guinea","flag-greece","flag-south-georgia--south-sandwich-islands","flag-guatemala","flag-guam","flag-guineabissau","flag-guyana","flag-hong-kong-sar-china","flag-heard--mcdonald-islands","flag-honduras","flag-croatia","flag-haiti","flag-hungary","flag-canary-islands","flag-indonesia","flag-ireland","flag-israel","flag-isle-of-man","flag-india","flag-british-indian-ocean-territory","flag-iraq","flag-iran","flag-iceland","flag-italy","flag-jersey","flag-jamaica","flag-jordan","flag-japan","flag-kenya","flag-kyrgyzstan","flag-cambodia","flag-kiribati","flag-comoros","flag-st-kitts--nevis","flag-north-korea","flag-south-korea","flag-kuwait","flag-cayman-islands","flag-kazakhstan","flag-laos","flag-lebanon","flag-st-lucia","flag-liechtenstein","flag-sri-lanka","flag-liberia","flag-lesotho","flag-lithuania","flag-luxembourg","flag-latvia","flag-libya","flag-morocco","flag-monaco","flag-moldova","flag-montenegro","flag-st-martin","flag-madagascar","flag-marshall-islands","flag-north-macedonia","flag-mali","flag-myanmar-burma","flag-mongolia","flag-macao-sar-china","flag-northern-mariana-islands","flag-martinique","flag-mauritania","flag-montserrat","flag-malta","flag-mauritius","flag-maldives","flag-malawi","flag-mexico","flag-malaysia","flag-mozambique","flag-namibia","flag-new-caledonia","flag-niger","flag-norfolk-island","flag-nigeria","flag-nicaragua","flag-netherlands","flag-norway","flag-nepal","flag-nauru","flag-niue","flag-new-zealand","flag-oman","flag-panama","flag-peru","flag-french-polynesia","flag-papua-new-guinea","flag-philippines","flag-pakistan","flag-poland","flag-st-pierre--miquelon","flag-pitcairn-islands","flag-puerto-rico","flag-palestinian-territories","flag-portugal","flag-palau","flag-paraguay","flag-qatar","flag-runion","flag-romania","flag-serbia","flag-russia","flag-rwanda","flag-saudi-arabia","flag-solomon-islands","flag-seychelles","flag-sudan","flag-sweden","flag-singapore","flag-st-helena","flag-slovenia","flag-svalbard--jan-mayen","flag-slovakia","flag-sierra-leone","flag-san-marino","flag-senegal","flag-somalia","flag-suriname","flag-south-sudan","flag-so-tom--prncipe","flag-el-salvador","flag-sint-maarten","flag-syria","flag-eswatini","flag-tristan-da-cunha","flag-turks--caicos-islands","flag-chad","flag-french-southern-territories","flag-togo","flag-thailand","flag-tajikistan","flag-tokelau","flag-timorleste","flag-turkmenistan","flag-tunisia","flag-tonga","flag-turkey","flag-trinidad--tobago","flag-tuvalu","flag-taiwan","flag-tanzania","flag-ukraine","flag-uganda","flag-us-outlying-islands","flag-united-nations","flag-united-states","flag-uruguay","flag-uzbekistan","flag-vatican-city","flag-st-vincent--grenadines","flag-venezuela","flag-british-virgin-islands","flag-us-virgin-islands","flag-vietnam","flag-vanuatu","flag-wallis--futuna","flag-samoa","flag-kosovo","flag-yemen","flag-mayotte","flag-south-africa","flag-zambia","flag-zimbabwe","flag-england","flag-scotland","flag-wales"]}],"emojis":{"grinning-face":{"a":"Grinning Face","b":"1F600","j":["face","grin","smile","happy","joy",":D"]},"grinning-face-with-big-eyes":{"a":"Grinning Face with Big Eyes","b":"1F603","j":["face","mouth","open","smile","happy","joy","haha",":D",":)","funny"]},"grinning-face-with-smiling-eyes":{"a":"Grinning Face with Smiling Eyes","b":"1F604","j":["eye","face","mouth","open","smile","happy","joy","funny","haha","laugh","like",":D",":)"]},"beaming-face-with-smiling-eyes":{"a":"Beaming Face with Smiling Eyes","b":"1F601","j":["eye","face","grin","smile","happy","joy","kawaii"]},"grinning-squinting-face":{"a":"Grinning Squinting Face","b":"1F606","j":["face","laugh","mouth","satisfied","smile","happy","joy","lol","haha","glad","XD"]},"grinning-face-with-sweat":{"a":"Grinning Face with Sweat","b":"1F605","j":["cold","face","open","smile","sweat","hot","happy","laugh","relief"]},"rolling-on-the-floor-laughing":{"a":"Rolling on the Floor Laughing","b":"1F923","j":["face","floor","laugh","rofl","rolling","rotfl","laughing","lol","haha"]},"face-with-tears-of-joy":{"a":"Face with Tears of Joy","b":"1F602","j":["face","joy","laugh","tear","cry","tears","weep","happy","happytears","haha"]},"slightly-smiling-face":{"a":"Slightly Smiling Face","b":"1F642","j":["face","smile"]},"upsidedown-face":{"a":"Upside-Down Face","b":"1F643","j":["face","upside-down","upside_down_face","flipped","silly","smile"]},"melting-face":{"a":"⊛ Melting Face","b":"1FAE0","j":["disappear","dissolve","liquid","melt"]},"winking-face":{"a":"Winking Face","b":"1F609","j":["face","wink","happy","mischievous","secret",";)","smile","eye"]},"smiling-face-with-smiling-eyes":{"a":"Smiling Face with Smiling Eyes","b":"1F60A","j":["blush","eye","face","smile","happy","flushed","crush","embarrassed","shy","joy"]},"smiling-face-with-halo":{"a":"Smiling Face with Halo","b":"1F607","j":["angel","face","fantasy","halo","innocent","heaven"]},"smiling-face-with-hearts":{"a":"Smiling Face with Hearts","b":"1F970","j":["adore","crush","hearts","in love","face","love","like","affection","valentines","infatuation"]},"smiling-face-with-hearteyes":{"a":"Smiling Face with Heart-Eyes","b":"1F60D","j":["eye","face","love","smile","smiling face with heart-eyes","smiling_face_with_heart_eyes","like","affection","valentines","infatuation","crush","heart"]},"starstruck":{"a":"Star-Struck","b":"1F929","j":["eyes","face","grinning","star","star-struck","starry-eyed","star_struck","smile","starry"]},"face-blowing-a-kiss":{"a":"Face Blowing a Kiss","b":"1F618","j":["face","kiss","love","like","affection","valentines","infatuation"]},"kissing-face":{"a":"Kissing Face","b":"1F617","j":["face","kiss","love","like","3","valentines","infatuation"]},"smiling-face":{"a":"Smiling Face","b":"263A","j":["face","outlined","relaxed","smile","blush","massage","happiness"]},"kissing-face-with-closed-eyes":{"a":"Kissing Face with Closed Eyes","b":"1F61A","j":["closed","eye","face","kiss","love","like","affection","valentines","infatuation"]},"kissing-face-with-smiling-eyes":{"a":"Kissing Face with Smiling Eyes","b":"1F619","j":["eye","face","kiss","smile","affection","valentines","infatuation"]},"smiling-face-with-tear":{"a":"Smiling Face with Tear","b":"1F972","j":["grateful","proud","relieved","smiling","tear","touched","sad","cry","pretend"]},"face-savoring-food":{"a":"Face Savoring Food","b":"1F60B","j":["delicious","face","savouring","smile","yum","happy","joy","tongue","silly","yummy","nom"]},"face-with-tongue":{"a":"Face with Tongue","b":"1F61B","j":["face","tongue","prank","childish","playful","mischievous","smile"]},"winking-face-with-tongue":{"a":"Winking Face with Tongue","b":"1F61C","j":["eye","face","joke","tongue","wink","prank","childish","playful","mischievous","smile"]},"zany-face":{"a":"Zany Face","b":"1F92A","j":["eye","goofy","large","small","face","crazy"]},"squinting-face-with-tongue":{"a":"Squinting Face with Tongue","b":"1F61D","j":["eye","face","horrible","taste","tongue","prank","playful","mischievous","smile"]},"moneymouth-face":{"a":"Money-Mouth Face","b":"1F911","j":["face","money","money-mouth face","mouth","money_mouth_face","rich","dollar"]},"smiling-face-with-open-hands":{"a":"Smiling Face with Open Hands","b":"1F917","j":["face","hug","hugging","open hands","smiling face","hugging_face","smile"]},"face-with-hand-over-mouth":{"a":"Face with Hand over Mouth","b":"1F92D","j":["whoops","shock","sudden realization","surprise","face"]},"face-with-open-eyes-and-hand-over-mouth":{"a":"⊛ Face with Open Eyes and Hand over Mouth","b":"1FAE2","j":["amazement","awe","disbelief","embarrass","scared","surprise"]},"face-with-peeking-eye":{"a":"⊛ Face with Peeking Eye","b":"1FAE3","j":["captivated","peep","stare"]},"shushing-face":{"a":"Shushing Face","b":"1F92B","j":["quiet","shush","face","shhh"]},"thinking-face":{"a":"Thinking Face","b":"1F914","j":["face","thinking","hmmm","think","consider"]},"saluting-face":{"a":"⊛ Saluting Face","b":"1FAE1","j":["ok","salute","sunny","troops","yes"]},"zippermouth-face":{"a":"Zipper-Mouth Face","b":"1F910","j":["face","mouth","zipper","zipper-mouth face","zipper_mouth_face","sealed","secret"]},"face-with-raised-eyebrow":{"a":"Face with Raised Eyebrow","b":"1F928","j":["distrust","skeptic","disapproval","disbelief","mild surprise","scepticism","face","surprise"]},"neutral-face":{"a":"Neutral Face","b":"1F610","j":["deadpan","face","meh","neutral","indifference",":|"]},"expressionless-face":{"a":"Expressionless Face","b":"1F611","j":["expressionless","face","inexpressive","meh","unexpressive","indifferent","-_-","deadpan"]},"face-without-mouth":{"a":"Face Without Mouth","b":"1F636","j":["face","mouth","quiet","silent","hellokitty"]},"dotted-line-face":{"a":"⊛ Dotted Line Face","b":"1FAE5","j":["depressed","disappear","hide","introvert","invisible"]},"face-in-clouds":{"a":"Face in Clouds","b":"1F636-200D-1F32B-FE0F","j":["absentminded","face in clouds","face in the fog","head in clouds"]},"smirking-face":{"a":"Smirking Face","b":"1F60F","j":["face","smirk","smile","mean","prank","smug","sarcasm"]},"unamused-face":{"a":"Unamused Face","b":"1F612","j":["face","unamused","unhappy","indifference","bored","straight face","serious","sarcasm","unimpressed","skeptical","dubious","side_eye"]},"face-with-rolling-eyes":{"a":"Face with Rolling Eyes","b":"1F644","j":["eyeroll","eyes","face","rolling","frustrated"]},"grimacing-face":{"a":"Grimacing Face","b":"1F62C","j":["face","grimace","teeth"]},"face-exhaling":{"a":"Face Exhaling","b":"1F62E-200D-1F4A8","j":["exhale","face exhaling","gasp","groan","relief","whisper","whistle"]},"lying-face":{"a":"Lying Face","b":"1F925","j":["face","lie","pinocchio"]},"relieved-face":{"a":"Relieved Face","b":"1F60C","j":["face","relieved","relaxed","phew","massage","happiness"]},"pensive-face":{"a":"Pensive Face","b":"1F614","j":["dejected","face","pensive","sad","depressed","upset"]},"sleepy-face":{"a":"Sleepy Face","b":"1F62A","j":["face","sleep","tired","rest","nap"]},"drooling-face":{"a":"Drooling Face","b":"1F924","j":["drooling","face"]},"sleeping-face":{"a":"Sleeping Face","b":"1F634","j":["face","sleep","zzz","tired","sleepy","night"]},"face-with-medical-mask":{"a":"Face with Medical Mask","b":"1F637","j":["cold","doctor","face","mask","sick","ill","disease"]},"face-with-thermometer":{"a":"Face with Thermometer","b":"1F912","j":["face","ill","sick","thermometer","temperature","cold","fever"]},"face-with-headbandage":{"a":"Face with Head-Bandage","b":"1F915","j":["bandage","face","face with head-bandage","hurt","injury","face_with_head_bandage","injured","clumsy"]},"nauseated-face":{"a":"Nauseated Face","b":"1F922","j":["face","nauseated","vomit","gross","green","sick","throw up","ill"]},"face-vomiting":{"a":"Face Vomiting","b":"1F92E","j":["puke","sick","vomit","face"]},"sneezing-face":{"a":"Sneezing Face","b":"1F927","j":["face","gesundheit","sneeze","sick","allergy"]},"hot-face":{"a":"Hot Face","b":"1F975","j":["feverish","heat stroke","hot","red-faced","sweating","face","heat","red"]},"cold-face":{"a":"Cold Face","b":"1F976","j":["blue-faced","cold","freezing","frostbite","icicles","face","blue","frozen"]},"woozy-face":{"a":"Woozy Face","b":"1F974","j":["dizzy","intoxicated","tipsy","uneven eyes","wavy mouth","face","wavy"]},"face-with-crossedout-eyes":{"a":"Face with Crossed-out Eyes","b":"1F635","j":["crossed-out eyes","dead","face","face with crossed-out eyes","knocked out","dizzy_face","spent","unconscious","xox","dizzy"]},"face-with-spiral-eyes":{"a":"Face with Spiral Eyes","b":"1F635-200D-1F4AB","j":["dizzy","face with spiral eyes","hypnotized","spiral","trouble","whoa"]},"exploding-head":{"a":"Exploding Head","b":"1F92F","j":["mind blown","shocked","face","mind","blown"]},"cowboy-hat-face":{"a":"Cowboy Hat Face","b":"1F920","j":["cowboy","cowgirl","face","hat"]},"partying-face":{"a":"Partying Face","b":"1F973","j":["celebration","hat","horn","party","face","woohoo"]},"disguised-face":{"a":"Disguised Face","b":"1F978","j":["disguise","face","glasses","incognito","nose","pretent","brows","moustache"]},"smiling-face-with-sunglasses":{"a":"Smiling Face with Sunglasses","b":"1F60E","j":["bright","cool","face","sun","sunglasses","smile","summer","beach","sunglass"]},"nerd-face":{"a":"Nerd Face","b":"1F913","j":["face","geek","nerd","nerdy","dork"]},"face-with-monocle":{"a":"Face with Monocle","b":"1F9D0","j":["stuffy","wealthy","face"]},"confused-face":{"a":"Confused Face","b":"1F615","j":["confused","face","meh","indifference","huh","weird","hmmm",":/"]},"face-with-diagonal-mouth":{"a":"⊛ Face with Diagonal Mouth","b":"1FAE4","j":["disappointed","meh","skeptical","unsure"]},"worried-face":{"a":"Worried Face","b":"1F61F","j":["face","worried","concern","nervous",":("]},"slightly-frowning-face":{"a":"Slightly Frowning Face","b":"1F641","j":["face","frown","frowning","disappointed","sad","upset"]},"frowning-face":{"a":"Frowning Face","b":"2639","j":["face","frown","sad","upset"]},"face-with-open-mouth":{"a":"Face with Open Mouth","b":"1F62E","j":["face","mouth","open","sympathy","surprise","impressed","wow","whoa",":O"]},"hushed-face":{"a":"Hushed Face","b":"1F62F","j":["face","hushed","stunned","surprised","woo","shh"]},"astonished-face":{"a":"Astonished Face","b":"1F632","j":["astonished","face","shocked","totally","xox","surprised","poisoned"]},"flushed-face":{"a":"Flushed Face","b":"1F633","j":["dazed","face","flushed","blush","shy","flattered"]},"pleading-face":{"a":"Pleading Face","b":"1F97A","j":["begging","mercy","puppy eyes","face"]},"face-holding-back-tears":{"a":"⊛ Face Holding Back Tears","b":"1F979","j":["angry","cry","proud","resist","sad"]},"frowning-face-with-open-mouth":{"a":"Frowning Face with Open Mouth","b":"1F626","j":["face","frown","mouth","open","aw","what"]},"anguished-face":{"a":"Anguished Face","b":"1F627","j":["anguished","face","stunned","nervous"]},"fearful-face":{"a":"Fearful Face","b":"1F628","j":["face","fear","fearful","scared","terrified","nervous","oops","huh"]},"anxious-face-with-sweat":{"a":"Anxious Face with Sweat","b":"1F630","j":["blue","cold","face","rushed","sweat","nervous"]},"sad-but-relieved-face":{"a":"Sad but Relieved Face","b":"1F625","j":["disappointed","face","relieved","whew","phew","sweat","nervous"]},"crying-face":{"a":"Crying Face","b":"1F622","j":["cry","face","sad","tear","tears","depressed","upset",":'("]},"loudly-crying-face":{"a":"Loudly Crying Face","b":"1F62D","j":["cry","face","sad","sob","tear","tears","upset","depressed"]},"face-screaming-in-fear":{"a":"Face Screaming in Fear","b":"1F631","j":["face","fear","munch","scared","scream","omg"]},"confounded-face":{"a":"Confounded Face","b":"1F616","j":["confounded","face","confused","sick","unwell","oops",":S"]},"persevering-face":{"a":"Persevering Face","b":"1F623","j":["face","persevere","sick","no","upset","oops"]},"disappointed-face":{"a":"Disappointed Face","b":"1F61E","j":["disappointed","face","sad","upset","depressed",":("]},"downcast-face-with-sweat":{"a":"Downcast Face with Sweat","b":"1F613","j":["cold","face","sweat","hot","sad","tired","exercise"]},"weary-face":{"a":"Weary Face","b":"1F629","j":["face","tired","weary","sleepy","sad","frustrated","upset"]},"tired-face":{"a":"Tired Face","b":"1F62B","j":["face","tired","sick","whine","upset","frustrated"]},"yawning-face":{"a":"Yawning Face","b":"1F971","j":["bored","tired","yawn","sleepy"]},"face-with-steam-from-nose":{"a":"Face with Steam From Nose","b":"1F624","j":["face","triumph","won","gas","phew","proud","pride"]},"pouting-face":{"a":"Pouting Face","b":"1F621","j":["angry","face","mad","pouting","rage","red","hate","despise"]},"angry-face":{"a":"Angry Face","b":"1F620","j":["anger","angry","face","mad","annoyed","frustrated"]},"face-with-symbols-on-mouth":{"a":"Face with Symbols on Mouth","b":"1F92C","j":["swearing","cursing","face","cussing","profanity","expletive"]},"smiling-face-with-horns":{"a":"Smiling Face with Horns","b":"1F608","j":["face","fairy tale","fantasy","horns","smile","devil"]},"angry-face-with-horns":{"a":"Angry Face with Horns","b":"1F47F","j":["demon","devil","face","fantasy","imp","angry","horns"]},"skull":{"a":"Skull","b":"1F480","j":["death","face","fairy tale","monster","dead","skeleton","creepy"]},"skull-and-crossbones":{"a":"Skull and Crossbones","b":"2620","j":["crossbones","death","face","monster","skull","poison","danger","deadly","scary","pirate","evil"]},"pile-of-poo":{"a":"Pile of Poo","b":"1F4A9","j":["dung","face","monster","poo","poop","hankey","shitface","fail","turd","shit"]},"clown-face":{"a":"Clown Face","b":"1F921","j":["clown","face"]},"ogre":{"a":"Ogre","b":"1F479","j":["creature","face","fairy tale","fantasy","monster","troll","red","mask","halloween","scary","creepy","devil","demon","japanese"]},"goblin":{"a":"Goblin","b":"1F47A","j":["creature","face","fairy tale","fantasy","monster","red","evil","mask","scary","creepy","japanese"]},"ghost":{"a":"Ghost","b":"1F47B","j":["creature","face","fairy tale","fantasy","monster","halloween","spooky","scary"]},"alien":{"a":"Alien","b":"1F47D","j":["creature","extraterrestrial","face","fantasy","ufo","UFO","paul","weird","outer_space"]},"alien-monster":{"a":"Alien Monster","b":"1F47E","j":["alien","creature","extraterrestrial","face","monster","ufo","game","arcade","play"]},"robot":{"a":"Robot","b":"1F916","j":["face","monster","computer","machine","bot"]},"grinning-cat":{"a":"Grinning Cat","b":"1F63A","j":["cat","face","grinning","mouth","open","smile","animal","cats","happy"]},"grinning-cat-with-smiling-eyes":{"a":"Grinning Cat with Smiling Eyes","b":"1F638","j":["cat","eye","face","grin","smile","animal","cats"]},"cat-with-tears-of-joy":{"a":"Cat with Tears of Joy","b":"1F639","j":["cat","face","joy","tear","animal","cats","haha","happy","tears"]},"smiling-cat-with-hearteyes":{"a":"Smiling Cat with Heart-Eyes","b":"1F63B","j":["cat","eye","face","heart","love","smile","smiling cat with heart-eyes","smiling_cat_with_heart_eyes","animal","like","affection","cats","valentines"]},"cat-with-wry-smile":{"a":"Cat with Wry Smile","b":"1F63C","j":["cat","face","ironic","smile","wry","animal","cats","smirk"]},"kissing-cat":{"a":"Kissing Cat","b":"1F63D","j":["cat","eye","face","kiss","animal","cats"]},"weary-cat":{"a":"Weary Cat","b":"1F640","j":["cat","face","oh","surprised","weary","animal","cats","munch","scared","scream"]},"crying-cat":{"a":"Crying Cat","b":"1F63F","j":["cat","cry","face","sad","tear","animal","tears","weep","cats","upset"]},"pouting-cat":{"a":"Pouting Cat","b":"1F63E","j":["cat","face","pouting","animal","cats"]},"seenoevil-monkey":{"a":"See-No-Evil Monkey","b":"1F648","j":["evil","face","forbidden","monkey","see","see-no-evil monkey","see_no_evil_monkey","animal","nature","haha"]},"hearnoevil-monkey":{"a":"Hear-No-Evil Monkey","b":"1F649","j":["evil","face","forbidden","hear","hear-no-evil monkey","monkey","hear_no_evil_monkey","animal","nature"]},"speaknoevil-monkey":{"a":"Speak-No-Evil Monkey","b":"1F64A","j":["evil","face","forbidden","monkey","speak","speak-no-evil monkey","speak_no_evil_monkey","animal","nature","omg"]},"kiss-mark":{"a":"Kiss Mark","b":"1F48B","j":["kiss","lips","face","love","like","affection","valentines"]},"love-letter":{"a":"Love Letter","b":"1F48C","j":["heart","letter","love","mail","email","like","affection","envelope","valentines"]},"heart-with-arrow":{"a":"Heart with Arrow","b":"1F498","j":["arrow","cupid","love","like","heart","affection","valentines"]},"heart-with-ribbon":{"a":"Heart with Ribbon","b":"1F49D","j":["ribbon","valentine","love","valentines"]},"sparkling-heart":{"a":"Sparkling Heart","b":"1F496","j":["excited","sparkle","love","like","affection","valentines"]},"growing-heart":{"a":"Growing Heart","b":"1F497","j":["excited","growing","nervous","pulse","like","love","affection","valentines","pink"]},"beating-heart":{"a":"Beating Heart","b":"1F493","j":["beating","heartbeat","pulsating","love","like","affection","valentines","pink","heart"]},"revolving-hearts":{"a":"Revolving Hearts","b":"1F49E","j":["revolving","love","like","affection","valentines"]},"two-hearts":{"a":"Two Hearts","b":"1F495","j":["love","like","affection","valentines","heart"]},"heart-decoration":{"a":"Heart Decoration","b":"1F49F","j":["heart","purple-square","love","like"]},"heart-exclamation":{"a":"Heart Exclamation","b":"2763","j":["exclamation","mark","punctuation","decoration","love"]},"broken-heart":{"a":"Broken Heart","b":"1F494","j":["break","broken","sad","sorry","heart","heartbreak"]},"heart-on-fire":{"a":"Heart on Fire","b":"2764-FE0F-200D-1F525","j":["burn","heart","heart on fire","love","lust","sacred heart"]},"mending-heart":{"a":"Mending Heart","b":"2764-FE0F-200D-1FA79","j":["healthier","improving","mending","mending heart","recovering","recuperating","well"]},"red-heart":{"a":"Red Heart","b":"2764","j":["heart","love","like","valentines"]},"orange-heart":{"a":"Orange Heart","b":"1F9E1","j":["orange","love","like","affection","valentines"]},"yellow-heart":{"a":"Yellow Heart","b":"1F49B","j":["yellow","love","like","affection","valentines"]},"green-heart":{"a":"Green Heart","b":"1F49A","j":["green","love","like","affection","valentines"]},"blue-heart":{"a":"Blue Heart","b":"1F499","j":["blue","love","like","affection","valentines"]},"purple-heart":{"a":"Purple Heart","b":"1F49C","j":["purple","love","like","affection","valentines"]},"brown-heart":{"a":"Brown Heart","b":"1F90E","j":["brown","heart","coffee"]},"black-heart":{"a":"Black Heart","b":"1F5A4","j":["black","evil","wicked"]},"white-heart":{"a":"White Heart","b":"1F90D","j":["heart","white","pure"]},"hundred-points":{"a":"Hundred Points","b":"1F4AF","j":["100","full","hundred","score","perfect","numbers","century","exam","quiz","test","pass"]},"anger-symbol":{"a":"Anger Symbol","b":"1F4A2","j":["angry","comic","mad"]},"collision":{"a":"Collision","b":"1F4A5","j":["boom","comic","bomb","explode","explosion","blown"]},"dizzy":{"a":"Dizzy","b":"1F4AB","j":["comic","star","sparkle","shoot","magic"]},"sweat-droplets":{"a":"Sweat Droplets","b":"1F4A6","j":["comic","splashing","sweat","water","drip","oops"]},"dashing-away":{"a":"Dashing Away","b":"1F4A8","j":["comic","dash","running","wind","air","fast","shoo","fart","smoke","puff"]},"hole":{"a":"Hole","b":"1F573","j":["embarrassing"]},"bomb":{"a":"Bomb","b":"1F4A3","j":["comic","boom","explode","explosion","terrorism"]},"speech-balloon":{"a":"Speech Balloon","b":"1F4AC","j":["balloon","bubble","comic","dialog","speech","words","message","talk","chatting"]},"eye-in-speech-bubble":{"a":"Eye in Speech Bubble","b":"1F441-FE0F-200D-1F5E8-FE0F","j":["eye","speech bubble","witness","info"]},"left-speech-bubble":{"a":"Left Speech Bubble","b":"1F5E8","j":["dialog","speech","words","message","talk","chatting"]},"right-anger-bubble":{"a":"Right Anger Bubble","b":"1F5EF","j":["angry","balloon","bubble","mad","caption","speech","thinking"]},"thought-balloon":{"a":"Thought Balloon","b":"1F4AD","j":["balloon","bubble","comic","thought","cloud","speech","thinking","dream"]},"zzz":{"a":"Zzz","b":"1F4A4","j":["comic","sleep","sleepy","tired","dream"]},"waving-hand":{"a":"Waving Hand","b":"1F44B","j":["hand","wave","waving","hands","gesture","goodbye","solong","farewell","hello","hi","palm"]},"raised-back-of-hand":{"a":"Raised Back of Hand","b":"1F91A","j":["backhand","raised","fingers"]},"hand-with-fingers-splayed":{"a":"Hand with Fingers Splayed","b":"1F590","j":["finger","hand","splayed","fingers","palm"]},"raised-hand":{"a":"Raised Hand","b":"270B","j":["hand","high 5","high five","fingers","stop","highfive","palm","ban"]},"vulcan-salute":{"a":"Vulcan Salute","b":"1F596","j":["finger","hand","spock","vulcan","fingers","star trek"]},"rightwards-hand":{"a":"⊛ Rightwards Hand","b":"1FAF1","j":["hand","right","rightward"]},"leftwards-hand":{"a":"⊛ Leftwards Hand","b":"1FAF2","j":["hand","left","leftward"]},"palm-down-hand":{"a":"⊛ Palm Down Hand","b":"1FAF3","j":["dismiss","drop","shoo"]},"palm-up-hand":{"a":"⊛ Palm Up Hand","b":"1FAF4","j":["beckon","catch","come","offer"]},"ok-hand":{"a":"Ok Hand","b":"1F44C","j":["hand","OK","fingers","limbs","perfect","ok","okay"]},"pinched-fingers":{"a":"Pinched Fingers","b":"1F90C","j":["fingers","hand gesture","interrogation","pinched","sarcastic","size","tiny","small"]},"pinching-hand":{"a":"Pinching Hand","b":"1F90F","j":["small amount","tiny","small","size"]},"victory-hand":{"a":"Victory Hand","b":"270C","j":["hand","v","victory","fingers","ohyeah","peace","two"]},"crossed-fingers":{"a":"Crossed Fingers","b":"1F91E","j":["cross","finger","hand","luck","good","lucky"]},"hand-with-index-finger-and-thumb-crossed":{"a":"⊛ Hand with Index Finger and Thumb Crossed","b":"1FAF0","j":["expensive","heart","love","money","snap"]},"loveyou-gesture":{"a":"Love-You Gesture","b":"1F91F","j":["hand","ILY","love-you gesture","love_you_gesture","fingers","gesture"]},"sign-of-the-horns":{"a":"Sign of the Horns","b":"1F918","j":["finger","hand","horns","rock-on","fingers","evil_eye","sign_of_horns","rock_on"]},"call-me-hand":{"a":"Call Me Hand","b":"1F919","j":["call","hand","hands","gesture","shaka"]},"backhand-index-pointing-left":{"a":"Backhand Index Pointing Left","b":"1F448","j":["backhand","finger","hand","index","point","direction","fingers","left"]},"backhand-index-pointing-right":{"a":"Backhand Index Pointing Right","b":"1F449","j":["backhand","finger","hand","index","point","fingers","direction","right"]},"backhand-index-pointing-up":{"a":"Backhand Index Pointing Up","b":"1F446","j":["backhand","finger","hand","point","up","fingers","direction"]},"middle-finger":{"a":"Middle Finger","b":"1F595","j":["finger","hand","fingers","rude","middle","flipping"]},"backhand-index-pointing-down":{"a":"Backhand Index Pointing Down","b":"1F447","j":["backhand","down","finger","hand","point","fingers","direction"]},"index-pointing-up":{"a":"Index Pointing Up","b":"261D","j":["finger","hand","index","point","up","fingers","direction"]},"index-pointing-at-the-viewer":{"a":"⊛ Index Pointing at the Viewer","b":"1FAF5","j":["point","you"]},"thumbs-up":{"a":"Thumbs Up","b":"1F44D","j":["+1","hand","thumb","up","thumbsup","yes","awesome","good","agree","accept","cool","like"]},"thumbs-down":{"a":"Thumbs Down","b":"1F44E","j":["-1","down","hand","thumb","thumbsdown","no","dislike"]},"raised-fist":{"a":"Raised Fist","b":"270A","j":["clenched","fist","hand","punch","fingers","grasp"]},"oncoming-fist":{"a":"Oncoming Fist","b":"1F44A","j":["clenched","fist","hand","punch","angry","violence","hit","attack"]},"leftfacing-fist":{"a":"Left-Facing Fist","b":"1F91B","j":["fist","left-facing fist","leftwards","left_facing_fist","hand","fistbump"]},"rightfacing-fist":{"a":"Right-Facing Fist","b":"1F91C","j":["fist","right-facing fist","rightwards","right_facing_fist","hand","fistbump"]},"clapping-hands":{"a":"Clapping Hands","b":"1F44F","j":["clap","hand","hands","praise","applause","congrats","yay"]},"raising-hands":{"a":"Raising Hands","b":"1F64C","j":["celebration","gesture","hand","hooray","raised","yea","hands"]},"heart-hands":{"a":"⊛ Heart Hands","b":"1FAF6","j":["love"]},"open-hands":{"a":"Open Hands","b":"1F450","j":["hand","open","fingers","butterfly","hands"]},"palms-up-together":{"a":"Palms Up Together","b":"1F932","j":["prayer","cupped hands","hands","gesture","cupped"]},"handshake":{"a":"Handshake","b":"1F91D","j":["agreement","hand","meeting","shake"]},"folded-hands":{"a":"Folded Hands","b":"1F64F","j":["ask","hand","high 5","high five","please","pray","thanks","hope","wish","namaste","highfive"]},"writing-hand":{"a":"Writing Hand","b":"270D","j":["hand","write","lower_left_ballpoint_pen","stationery","compose"]},"nail-polish":{"a":"Nail Polish","b":"1F485","j":["care","cosmetics","manicure","nail","polish","beauty","finger","fashion"]},"selfie":{"a":"Selfie","b":"1F933","j":["camera","phone"]},"flexed-biceps":{"a":"Flexed Biceps","b":"1F4AA","j":["biceps","comic","flex","muscle","arm","hand","summer","strong"]},"mechanical-arm":{"a":"Mechanical Arm","b":"1F9BE","j":["accessibility","prosthetic"]},"mechanical-leg":{"a":"Mechanical Leg","b":"1F9BF","j":["accessibility","prosthetic"]},"leg":{"a":"Leg","b":"1F9B5","j":["kick","limb"]},"foot":{"a":"Foot","b":"1F9B6","j":["kick","stomp"]},"ear":{"a":"Ear","b":"1F442","j":["body","face","hear","sound","listen"]},"ear-with-hearing-aid":{"a":"Ear with Hearing Aid","b":"1F9BB","j":["accessibility","hard of hearing"]},"nose":{"a":"Nose","b":"1F443","j":["body","smell","sniff"]},"brain":{"a":"Brain","b":"1F9E0","j":["intelligent","smart"]},"anatomical-heart":{"a":"Anatomical Heart","b":"1FAC0","j":["anatomical","cardiology","heart","organ","pulse","health","heartbeat"]},"lungs":{"a":"Lungs","b":"1FAC1","j":["breath","exhalation","inhalation","organ","respiration","breathe"]},"tooth":{"a":"Tooth","b":"1F9B7","j":["dentist","teeth"]},"bone":{"a":"Bone","b":"1F9B4","j":["skeleton"]},"eyes":{"a":"Eyes","b":"1F440","j":["eye","face","look","watch","stalk","peek","see"]},"eye":{"a":"Eye","b":"1F441","j":["body","face","look","see","watch","stare"]},"tongue":{"a":"Tongue","b":"1F445","j":["body","mouth","playful"]},"mouth":{"a":"Mouth","b":"1F444","j":["lips","kiss"]},"biting-lip":{"a":"⊛ Biting Lip","b":"1FAE6","j":["anxious","fear","flirting","nervous","uncomfortable","worried"]},"baby":{"a":"Baby","b":"1F476","j":["young","child","boy","girl","toddler"]},"child":{"a":"Child","b":"1F9D2","j":["gender-neutral","unspecified gender","young"]},"boy":{"a":"Boy","b":"1F466","j":["young","man","male","guy","teenager"]},"girl":{"a":"Girl","b":"1F467","j":["Virgo","young","zodiac","female","woman","teenager"]},"person":{"a":"Person","b":"1F9D1","j":["adult","gender-neutral","unspecified gender"]},"person-blond-hair":{"a":"Person: Blond Hair","b":"1F471","j":["blond","blond-haired person","hair","person: blond hair","hairstyle"]},"man":{"a":"Man","b":"1F468","j":["adult","mustache","father","dad","guy","classy","sir","moustache"]},"person-beard":{"a":"Person: Beard","b":"1F9D4","j":["beard","person","person: beard","bewhiskered","man_beard"]},"man-beard":{"a":"Man: Beard","b":"1F9D4-200D-2642-FE0F","j":["beard","man","man: beard"]},"woman-beard":{"a":"Woman: Beard","b":"1F9D4-200D-2640-FE0F","j":["beard","woman","woman: beard"]},"man-red-hair":{"a":"Man: Red Hair","b":"1F468-200D-1F9B0","j":["adult","man","red hair","hairstyle"]},"man-curly-hair":{"a":"Man: Curly Hair","b":"1F468-200D-1F9B1","j":["adult","curly hair","man","hairstyle"]},"man-white-hair":{"a":"Man: White Hair","b":"1F468-200D-1F9B3","j":["adult","man","white hair","old","elder"]},"man-bald":{"a":"Man: Bald","b":"1F468-200D-1F9B2","j":["adult","bald","man","hairless"]},"woman":{"a":"Woman","b":"1F469","j":["adult","female","girls","lady"]},"woman-red-hair":{"a":"Woman: Red Hair","b":"1F469-200D-1F9B0","j":["adult","red hair","woman","hairstyle"]},"person-red-hair":{"a":"Person: Red Hair","b":"1F9D1-200D-1F9B0","j":["adult","gender-neutral","person","red hair","unspecified gender","hairstyle"]},"woman-curly-hair":{"a":"Woman: Curly Hair","b":"1F469-200D-1F9B1","j":["adult","curly hair","woman","hairstyle"]},"person-curly-hair":{"a":"Person: Curly Hair","b":"1F9D1-200D-1F9B1","j":["adult","curly hair","gender-neutral","person","unspecified gender","hairstyle"]},"woman-white-hair":{"a":"Woman: White Hair","b":"1F469-200D-1F9B3","j":["adult","white hair","woman","old","elder"]},"person-white-hair":{"a":"Person: White Hair","b":"1F9D1-200D-1F9B3","j":["adult","gender-neutral","person","unspecified gender","white hair","elder","old"]},"woman-bald":{"a":"Woman: Bald","b":"1F469-200D-1F9B2","j":["adult","bald","woman","hairless"]},"person-bald":{"a":"Person: Bald","b":"1F9D1-200D-1F9B2","j":["adult","bald","gender-neutral","person","unspecified gender","hairless"]},"woman-blond-hair":{"a":"Woman: Blond Hair","b":"1F471-200D-2640-FE0F","j":["blond-haired woman","blonde","hair","woman","woman: blond hair","female","girl","person"]},"man-blond-hair":{"a":"Man: Blond Hair","b":"1F471-200D-2642-FE0F","j":["blond","blond-haired man","hair","man","man: blond hair","male","boy","blonde","guy","person"]},"older-person":{"a":"Older Person","b":"1F9D3","j":["adult","gender-neutral","old","unspecified gender","human","elder","senior"]},"old-man":{"a":"Old Man","b":"1F474","j":["adult","man","old","human","male","men","elder","senior"]},"old-woman":{"a":"Old Woman","b":"1F475","j":["adult","old","woman","human","female","women","lady","elder","senior"]},"person-frowning":{"a":"Person Frowning","b":"1F64D","j":["frown","gesture","worried"]},"man-frowning":{"a":"Man Frowning","b":"1F64D-200D-2642-FE0F","j":["frowning","gesture","man","male","boy","sad","depressed","discouraged","unhappy"]},"woman-frowning":{"a":"Woman Frowning","b":"1F64D-200D-2640-FE0F","j":["frowning","gesture","woman","female","girl","sad","depressed","discouraged","unhappy"]},"person-pouting":{"a":"Person Pouting","b":"1F64E","j":["gesture","pouting","upset"]},"man-pouting":{"a":"Man Pouting","b":"1F64E-200D-2642-FE0F","j":["gesture","man","pouting","male","boy"]},"woman-pouting":{"a":"Woman Pouting","b":"1F64E-200D-2640-FE0F","j":["gesture","pouting","woman","female","girl"]},"person-gesturing-no":{"a":"Person Gesturing No","b":"1F645","j":["forbidden","gesture","hand","person gesturing NO","prohibited","decline"]},"man-gesturing-no":{"a":"Man Gesturing No","b":"1F645-200D-2642-FE0F","j":["forbidden","gesture","hand","man","man gesturing NO","prohibited","male","boy","nope"]},"woman-gesturing-no":{"a":"Woman Gesturing No","b":"1F645-200D-2640-FE0F","j":["forbidden","gesture","hand","prohibited","woman","woman gesturing NO","female","girl","nope"]},"person-gesturing-ok":{"a":"Person Gesturing Ok","b":"1F646","j":["gesture","hand","OK","person gesturing OK","agree"]},"man-gesturing-ok":{"a":"Man Gesturing Ok","b":"1F646-200D-2642-FE0F","j":["gesture","hand","man","man gesturing OK","OK","men","boy","male","blue","human"]},"woman-gesturing-ok":{"a":"Woman Gesturing Ok","b":"1F646-200D-2640-FE0F","j":["gesture","hand","OK","woman","woman gesturing OK","women","girl","female","pink","human"]},"person-tipping-hand":{"a":"Person Tipping Hand","b":"1F481","j":["hand","help","information","sassy","tipping"]},"man-tipping-hand":{"a":"Man Tipping Hand","b":"1F481-200D-2642-FE0F","j":["man","sassy","tipping hand","male","boy","human","information"]},"woman-tipping-hand":{"a":"Woman Tipping Hand","b":"1F481-200D-2640-FE0F","j":["sassy","tipping hand","woman","female","girl","human","information"]},"person-raising-hand":{"a":"Person Raising Hand","b":"1F64B","j":["gesture","hand","happy","raised","question"]},"man-raising-hand":{"a":"Man Raising Hand","b":"1F64B-200D-2642-FE0F","j":["gesture","man","raising hand","male","boy"]},"woman-raising-hand":{"a":"Woman Raising Hand","b":"1F64B-200D-2640-FE0F","j":["gesture","raising hand","woman","female","girl"]},"deaf-person":{"a":"Deaf Person","b":"1F9CF","j":["accessibility","deaf","ear","hear"]},"deaf-man":{"a":"Deaf Man","b":"1F9CF-200D-2642-FE0F","j":["deaf","man","accessibility"]},"deaf-woman":{"a":"Deaf Woman","b":"1F9CF-200D-2640-FE0F","j":["deaf","woman","accessibility"]},"person-bowing":{"a":"Person Bowing","b":"1F647","j":["apology","bow","gesture","sorry","respectiful"]},"man-bowing":{"a":"Man Bowing","b":"1F647-200D-2642-FE0F","j":["apology","bowing","favor","gesture","man","sorry","male","boy"]},"woman-bowing":{"a":"Woman Bowing","b":"1F647-200D-2640-FE0F","j":["apology","bowing","favor","gesture","sorry","woman","female","girl"]},"person-facepalming":{"a":"Person Facepalming","b":"1F926","j":["disbelief","exasperation","face","palm","disappointed"]},"man-facepalming":{"a":"Man Facepalming","b":"1F926-200D-2642-FE0F","j":["disbelief","exasperation","facepalm","man","male","boy"]},"woman-facepalming":{"a":"Woman Facepalming","b":"1F926-200D-2640-FE0F","j":["disbelief","exasperation","facepalm","woman","female","girl"]},"person-shrugging":{"a":"Person Shrugging","b":"1F937","j":["doubt","ignorance","indifference","shrug","regardless"]},"man-shrugging":{"a":"Man Shrugging","b":"1F937-200D-2642-FE0F","j":["doubt","ignorance","indifference","man","shrug","male","boy","confused","indifferent"]},"woman-shrugging":{"a":"Woman Shrugging","b":"1F937-200D-2640-FE0F","j":["doubt","ignorance","indifference","shrug","woman","female","girl","confused","indifferent"]},"health-worker":{"a":"Health Worker","b":"1F9D1-200D-2695-FE0F","j":["doctor","healthcare","nurse","therapist","hospital"]},"man-health-worker":{"a":"Man Health Worker","b":"1F468-200D-2695-FE0F","j":["doctor","healthcare","man","nurse","therapist","human"]},"woman-health-worker":{"a":"Woman Health Worker","b":"1F469-200D-2695-FE0F","j":["doctor","healthcare","nurse","therapist","woman","human"]},"student":{"a":"Student","b":"1F9D1-200D-1F393","j":["graduate","learn"]},"man-student":{"a":"Man Student","b":"1F468-200D-1F393","j":["graduate","man","student","human"]},"woman-student":{"a":"Woman Student","b":"1F469-200D-1F393","j":["graduate","student","woman","human"]},"teacher":{"a":"Teacher","b":"1F9D1-200D-1F3EB","j":["instructor","professor"]},"man-teacher":{"a":"Man Teacher","b":"1F468-200D-1F3EB","j":["instructor","man","professor","teacher","human"]},"woman-teacher":{"a":"Woman Teacher","b":"1F469-200D-1F3EB","j":["instructor","professor","teacher","woman","human"]},"judge":{"a":"Judge","b":"1F9D1-200D-2696-FE0F","j":["justice","scales","law"]},"man-judge":{"a":"Man Judge","b":"1F468-200D-2696-FE0F","j":["judge","justice","man","scales","court","human"]},"woman-judge":{"a":"Woman Judge","b":"1F469-200D-2696-FE0F","j":["judge","justice","scales","woman","court","human"]},"farmer":{"a":"Farmer","b":"1F9D1-200D-1F33E","j":["gardener","rancher","crops"]},"man-farmer":{"a":"Man Farmer","b":"1F468-200D-1F33E","j":["farmer","gardener","man","rancher","human"]},"woman-farmer":{"a":"Woman Farmer","b":"1F469-200D-1F33E","j":["farmer","gardener","rancher","woman","human"]},"cook":{"a":"Cook","b":"1F9D1-200D-1F373","j":["chef","food","kitchen","culinary"]},"man-cook":{"a":"Man Cook","b":"1F468-200D-1F373","j":["chef","cook","man","human"]},"woman-cook":{"a":"Woman Cook","b":"1F469-200D-1F373","j":["chef","cook","woman","human"]},"mechanic":{"a":"Mechanic","b":"1F9D1-200D-1F527","j":["electrician","plumber","tradesperson","worker","technician"]},"man-mechanic":{"a":"Man Mechanic","b":"1F468-200D-1F527","j":["electrician","man","mechanic","plumber","tradesperson","human","wrench"]},"woman-mechanic":{"a":"Woman Mechanic","b":"1F469-200D-1F527","j":["electrician","mechanic","plumber","tradesperson","woman","human","wrench"]},"factory-worker":{"a":"Factory Worker","b":"1F9D1-200D-1F3ED","j":["assembly","factory","industrial","worker","labor"]},"man-factory-worker":{"a":"Man Factory Worker","b":"1F468-200D-1F3ED","j":["assembly","factory","industrial","man","worker","human"]},"woman-factory-worker":{"a":"Woman Factory Worker","b":"1F469-200D-1F3ED","j":["assembly","factory","industrial","woman","worker","human"]},"office-worker":{"a":"Office Worker","b":"1F9D1-200D-1F4BC","j":["architect","business","manager","white-collar"]},"man-office-worker":{"a":"Man Office Worker","b":"1F468-200D-1F4BC","j":["architect","business","man","manager","white-collar","human"]},"woman-office-worker":{"a":"Woman Office Worker","b":"1F469-200D-1F4BC","j":["architect","business","manager","white-collar","woman","human"]},"scientist":{"a":"Scientist","b":"1F9D1-200D-1F52C","j":["biologist","chemist","engineer","physicist","chemistry"]},"man-scientist":{"a":"Man Scientist","b":"1F468-200D-1F52C","j":["biologist","chemist","engineer","man","physicist","scientist","human"]},"woman-scientist":{"a":"Woman Scientist","b":"1F469-200D-1F52C","j":["biologist","chemist","engineer","physicist","scientist","woman","human"]},"technologist":{"a":"Technologist","b":"1F9D1-200D-1F4BB","j":["coder","developer","inventor","software","computer"]},"man-technologist":{"a":"Man Technologist","b":"1F468-200D-1F4BB","j":["coder","developer","inventor","man","software","technologist","engineer","programmer","human","laptop","computer"]},"woman-technologist":{"a":"Woman Technologist","b":"1F469-200D-1F4BB","j":["coder","developer","inventor","software","technologist","woman","engineer","programmer","human","laptop","computer"]},"singer":{"a":"Singer","b":"1F9D1-200D-1F3A4","j":["actor","entertainer","rock","star","song","artist","performer"]},"man-singer":{"a":"Man Singer","b":"1F468-200D-1F3A4","j":["actor","entertainer","man","rock","singer","star","rockstar","human"]},"woman-singer":{"a":"Woman Singer","b":"1F469-200D-1F3A4","j":["actor","entertainer","rock","singer","star","woman","rockstar","human"]},"artist":{"a":"Artist","b":"1F9D1-200D-1F3A8","j":["palette","painting","draw","creativity"]},"man-artist":{"a":"Man Artist","b":"1F468-200D-1F3A8","j":["artist","man","palette","painter","human"]},"woman-artist":{"a":"Woman Artist","b":"1F469-200D-1F3A8","j":["artist","palette","woman","painter","human"]},"pilot":{"a":"Pilot","b":"1F9D1-200D-2708-FE0F","j":["plane","fly","airplane"]},"man-pilot":{"a":"Man Pilot","b":"1F468-200D-2708-FE0F","j":["man","pilot","plane","aviator","human"]},"woman-pilot":{"a":"Woman Pilot","b":"1F469-200D-2708-FE0F","j":["pilot","plane","woman","aviator","human"]},"astronaut":{"a":"Astronaut","b":"1F9D1-200D-1F680","j":["rocket","outerspace"]},"man-astronaut":{"a":"Man Astronaut","b":"1F468-200D-1F680","j":["astronaut","man","rocket","space","human"]},"woman-astronaut":{"a":"Woman Astronaut","b":"1F469-200D-1F680","j":["astronaut","rocket","woman","space","human"]},"firefighter":{"a":"Firefighter","b":"1F9D1-200D-1F692","j":["firetruck","fire"]},"man-firefighter":{"a":"Man Firefighter","b":"1F468-200D-1F692","j":["firefighter","firetruck","man","fireman","human"]},"woman-firefighter":{"a":"Woman Firefighter","b":"1F469-200D-1F692","j":["firefighter","firetruck","woman","fireman","human"]},"police-officer":{"a":"Police Officer","b":"1F46E","j":["cop","officer","police"]},"man-police-officer":{"a":"Man Police Officer","b":"1F46E-200D-2642-FE0F","j":["cop","man","officer","police","law","legal","enforcement","arrest","911"]},"woman-police-officer":{"a":"Woman Police Officer","b":"1F46E-200D-2640-FE0F","j":["cop","officer","police","woman","law","legal","enforcement","arrest","911","female"]},"detective":{"a":"Detective","b":"1F575","j":["sleuth","spy","human"]},"man-detective":{"a":"Man Detective","b":"1F575-FE0F-200D-2642-FE0F","j":["detective","man","sleuth","spy","crime"]},"woman-detective":{"a":"Woman Detective","b":"1F575-FE0F-200D-2640-FE0F","j":["detective","sleuth","spy","woman","human","female"]},"guard":{"a":"Guard","b":"1F482","j":["protect"]},"man-guard":{"a":"Man Guard","b":"1F482-200D-2642-FE0F","j":["guard","man","uk","gb","british","male","guy","royal"]},"woman-guard":{"a":"Woman Guard","b":"1F482-200D-2640-FE0F","j":["guard","woman","uk","gb","british","female","royal"]},"ninja":{"a":"Ninja","b":"1F977","j":["fighter","hidden","stealth","ninjutsu","skills","japanese"]},"construction-worker":{"a":"Construction Worker","b":"1F477","j":["construction","hat","worker","labor","build"]},"man-construction-worker":{"a":"Man Construction Worker","b":"1F477-200D-2642-FE0F","j":["construction","man","worker","male","human","wip","guy","build","labor"]},"woman-construction-worker":{"a":"Woman Construction Worker","b":"1F477-200D-2640-FE0F","j":["construction","woman","worker","female","human","wip","build","labor"]},"person-with-crown":{"a":"⊛ Person with Crown","b":"1FAC5","j":["monarch","noble","regal","royalty"]},"prince":{"a":"Prince","b":"1F934","j":["boy","man","male","crown","royal","king"]},"princess":{"a":"Princess","b":"1F478","j":["fairy tale","fantasy","girl","woman","female","blond","crown","royal","queen"]},"person-wearing-turban":{"a":"Person Wearing Turban","b":"1F473","j":["turban","headdress"]},"man-wearing-turban":{"a":"Man Wearing Turban","b":"1F473-200D-2642-FE0F","j":["man","turban","male","indian","hinduism","arabs"]},"woman-wearing-turban":{"a":"Woman Wearing Turban","b":"1F473-200D-2640-FE0F","j":["turban","woman","female","indian","hinduism","arabs"]},"person-with-skullcap":{"a":"Person with Skullcap","b":"1F472","j":["cap","gua pi mao","hat","person","skullcap","man_with_skullcap","male","boy","chinese"]},"woman-with-headscarf":{"a":"Woman with Headscarf","b":"1F9D5","j":["headscarf","hijab","mantilla","tichel","bandana","head kerchief","female"]},"person-in-tuxedo":{"a":"Person in Tuxedo","b":"1F935","j":["groom","person","tuxedo","man_in_tuxedo","couple","marriage","wedding"]},"man-in-tuxedo":{"a":"Man in Tuxedo","b":"1F935-200D-2642-FE0F","j":["man","tuxedo","formal","fashion"]},"woman-in-tuxedo":{"a":"Woman in Tuxedo","b":"1F935-200D-2640-FE0F","j":["tuxedo","woman","formal","fashion"]},"person-with-veil":{"a":"Person with Veil","b":"1F470","j":["bride","person","veil","wedding","bride_with_veil","couple","marriage","woman"]},"man-with-veil":{"a":"Man with Veil","b":"1F470-200D-2642-FE0F","j":["man","veil","wedding","marriage"]},"woman-with-veil":{"a":"Woman with Veil","b":"1F470-200D-2640-FE0F","j":["veil","woman","wedding","marriage"]},"pregnant-woman":{"a":"Pregnant Woman","b":"1F930","j":["pregnant","woman","baby"]},"pregnant-man":{"a":"⊛ Pregnant Man","b":"1FAC3","j":["belly","bloated","full","pregnant"]},"pregnant-person":{"a":"⊛ Pregnant Person","b":"1FAC4","j":["belly","bloated","full","pregnant"]},"breastfeeding":{"a":"Breast-Feeding","b":"1F931","j":["baby","breast","breast-feeding","nursing","breast_feeding"]},"woman-feeding-baby":{"a":"Woman Feeding Baby","b":"1F469-200D-1F37C","j":["baby","feeding","nursing","woman","birth","food"]},"man-feeding-baby":{"a":"Man Feeding Baby","b":"1F468-200D-1F37C","j":["baby","feeding","man","nursing","birth","food"]},"person-feeding-baby":{"a":"Person Feeding Baby","b":"1F9D1-200D-1F37C","j":["baby","feeding","nursing","person","birth","food"]},"baby-angel":{"a":"Baby Angel","b":"1F47C","j":["angel","baby","face","fairy tale","fantasy","heaven","wings","halo"]},"santa-claus":{"a":"Santa Claus","b":"1F385","j":["celebration","Christmas","claus","father","santa","festival","man","male","xmas","father christmas"]},"mrs-claus":{"a":"Mrs. Claus","b":"1F936","j":["celebration","Christmas","claus","mother","Mrs.","woman","female","xmas","mother christmas"]},"mx-claus":{"a":"Mx Claus","b":"1F9D1-200D-1F384","j":["Claus, christmas","christmas"]},"superhero":{"a":"Superhero","b":"1F9B8","j":["good","hero","heroine","superpower","marvel"]},"man-superhero":{"a":"Man Superhero","b":"1F9B8-200D-2642-FE0F","j":["good","hero","man","superpower","male","superpowers"]},"woman-superhero":{"a":"Woman Superhero","b":"1F9B8-200D-2640-FE0F","j":["good","hero","heroine","superpower","woman","female","superpowers"]},"supervillain":{"a":"Supervillain","b":"1F9B9","j":["criminal","evil","superpower","villain","marvel"]},"man-supervillain":{"a":"Man Supervillain","b":"1F9B9-200D-2642-FE0F","j":["criminal","evil","man","superpower","villain","male","bad","hero","superpowers"]},"woman-supervillain":{"a":"Woman Supervillain","b":"1F9B9-200D-2640-FE0F","j":["criminal","evil","superpower","villain","woman","female","bad","heroine","superpowers"]},"mage":{"a":"Mage","b":"1F9D9","j":["sorcerer","sorceress","witch","wizard","magic"]},"man-mage":{"a":"Man Mage","b":"1F9D9-200D-2642-FE0F","j":["sorcerer","wizard","man","male","mage"]},"woman-mage":{"a":"Woman Mage","b":"1F9D9-200D-2640-FE0F","j":["sorceress","witch","woman","female","mage"]},"fairy":{"a":"Fairy","b":"1F9DA","j":["Oberon","Puck","Titania","wings","magical"]},"man-fairy":{"a":"Man Fairy","b":"1F9DA-200D-2642-FE0F","j":["Oberon","Puck","man","male"]},"woman-fairy":{"a":"Woman Fairy","b":"1F9DA-200D-2640-FE0F","j":["Titania","woman","female"]},"vampire":{"a":"Vampire","b":"1F9DB","j":["Dracula","undead","blood","twilight"]},"man-vampire":{"a":"Man Vampire","b":"1F9DB-200D-2642-FE0F","j":["Dracula","undead","man","male","dracula"]},"woman-vampire":{"a":"Woman Vampire","b":"1F9DB-200D-2640-FE0F","j":["undead","woman","female"]},"merperson":{"a":"Merperson","b":"1F9DC","j":["mermaid","merman","merwoman","sea"]},"merman":{"a":"Merman","b":"1F9DC-200D-2642-FE0F","j":["Triton","man","male","triton"]},"mermaid":{"a":"Mermaid","b":"1F9DC-200D-2640-FE0F","j":["merwoman","woman","female","ariel"]},"elf":{"a":"Elf","b":"1F9DD","j":["magical","LOTR style"]},"man-elf":{"a":"Man Elf","b":"1F9DD-200D-2642-FE0F","j":["magical","man","male"]},"woman-elf":{"a":"Woman Elf","b":"1F9DD-200D-2640-FE0F","j":["magical","woman","female"]},"genie":{"a":"Genie","b":"1F9DE","j":["djinn","(non-human color)","magical","wishes"]},"man-genie":{"a":"Man Genie","b":"1F9DE-200D-2642-FE0F","j":["djinn","man","male"]},"woman-genie":{"a":"Woman Genie","b":"1F9DE-200D-2640-FE0F","j":["djinn","woman","female"]},"zombie":{"a":"Zombie","b":"1F9DF","j":["undead","walking dead","(non-human color)","dead"]},"man-zombie":{"a":"Man Zombie","b":"1F9DF-200D-2642-FE0F","j":["undead","walking dead","man","male","dracula"]},"woman-zombie":{"a":"Woman Zombie","b":"1F9DF-200D-2640-FE0F","j":["undead","walking dead","woman","female"]},"troll":{"a":"⊛ Troll","b":"1F9CC","j":["fairy tale","fantasy","monster"]},"person-getting-massage":{"a":"Person Getting Massage","b":"1F486","j":["face","massage","salon","relax"]},"man-getting-massage":{"a":"Man Getting Massage","b":"1F486-200D-2642-FE0F","j":["face","man","massage","male","boy","head"]},"woman-getting-massage":{"a":"Woman Getting Massage","b":"1F486-200D-2640-FE0F","j":["face","massage","woman","female","girl","head"]},"person-getting-haircut":{"a":"Person Getting Haircut","b":"1F487","j":["barber","beauty","haircut","parlor","hairstyle"]},"man-getting-haircut":{"a":"Man Getting Haircut","b":"1F487-200D-2642-FE0F","j":["haircut","man","male","boy"]},"woman-getting-haircut":{"a":"Woman Getting Haircut","b":"1F487-200D-2640-FE0F","j":["haircut","woman","female","girl"]},"person-walking":{"a":"Person Walking","b":"1F6B6","j":["hike","walk","walking","move"]},"man-walking":{"a":"Man Walking","b":"1F6B6-200D-2642-FE0F","j":["hike","man","walk","human","feet","steps"]},"woman-walking":{"a":"Woman Walking","b":"1F6B6-200D-2640-FE0F","j":["hike","walk","woman","human","feet","steps","female"]},"person-standing":{"a":"Person Standing","b":"1F9CD","j":["stand","standing","still"]},"man-standing":{"a":"Man Standing","b":"1F9CD-200D-2642-FE0F","j":["man","standing","still"]},"woman-standing":{"a":"Woman Standing","b":"1F9CD-200D-2640-FE0F","j":["standing","woman","still"]},"person-kneeling":{"a":"Person Kneeling","b":"1F9CE","j":["kneel","kneeling","pray","respectful"]},"man-kneeling":{"a":"Man Kneeling","b":"1F9CE-200D-2642-FE0F","j":["kneeling","man","pray","respectful"]},"woman-kneeling":{"a":"Woman Kneeling","b":"1F9CE-200D-2640-FE0F","j":["kneeling","woman","respectful","pray"]},"person-with-white-cane":{"a":"Person with White Cane","b":"1F9D1-200D-1F9AF","j":["accessibility","blind","person_with_probing_cane"]},"man-with-white-cane":{"a":"Man with White Cane","b":"1F468-200D-1F9AF","j":["accessibility","blind","man","man_with_probing_cane"]},"woman-with-white-cane":{"a":"Woman with White Cane","b":"1F469-200D-1F9AF","j":["accessibility","blind","woman","woman_with_probing_cane"]},"person-in-motorized-wheelchair":{"a":"Person in Motorized Wheelchair","b":"1F9D1-200D-1F9BC","j":["accessibility","wheelchair","disability"]},"man-in-motorized-wheelchair":{"a":"Man in Motorized Wheelchair","b":"1F468-200D-1F9BC","j":["accessibility","man","wheelchair","disability"]},"woman-in-motorized-wheelchair":{"a":"Woman in Motorized Wheelchair","b":"1F469-200D-1F9BC","j":["accessibility","wheelchair","woman","disability"]},"person-in-manual-wheelchair":{"a":"Person in Manual Wheelchair","b":"1F9D1-200D-1F9BD","j":["accessibility","wheelchair","disability"]},"man-in-manual-wheelchair":{"a":"Man in Manual Wheelchair","b":"1F468-200D-1F9BD","j":["accessibility","man","wheelchair","disability"]},"woman-in-manual-wheelchair":{"a":"Woman in Manual Wheelchair","b":"1F469-200D-1F9BD","j":["accessibility","wheelchair","woman","disability"]},"person-running":{"a":"Person Running","b":"1F3C3","j":["marathon","running","move"]},"man-running":{"a":"Man Running","b":"1F3C3-200D-2642-FE0F","j":["man","marathon","racing","running","walking","exercise","race"]},"woman-running":{"a":"Woman Running","b":"1F3C3-200D-2640-FE0F","j":["marathon","racing","running","woman","walking","exercise","race","female"]},"woman-dancing":{"a":"Woman Dancing","b":"1F483","j":["dance","dancing","woman","female","girl","fun"]},"man-dancing":{"a":"Man Dancing","b":"1F57A","j":["dance","dancing","man","male","boy","fun","dancer"]},"person-in-suit-levitating":{"a":"Person in Suit Levitating","b":"1F574","j":["business","person","suit","man_in_suit_levitating","levitate","hover","jump"]},"people-with-bunny-ears":{"a":"People with Bunny Ears","b":"1F46F","j":["bunny ear","dancer","partying","perform","costume"]},"men-with-bunny-ears":{"a":"Men with Bunny Ears","b":"1F46F-200D-2642-FE0F","j":["bunny ear","dancer","men","partying","male","bunny","boys"]},"women-with-bunny-ears":{"a":"Women with Bunny Ears","b":"1F46F-200D-2640-FE0F","j":["bunny ear","dancer","partying","women","female","bunny","girls"]},"person-in-steamy-room":{"a":"Person in Steamy Room","b":"1F9D6","j":["sauna","steam room","hamam","steambath","relax","spa"]},"man-in-steamy-room":{"a":"Man in Steamy Room","b":"1F9D6-200D-2642-FE0F","j":["sauna","steam room","male","man","spa","steamroom"]},"woman-in-steamy-room":{"a":"Woman in Steamy Room","b":"1F9D6-200D-2640-FE0F","j":["sauna","steam room","female","woman","spa","steamroom"]},"person-climbing":{"a":"Person Climbing","b":"1F9D7","j":["climber","sport"]},"man-climbing":{"a":"Man Climbing","b":"1F9D7-200D-2642-FE0F","j":["climber","sports","hobby","man","male","rock"]},"woman-climbing":{"a":"Woman Climbing","b":"1F9D7-200D-2640-FE0F","j":["climber","sports","hobby","woman","female","rock"]},"person-fencing":{"a":"Person Fencing","b":"1F93A","j":["fencer","fencing","sword","sports"]},"horse-racing":{"a":"Horse Racing","b":"1F3C7","j":["horse","jockey","racehorse","racing","animal","betting","competition","gambling","luck"]},"skier":{"a":"Skier","b":"26F7","j":["ski","snow","sports","winter"]},"snowboarder":{"a":"Snowboarder","b":"1F3C2","j":["ski","snow","snowboard","sports","winter"]},"person-golfing":{"a":"Person Golfing","b":"1F3CC","j":["ball","golf","sports","business"]},"man-golfing":{"a":"Man Golfing","b":"1F3CC-FE0F-200D-2642-FE0F","j":["golf","man","sport"]},"woman-golfing":{"a":"Woman Golfing","b":"1F3CC-FE0F-200D-2640-FE0F","j":["golf","woman","sports","business","female"]},"person-surfing":{"a":"Person Surfing","b":"1F3C4","j":["surfing","sport","sea"]},"man-surfing":{"a":"Man Surfing","b":"1F3C4-200D-2642-FE0F","j":["man","surfing","sports","ocean","sea","summer","beach"]},"woman-surfing":{"a":"Woman Surfing","b":"1F3C4-200D-2640-FE0F","j":["surfing","woman","sports","ocean","sea","summer","beach","female"]},"person-rowing-boat":{"a":"Person Rowing Boat","b":"1F6A3","j":["boat","rowboat","sport","move"]},"man-rowing-boat":{"a":"Man Rowing Boat","b":"1F6A3-200D-2642-FE0F","j":["boat","man","rowboat","sports","hobby","water","ship"]},"woman-rowing-boat":{"a":"Woman Rowing Boat","b":"1F6A3-200D-2640-FE0F","j":["boat","rowboat","woman","sports","hobby","water","ship","female"]},"person-swimming":{"a":"Person Swimming","b":"1F3CA","j":["swim","sport","pool"]},"man-swimming":{"a":"Man Swimming","b":"1F3CA-200D-2642-FE0F","j":["man","swim","sports","exercise","human","athlete","water","summer"]},"woman-swimming":{"a":"Woman Swimming","b":"1F3CA-200D-2640-FE0F","j":["swim","woman","sports","exercise","human","athlete","water","summer","female"]},"person-bouncing-ball":{"a":"Person Bouncing Ball","b":"26F9","j":["ball","sports","human"]},"man-bouncing-ball":{"a":"Man Bouncing Ball","b":"26F9-FE0F-200D-2642-FE0F","j":["ball","man","sport"]},"woman-bouncing-ball":{"a":"Woman Bouncing Ball","b":"26F9-FE0F-200D-2640-FE0F","j":["ball","woman","sports","human","female"]},"person-lifting-weights":{"a":"Person Lifting Weights","b":"1F3CB","j":["lifter","weight","sports","training","exercise"]},"man-lifting-weights":{"a":"Man Lifting Weights","b":"1F3CB-FE0F-200D-2642-FE0F","j":["man","weight lifter","sport"]},"woman-lifting-weights":{"a":"Woman Lifting Weights","b":"1F3CB-FE0F-200D-2640-FE0F","j":["weight lifter","woman","sports","training","exercise","female"]},"person-biking":{"a":"Person Biking","b":"1F6B4","j":["bicycle","biking","cyclist","sport","move"]},"man-biking":{"a":"Man Biking","b":"1F6B4-200D-2642-FE0F","j":["bicycle","biking","cyclist","man","sports","bike","exercise","hipster"]},"woman-biking":{"a":"Woman Biking","b":"1F6B4-200D-2640-FE0F","j":["bicycle","biking","cyclist","woman","sports","bike","exercise","hipster","female"]},"person-mountain-biking":{"a":"Person Mountain Biking","b":"1F6B5","j":["bicycle","bicyclist","bike","cyclist","mountain","sport","move"]},"man-mountain-biking":{"a":"Man Mountain Biking","b":"1F6B5-200D-2642-FE0F","j":["bicycle","bike","cyclist","man","mountain","transportation","sports","human","race"]},"woman-mountain-biking":{"a":"Woman Mountain Biking","b":"1F6B5-200D-2640-FE0F","j":["bicycle","bike","biking","cyclist","mountain","woman","transportation","sports","human","race","female"]},"person-cartwheeling":{"a":"Person Cartwheeling","b":"1F938","j":["cartwheel","gymnastics","sport","gymnastic"]},"man-cartwheeling":{"a":"Man Cartwheeling","b":"1F938-200D-2642-FE0F","j":["cartwheel","gymnastics","man"]},"woman-cartwheeling":{"a":"Woman Cartwheeling","b":"1F938-200D-2640-FE0F","j":["cartwheel","gymnastics","woman"]},"people-wrestling":{"a":"People Wrestling","b":"1F93C","j":["wrestle","wrestler","sport"]},"men-wrestling":{"a":"Men Wrestling","b":"1F93C-200D-2642-FE0F","j":["men","wrestle","sports","wrestlers"]},"women-wrestling":{"a":"Women Wrestling","b":"1F93C-200D-2640-FE0F","j":["women","wrestle","sports","wrestlers"]},"person-playing-water-polo":{"a":"Person Playing Water Polo","b":"1F93D","j":["polo","water","sport"]},"man-playing-water-polo":{"a":"Man Playing Water Polo","b":"1F93D-200D-2642-FE0F","j":["man","water polo","sports","pool"]},"woman-playing-water-polo":{"a":"Woman Playing Water Polo","b":"1F93D-200D-2640-FE0F","j":["water polo","woman","sports","pool"]},"person-playing-handball":{"a":"Person Playing Handball","b":"1F93E","j":["ball","handball","sport"]},"man-playing-handball":{"a":"Man Playing Handball","b":"1F93E-200D-2642-FE0F","j":["handball","man","sports"]},"woman-playing-handball":{"a":"Woman Playing Handball","b":"1F93E-200D-2640-FE0F","j":["handball","woman","sports"]},"person-juggling":{"a":"Person Juggling","b":"1F939","j":["balance","juggle","multitask","skill","performance"]},"man-juggling":{"a":"Man Juggling","b":"1F939-200D-2642-FE0F","j":["juggling","man","multitask","juggle","balance","skill"]},"woman-juggling":{"a":"Woman Juggling","b":"1F939-200D-2640-FE0F","j":["juggling","multitask","woman","juggle","balance","skill"]},"person-in-lotus-position":{"a":"Person in Lotus Position","b":"1F9D8","j":["meditation","yoga","serenity","meditate"]},"man-in-lotus-position":{"a":"Man in Lotus Position","b":"1F9D8-200D-2642-FE0F","j":["meditation","yoga","man","male","serenity","zen","mindfulness"]},"woman-in-lotus-position":{"a":"Woman in Lotus Position","b":"1F9D8-200D-2640-FE0F","j":["meditation","yoga","woman","female","serenity","zen","mindfulness"]},"person-taking-bath":{"a":"Person Taking Bath","b":"1F6C0","j":["bath","bathtub","clean","shower","bathroom"]},"person-in-bed":{"a":"Person in Bed","b":"1F6CC","j":["hotel","sleep","bed","rest"]},"people-holding-hands":{"a":"People Holding Hands","b":"1F9D1-200D-1F91D-200D-1F9D1","j":["couple","hand","hold","holding hands","person","friendship"]},"women-holding-hands":{"a":"Women Holding Hands","b":"1F46D","j":["couple","hand","holding hands","women","pair","friendship","love","like","female","people","human"]},"woman-and-man-holding-hands":{"a":"Woman and Man Holding Hands","b":"1F46B","j":["couple","hand","hold","holding hands","man","woman","pair","people","human","love","date","dating","like","affection","valentines","marriage"]},"men-holding-hands":{"a":"Men Holding Hands","b":"1F46C","j":["couple","Gemini","holding hands","man","men","twins","zodiac","pair","love","like","bromance","friendship","people","human"]},"kiss":{"a":"Kiss","b":"1F48F","j":["couple","pair","valentines","love","like","dating","marriage"]},"kiss-woman-man":{"a":"Kiss: Woman, Man","b":"1F469-200D-2764-FE0F-200D-1F48B-200D-1F468","j":["couple","kiss","man","woman","love"]},"kiss-man-man":{"a":"Kiss: Man, Man","b":"1F468-200D-2764-FE0F-200D-1F48B-200D-1F468","j":["couple","kiss","man","pair","valentines","love","like","dating","marriage"]},"kiss-woman-woman":{"a":"Kiss: Woman, Woman","b":"1F469-200D-2764-FE0F-200D-1F48B-200D-1F469","j":["couple","kiss","woman","pair","valentines","love","like","dating","marriage"]},"couple-with-heart":{"a":"Couple with Heart","b":"1F491","j":["couple","love","pair","like","affection","human","dating","valentines","marriage"]},"couple-with-heart-woman-man":{"a":"Couple with Heart: Woman, Man","b":"1F469-200D-2764-FE0F-200D-1F468","j":["couple","couple with heart","love","man","woman"]},"couple-with-heart-man-man":{"a":"Couple with Heart: Man, Man","b":"1F468-200D-2764-FE0F-200D-1F468","j":["couple","couple with heart","love","man","pair","like","affection","human","dating","valentines","marriage"]},"couple-with-heart-woman-woman":{"a":"Couple with Heart: Woman, Woman","b":"1F469-200D-2764-FE0F-200D-1F469","j":["couple","couple with heart","love","woman","pair","like","affection","human","dating","valentines","marriage"]},"family":{"a":"Family","b":"1F46A","j":["home","parents","child","mom","dad","father","mother","people","human"]},"family-man-woman-boy":{"a":"Family: Man, Woman, Boy","b":"1F468-200D-1F469-200D-1F466","j":["boy","family","man","woman","love"]},"family-man-woman-girl":{"a":"Family: Man, Woman, Girl","b":"1F468-200D-1F469-200D-1F467","j":["family","girl","man","woman","home","parents","people","human","child"]},"family-man-woman-girl-boy":{"a":"Family: Man, Woman, Girl, Boy","b":"1F468-200D-1F469-200D-1F467-200D-1F466","j":["boy","family","girl","man","woman","home","parents","people","human","children"]},"family-man-woman-boy-boy":{"a":"Family: Man, Woman, Boy, Boy","b":"1F468-200D-1F469-200D-1F466-200D-1F466","j":["boy","family","man","woman","home","parents","people","human","children"]},"family-man-woman-girl-girl":{"a":"Family: Man, Woman, Girl, Girl","b":"1F468-200D-1F469-200D-1F467-200D-1F467","j":["family","girl","man","woman","home","parents","people","human","children"]},"family-man-man-boy":{"a":"Family: Man, Man, Boy","b":"1F468-200D-1F468-200D-1F466","j":["boy","family","man","home","parents","people","human","children"]},"family-man-man-girl":{"a":"Family: Man, Man, Girl","b":"1F468-200D-1F468-200D-1F467","j":["family","girl","man","home","parents","people","human","children"]},"family-man-man-girl-boy":{"a":"Family: Man, Man, Girl, Boy","b":"1F468-200D-1F468-200D-1F467-200D-1F466","j":["boy","family","girl","man","home","parents","people","human","children"]},"family-man-man-boy-boy":{"a":"Family: Man, Man, Boy, Boy","b":"1F468-200D-1F468-200D-1F466-200D-1F466","j":["boy","family","man","home","parents","people","human","children"]},"family-man-man-girl-girl":{"a":"Family: Man, Man, Girl, Girl","b":"1F468-200D-1F468-200D-1F467-200D-1F467","j":["family","girl","man","home","parents","people","human","children"]},"family-woman-woman-boy":{"a":"Family: Woman, Woman, Boy","b":"1F469-200D-1F469-200D-1F466","j":["boy","family","woman","home","parents","people","human","children"]},"family-woman-woman-girl":{"a":"Family: Woman, Woman, Girl","b":"1F469-200D-1F469-200D-1F467","j":["family","girl","woman","home","parents","people","human","children"]},"family-woman-woman-girl-boy":{"a":"Family: Woman, Woman, Girl, Boy","b":"1F469-200D-1F469-200D-1F467-200D-1F466","j":["boy","family","girl","woman","home","parents","people","human","children"]},"family-woman-woman-boy-boy":{"a":"Family: Woman, Woman, Boy, Boy","b":"1F469-200D-1F469-200D-1F466-200D-1F466","j":["boy","family","woman","home","parents","people","human","children"]},"family-woman-woman-girl-girl":{"a":"Family: Woman, Woman, Girl, Girl","b":"1F469-200D-1F469-200D-1F467-200D-1F467","j":["family","girl","woman","home","parents","people","human","children"]},"family-man-boy":{"a":"Family: Man, Boy","b":"1F468-200D-1F466","j":["boy","family","man","home","parent","people","human","child"]},"family-man-boy-boy":{"a":"Family: Man, Boy, Boy","b":"1F468-200D-1F466-200D-1F466","j":["boy","family","man","home","parent","people","human","children"]},"family-man-girl":{"a":"Family: Man, Girl","b":"1F468-200D-1F467","j":["family","girl","man","home","parent","people","human","child"]},"family-man-girl-boy":{"a":"Family: Man, Girl, Boy","b":"1F468-200D-1F467-200D-1F466","j":["boy","family","girl","man","home","parent","people","human","children"]},"family-man-girl-girl":{"a":"Family: Man, Girl, Girl","b":"1F468-200D-1F467-200D-1F467","j":["family","girl","man","home","parent","people","human","children"]},"family-woman-boy":{"a":"Family: Woman, Boy","b":"1F469-200D-1F466","j":["boy","family","woman","home","parent","people","human","child"]},"family-woman-boy-boy":{"a":"Family: Woman, Boy, Boy","b":"1F469-200D-1F466-200D-1F466","j":["boy","family","woman","home","parent","people","human","children"]},"family-woman-girl":{"a":"Family: Woman, Girl","b":"1F469-200D-1F467","j":["family","girl","woman","home","parent","people","human","child"]},"family-woman-girl-boy":{"a":"Family: Woman, Girl, Boy","b":"1F469-200D-1F467-200D-1F466","j":["boy","family","girl","woman","home","parent","people","human","children"]},"family-woman-girl-girl":{"a":"Family: Woman, Girl, Girl","b":"1F469-200D-1F467-200D-1F467","j":["family","girl","woman","home","parent","people","human","children"]},"speaking-head":{"a":"Speaking Head","b":"1F5E3","j":["face","head","silhouette","speak","speaking","user","person","human","sing","say","talk"]},"bust-in-silhouette":{"a":"Bust in Silhouette","b":"1F464","j":["bust","silhouette","user","person","human"]},"busts-in-silhouette":{"a":"Busts in Silhouette","b":"1F465","j":["bust","silhouette","user","person","human","group","team"]},"people-hugging":{"a":"People Hugging","b":"1FAC2","j":["goodbye","hello","hug","thanks","care"]},"footprints":{"a":"Footprints","b":"1F463","j":["clothing","footprint","print","feet","tracking","walking","beach"]},"red-hair":{"a":"Red Hair","b":"1F9B0","j":["ginger","red hair","redhead"]},"curly-hair":{"a":"Curly Hair","b":"1F9B1","j":["afro","curly","curly hair","ringlets"]},"white-hair":{"a":"White Hair","b":"1F9B3","j":["gray","hair","old","white"]},"bald":{"a":"Bald","b":"1F9B2","j":["bald","chemotherapy","hairless","no hair","shaven"]},"monkey-face":{"a":"Monkey Face","b":"1F435","j":["face","monkey","animal","nature","circus"]},"monkey":{"a":"Monkey","b":"1F412","j":["animal","nature","banana","circus"]},"gorilla":{"a":"Gorilla","b":"1F98D","j":["animal","nature","circus"]},"orangutan":{"a":"Orangutan","b":"1F9A7","j":["ape","animal"]},"dog-face":{"a":"Dog Face","b":"1F436","j":["dog","face","pet","animal","friend","nature","woof","puppy","faithful"]},"dog":{"a":"Dog","b":"1F415","j":["pet","animal","nature","friend","doge","faithful"]},"guide-dog":{"a":"Guide Dog","b":"1F9AE","j":["accessibility","blind","guide","animal"]},"service-dog":{"a":"Service Dog","b":"1F415-200D-1F9BA","j":["accessibility","assistance","dog","service","blind","animal"]},"poodle":{"a":"Poodle","b":"1F429","j":["dog","animal","101","nature","pet"]},"wolf":{"a":"Wolf","b":"1F43A","j":["face","animal","nature","wild"]},"fox":{"a":"Fox","b":"1F98A","j":["face","animal","nature"]},"raccoon":{"a":"Raccoon","b":"1F99D","j":["curious","sly","animal","nature"]},"cat-face":{"a":"Cat Face","b":"1F431","j":["cat","face","pet","animal","meow","nature","kitten"]},"cat":{"a":"Cat","b":"1F408","j":["pet","animal","meow","cats"]},"black-cat":{"a":"Black Cat","b":"1F408-200D-2B1B","j":["black","cat","unlucky","superstition","luck"]},"lion":{"a":"Lion","b":"1F981","j":["face","Leo","zodiac","animal","nature"]},"tiger-face":{"a":"Tiger Face","b":"1F42F","j":["face","tiger","animal","cat","danger","wild","nature","roar"]},"tiger":{"a":"Tiger","b":"1F405","j":["animal","nature","roar"]},"leopard":{"a":"Leopard","b":"1F406","j":["animal","nature"]},"horse-face":{"a":"Horse Face","b":"1F434","j":["face","horse","animal","brown","nature"]},"horse":{"a":"Horse","b":"1F40E","j":["equestrian","racehorse","racing","animal","gamble","luck"]},"unicorn":{"a":"Unicorn","b":"1F984","j":["face","animal","nature","mystical"]},"zebra":{"a":"Zebra","b":"1F993","j":["stripe","animal","nature","stripes","safari"]},"deer":{"a":"Deer","b":"1F98C","j":["animal","nature","horns","venison"]},"bison":{"a":"Bison","b":"1F9AC","j":["buffalo","herd","wisent","ox"]},"cow-face":{"a":"Cow Face","b":"1F42E","j":["cow","face","beef","ox","animal","nature","moo","milk"]},"ox":{"a":"Ox","b":"1F402","j":["bull","Taurus","zodiac","animal","cow","beef"]},"water-buffalo":{"a":"Water Buffalo","b":"1F403","j":["buffalo","water","animal","nature","ox","cow"]},"cow":{"a":"Cow","b":"1F404","j":["beef","ox","animal","nature","moo","milk"]},"pig-face":{"a":"Pig Face","b":"1F437","j":["face","pig","animal","oink","nature"]},"pig":{"a":"Pig","b":"1F416","j":["sow","animal","nature"]},"boar":{"a":"Boar","b":"1F417","j":["pig","animal","nature"]},"pig-nose":{"a":"Pig Nose","b":"1F43D","j":["face","nose","pig","animal","oink"]},"ram":{"a":"Ram","b":"1F40F","j":["Aries","male","sheep","zodiac","animal","nature"]},"ewe":{"a":"Ewe","b":"1F411","j":["female","sheep","animal","nature","wool","shipit"]},"goat":{"a":"Goat","b":"1F410","j":["Capricorn","zodiac","animal","nature"]},"camel":{"a":"Camel","b":"1F42A","j":["dromedary","hump","animal","hot","desert"]},"twohump-camel":{"a":"Two-Hump Camel","b":"1F42B","j":["bactrian","camel","hump","two-hump camel","two_hump_camel","animal","nature","hot","desert"]},"llama":{"a":"Llama","b":"1F999","j":["alpaca","guanaco","vicuña","wool","animal","nature"]},"giraffe":{"a":"Giraffe","b":"1F992","j":["spots","animal","nature","safari"]},"elephant":{"a":"Elephant","b":"1F418","j":["animal","nature","nose","th","circus"]},"mammoth":{"a":"Mammoth","b":"1F9A3","j":["extinction","large","tusk","woolly","elephant","tusks"]},"rhinoceros":{"a":"Rhinoceros","b":"1F98F","j":["animal","nature","horn"]},"hippopotamus":{"a":"Hippopotamus","b":"1F99B","j":["hippo","animal","nature"]},"mouse-face":{"a":"Mouse Face","b":"1F42D","j":["face","mouse","animal","nature","cheese_wedge","rodent"]},"mouse":{"a":"Mouse","b":"1F401","j":["animal","nature","rodent"]},"rat":{"a":"Rat","b":"1F400","j":["animal","mouse","rodent"]},"hamster":{"a":"Hamster","b":"1F439","j":["face","pet","animal","nature"]},"rabbit-face":{"a":"Rabbit Face","b":"1F430","j":["bunny","face","pet","rabbit","animal","nature","spring","magic"]},"rabbit":{"a":"Rabbit","b":"1F407","j":["bunny","pet","animal","nature","magic","spring"]},"chipmunk":{"a":"Chipmunk","b":"1F43F","j":["squirrel","animal","nature","rodent"]},"beaver":{"a":"Beaver","b":"1F9AB","j":["dam","animal","rodent"]},"hedgehog":{"a":"Hedgehog","b":"1F994","j":["spiny","animal","nature"]},"bat":{"a":"Bat","b":"1F987","j":["vampire","animal","nature","blind"]},"bear":{"a":"Bear","b":"1F43B","j":["face","animal","nature","wild"]},"polar-bear":{"a":"Polar Bear","b":"1F43B-200D-2744-FE0F","j":["arctic","bear","white","animal"]},"koala":{"a":"Koala","b":"1F428","j":["face","marsupial","animal","nature"]},"panda":{"a":"Panda","b":"1F43C","j":["face","animal","nature"]},"sloth":{"a":"Sloth","b":"1F9A5","j":["lazy","slow","animal"]},"otter":{"a":"Otter","b":"1F9A6","j":["fishing","playful","animal"]},"skunk":{"a":"Skunk","b":"1F9A8","j":["stink","animal"]},"kangaroo":{"a":"Kangaroo","b":"1F998","j":["Australia","joey","jump","marsupial","animal","nature","australia","hop"]},"badger":{"a":"Badger","b":"1F9A1","j":["honey badger","pester","animal","nature","honey"]},"paw-prints":{"a":"Paw Prints","b":"1F43E","j":["feet","paw","print","animal","tracking","footprints","dog","cat","pet"]},"turkey":{"a":"Turkey","b":"1F983","j":["bird","animal"]},"chicken":{"a":"Chicken","b":"1F414","j":["bird","animal","cluck","nature"]},"rooster":{"a":"Rooster","b":"1F413","j":["bird","animal","nature","chicken"]},"hatching-chick":{"a":"Hatching Chick","b":"1F423","j":["baby","bird","chick","hatching","animal","chicken","egg","born"]},"baby-chick":{"a":"Baby Chick","b":"1F424","j":["baby","bird","chick","animal","chicken"]},"frontfacing-baby-chick":{"a":"Front-Facing Baby Chick","b":"1F425","j":["baby","bird","chick","front-facing baby chick","front_facing_baby_chick","animal","chicken"]},"bird":{"a":"Bird","b":"1F426","j":["animal","nature","fly","tweet","spring"]},"penguin":{"a":"Penguin","b":"1F427","j":["bird","animal","nature"]},"dove":{"a":"Dove","b":"1F54A","j":["bird","fly","peace","animal"]},"eagle":{"a":"Eagle","b":"1F985","j":["bird","animal","nature"]},"duck":{"a":"Duck","b":"1F986","j":["bird","animal","nature","mallard"]},"swan":{"a":"Swan","b":"1F9A2","j":["bird","cygnet","ugly duckling","animal","nature"]},"owl":{"a":"Owl","b":"1F989","j":["bird","wise","animal","nature","hoot"]},"dodo":{"a":"Dodo","b":"1F9A4","j":["extinction","large","Mauritius","animal","bird"]},"feather":{"a":"Feather","b":"1FAB6","j":["bird","flight","light","plumage","fly"]},"flamingo":{"a":"Flamingo","b":"1F9A9","j":["flamboyant","tropical","animal"]},"peacock":{"a":"Peacock","b":"1F99A","j":["bird","ostentatious","peahen","proud","animal","nature"]},"parrot":{"a":"Parrot","b":"1F99C","j":["bird","pirate","talk","animal","nature"]},"frog":{"a":"Frog","b":"1F438","j":["face","animal","nature","croak","toad"]},"crocodile":{"a":"Crocodile","b":"1F40A","j":["animal","nature","reptile","lizard","alligator"]},"turtle":{"a":"Turtle","b":"1F422","j":["terrapin","tortoise","animal","slow","nature"]},"lizard":{"a":"Lizard","b":"1F98E","j":["reptile","animal","nature"]},"snake":{"a":"Snake","b":"1F40D","j":["bearer","Ophiuchus","serpent","zodiac","animal","evil","nature","hiss","python"]},"dragon-face":{"a":"Dragon Face","b":"1F432","j":["dragon","face","fairy tale","animal","myth","nature","chinese","green"]},"dragon":{"a":"Dragon","b":"1F409","j":["fairy tale","animal","myth","nature","chinese","green"]},"sauropod":{"a":"Sauropod","b":"1F995","j":["brachiosaurus","brontosaurus","diplodocus","animal","nature","dinosaur","extinct"]},"trex":{"a":"T-Rex","b":"1F996","j":["Tyrannosaurus Rex","t_rex","animal","nature","dinosaur","tyrannosaurus","extinct"]},"spouting-whale":{"a":"Spouting Whale","b":"1F433","j":["face","spouting","whale","animal","nature","sea","ocean"]},"whale":{"a":"Whale","b":"1F40B","j":["animal","nature","sea","ocean"]},"dolphin":{"a":"Dolphin","b":"1F42C","j":["flipper","animal","nature","fish","sea","ocean","fins","beach"]},"seal":{"a":"Seal","b":"1F9AD","j":["sea lion","animal","creature","sea"]},"fish":{"a":"Fish","b":"1F41F","j":["Pisces","zodiac","animal","food","nature"]},"tropical-fish":{"a":"Tropical Fish","b":"1F420","j":["fish","tropical","animal","swim","ocean","beach","nemo"]},"blowfish":{"a":"Blowfish","b":"1F421","j":["fish","animal","nature","food","sea","ocean"]},"shark":{"a":"Shark","b":"1F988","j":["fish","animal","nature","sea","ocean","jaws","fins","beach"]},"octopus":{"a":"Octopus","b":"1F419","j":["animal","creature","ocean","sea","nature","beach"]},"spiral-shell":{"a":"Spiral Shell","b":"1F41A","j":["shell","spiral","nature","sea","beach"]},"coral":{"a":"⊛ Coral","b":"1FAB8","j":["ocean","reef"]},"snail":{"a":"Snail","b":"1F40C","j":["slow","animal","shell"]},"butterfly":{"a":"Butterfly","b":"1F98B","j":["insect","pretty","animal","nature","caterpillar"]},"bug":{"a":"Bug","b":"1F41B","j":["insect","animal","nature","worm"]},"ant":{"a":"Ant","b":"1F41C","j":["insect","animal","nature","bug"]},"honeybee":{"a":"Honeybee","b":"1F41D","j":["bee","insect","animal","nature","bug","spring","honey"]},"beetle":{"a":"Beetle","b":"1FAB2","j":["bug","insect"]},"lady-beetle":{"a":"Lady Beetle","b":"1F41E","j":["beetle","insect","ladybird","ladybug","animal","nature"]},"cricket":{"a":"Cricket","b":"1F997","j":["grasshopper","Orthoptera","animal","chirp"]},"cockroach":{"a":"Cockroach","b":"1FAB3","j":["insect","pest","roach","pests"]},"spider":{"a":"Spider","b":"1F577","j":["insect","animal","arachnid"]},"spider-web":{"a":"Spider Web","b":"1F578","j":["spider","web","animal","insect","arachnid","silk"]},"scorpion":{"a":"Scorpion","b":"1F982","j":["scorpio","Scorpio","zodiac","animal","arachnid"]},"mosquito":{"a":"Mosquito","b":"1F99F","j":["disease","fever","malaria","pest","virus","animal","nature","insect"]},"fly":{"a":"Fly","b":"1FAB0","j":["disease","maggot","pest","rotting","insect"]},"worm":{"a":"Worm","b":"1FAB1","j":["annelid","earthworm","parasite","animal"]},"microbe":{"a":"Microbe","b":"1F9A0","j":["amoeba","bacteria","virus","germs"]},"bouquet":{"a":"Bouquet","b":"1F490","j":["flower","flowers","nature","spring"]},"cherry-blossom":{"a":"Cherry Blossom","b":"1F338","j":["blossom","cherry","flower","nature","plant","spring"]},"white-flower":{"a":"White Flower","b":"1F4AE","j":["flower","japanese","spring"]},"lotus":{"a":"⊛ Lotus","b":"1FAB7","j":["Buddhism","flower","Hinduism","India","purity","Vietnam"]},"rosette":{"a":"Rosette","b":"1F3F5","j":["plant","flower","decoration","military"]},"rose":{"a":"Rose","b":"1F339","j":["flower","flowers","valentines","love","spring"]},"wilted-flower":{"a":"Wilted Flower","b":"1F940","j":["flower","wilted","plant","nature"]},"hibiscus":{"a":"Hibiscus","b":"1F33A","j":["flower","plant","vegetable","flowers","beach"]},"sunflower":{"a":"Sunflower","b":"1F33B","j":["flower","sun","nature","plant","fall"]},"blossom":{"a":"Blossom","b":"1F33C","j":["flower","nature","flowers","yellow"]},"tulip":{"a":"Tulip","b":"1F337","j":["flower","flowers","plant","nature","summer","spring"]},"seedling":{"a":"Seedling","b":"1F331","j":["young","plant","nature","grass","lawn","spring"]},"potted-plant":{"a":"Potted Plant","b":"1FAB4","j":["boring","grow","house","nurturing","plant","useless","greenery"]},"evergreen-tree":{"a":"Evergreen Tree","b":"1F332","j":["tree","plant","nature"]},"deciduous-tree":{"a":"Deciduous Tree","b":"1F333","j":["deciduous","shedding","tree","plant","nature"]},"palm-tree":{"a":"Palm Tree","b":"1F334","j":["palm","tree","plant","vegetable","nature","summer","beach","mojito","tropical"]},"cactus":{"a":"Cactus","b":"1F335","j":["plant","vegetable","nature"]},"sheaf-of-rice":{"a":"Sheaf of Rice","b":"1F33E","j":["ear","grain","rice","nature","plant"]},"herb":{"a":"Herb","b":"1F33F","j":["leaf","vegetable","plant","medicine","weed","grass","lawn"]},"shamrock":{"a":"Shamrock","b":"2618","j":["plant","vegetable","nature","irish","clover"]},"four-leaf-clover":{"a":"Four Leaf Clover","b":"1F340","j":["4","clover","four","four-leaf clover","leaf","vegetable","plant","nature","lucky","irish"]},"maple-leaf":{"a":"Maple Leaf","b":"1F341","j":["falling","leaf","maple","nature","plant","vegetable","ca","fall"]},"fallen-leaf":{"a":"Fallen Leaf","b":"1F342","j":["falling","leaf","nature","plant","vegetable","leaves"]},"leaf-fluttering-in-wind":{"a":"Leaf Fluttering in Wind","b":"1F343","j":["blow","flutter","leaf","wind","nature","plant","tree","vegetable","grass","lawn","spring"]},"empty-nest":{"a":"⊛ Empty Nest","b":"1FAB9","j":["nesting"]},"nest-with-eggs":{"a":"⊛ Nest with Eggs","b":"1FABA","j":["nesting"]},"grapes":{"a":"Grapes","b":"1F347","j":["fruit","grape","food","wine"]},"melon":{"a":"Melon","b":"1F348","j":["fruit","nature","food"]},"watermelon":{"a":"Watermelon","b":"1F349","j":["fruit","food","picnic","summer"]},"tangerine":{"a":"Tangerine","b":"1F34A","j":["fruit","orange","food","nature"]},"lemon":{"a":"Lemon","b":"1F34B","j":["citrus","fruit","nature"]},"banana":{"a":"Banana","b":"1F34C","j":["fruit","food","monkey"]},"pineapple":{"a":"Pineapple","b":"1F34D","j":["fruit","nature","food"]},"mango":{"a":"Mango","b":"1F96D","j":["fruit","tropical","food"]},"red-apple":{"a":"Red Apple","b":"1F34E","j":["apple","fruit","red","mac","school"]},"green-apple":{"a":"Green Apple","b":"1F34F","j":["apple","fruit","green","nature"]},"pear":{"a":"Pear","b":"1F350","j":["fruit","nature","food"]},"peach":{"a":"Peach","b":"1F351","j":["fruit","nature","food"]},"cherries":{"a":"Cherries","b":"1F352","j":["berries","cherry","fruit","red","food"]},"strawberry":{"a":"Strawberry","b":"1F353","j":["berry","fruit","food","nature"]},"blueberries":{"a":"Blueberries","b":"1FAD0","j":["berry","bilberry","blue","blueberry","fruit"]},"kiwi-fruit":{"a":"Kiwi Fruit","b":"1F95D","j":["food","fruit","kiwi"]},"tomato":{"a":"Tomato","b":"1F345","j":["fruit","vegetable","nature","food"]},"olive":{"a":"Olive","b":"1FAD2","j":["food","fruit"]},"coconut":{"a":"Coconut","b":"1F965","j":["palm","piña colada","fruit","nature","food"]},"avocado":{"a":"Avocado","b":"1F951","j":["food","fruit"]},"eggplant":{"a":"Eggplant","b":"1F346","j":["aubergine","vegetable","nature","food"]},"potato":{"a":"Potato","b":"1F954","j":["food","vegetable","tuber","vegatable","starch"]},"carrot":{"a":"Carrot","b":"1F955","j":["food","vegetable","orange"]},"ear-of-corn":{"a":"Ear of Corn","b":"1F33D","j":["corn","ear","maize","maze","food","vegetable","plant"]},"hot-pepper":{"a":"Hot Pepper","b":"1F336","j":["hot","pepper","food","spicy","chilli","chili"]},"bell-pepper":{"a":"Bell Pepper","b":"1FAD1","j":["capsicum","pepper","vegetable","fruit","plant"]},"cucumber":{"a":"Cucumber","b":"1F952","j":["food","pickle","vegetable","fruit"]},"leafy-green":{"a":"Leafy Green","b":"1F96C","j":["bok choy","cabbage","kale","lettuce","food","vegetable","plant"]},"broccoli":{"a":"Broccoli","b":"1F966","j":["wild cabbage","fruit","food","vegetable"]},"garlic":{"a":"Garlic","b":"1F9C4","j":["flavoring","food","spice","cook"]},"onion":{"a":"Onion","b":"1F9C5","j":["flavoring","cook","food","spice"]},"mushroom":{"a":"Mushroom","b":"1F344","j":["toadstool","plant","vegetable"]},"peanuts":{"a":"Peanuts","b":"1F95C","j":["food","nut","peanut","vegetable"]},"beans":{"a":"⊛ Beans","b":"1FAD8","j":["food","kidney","legume"]},"chestnut":{"a":"Chestnut","b":"1F330","j":["plant","food","squirrel"]},"bread":{"a":"Bread","b":"1F35E","j":["loaf","food","wheat","breakfast","toast"]},"croissant":{"a":"Croissant","b":"1F950","j":["bread","breakfast","food","french","roll"]},"baguette-bread":{"a":"Baguette Bread","b":"1F956","j":["baguette","bread","food","french"]},"flatbread":{"a":"Flatbread","b":"1FAD3","j":["arepa","lavash","naan","pita","flour","food"]},"pretzel":{"a":"Pretzel","b":"1F968","j":["twisted","convoluted","food","bread"]},"bagel":{"a":"Bagel","b":"1F96F","j":["bakery","breakfast","schmear","food","bread"]},"pancakes":{"a":"Pancakes","b":"1F95E","j":["breakfast","crêpe","food","hotcake","pancake","flapjacks","hotcakes"]},"waffle":{"a":"Waffle","b":"1F9C7","j":["breakfast","indecisive","iron","food"]},"cheese-wedge":{"a":"Cheese Wedge","b":"1F9C0","j":["cheese","food","chadder"]},"meat-on-bone":{"a":"Meat on Bone","b":"1F356","j":["bone","meat","good","food","drumstick"]},"poultry-leg":{"a":"Poultry Leg","b":"1F357","j":["bone","chicken","drumstick","leg","poultry","food","meat","bird","turkey"]},"cut-of-meat":{"a":"Cut of Meat","b":"1F969","j":["chop","lambchop","porkchop","steak","food","cow","meat","cut"]},"bacon":{"a":"Bacon","b":"1F953","j":["breakfast","food","meat","pork","pig"]},"hamburger":{"a":"Hamburger","b":"1F354","j":["burger","meat","fast food","beef","cheeseburger","mcdonalds","burger king"]},"french-fries":{"a":"French Fries","b":"1F35F","j":["french","fries","chips","snack","fast food"]},"pizza":{"a":"Pizza","b":"1F355","j":["cheese","slice","food","party"]},"hot-dog":{"a":"Hot Dog","b":"1F32D","j":["frankfurter","hotdog","sausage","food"]},"sandwich":{"a":"Sandwich","b":"1F96A","j":["bread","food","lunch"]},"taco":{"a":"Taco","b":"1F32E","j":["mexican","food"]},"burrito":{"a":"Burrito","b":"1F32F","j":["mexican","wrap","food"]},"tamale":{"a":"Tamale","b":"1FAD4","j":["mexican","wrapped","food","masa"]},"stuffed-flatbread":{"a":"Stuffed Flatbread","b":"1F959","j":["falafel","flatbread","food","gyro","kebab","stuffed"]},"falafel":{"a":"Falafel","b":"1F9C6","j":["chickpea","meatball","food"]},"egg":{"a":"Egg","b":"1F95A","j":["breakfast","food","chicken"]},"cooking":{"a":"Cooking","b":"1F373","j":["breakfast","egg","frying","pan","food","kitchen"]},"shallow-pan-of-food":{"a":"Shallow Pan of Food","b":"1F958","j":["casserole","food","paella","pan","shallow","cooking"]},"pot-of-food":{"a":"Pot of Food","b":"1F372","j":["pot","stew","food","meat","soup"]},"fondue":{"a":"Fondue","b":"1FAD5","j":["cheese","chocolate","melted","pot","Swiss","food"]},"bowl-with-spoon":{"a":"Bowl with Spoon","b":"1F963","j":["breakfast","cereal","congee","oatmeal","porridge","food"]},"green-salad":{"a":"Green Salad","b":"1F957","j":["food","green","salad","healthy","lettuce"]},"popcorn":{"a":"Popcorn","b":"1F37F","j":["food","movie theater","films","snack"]},"butter":{"a":"Butter","b":"1F9C8","j":["dairy","food","cook"]},"salt":{"a":"Salt","b":"1F9C2","j":["condiment","shaker"]},"canned-food":{"a":"Canned Food","b":"1F96B","j":["can","food","soup"]},"bento-box":{"a":"Bento Box","b":"1F371","j":["bento","box","food","japanese"]},"rice-cracker":{"a":"Rice Cracker","b":"1F358","j":["cracker","rice","food","japanese"]},"rice-ball":{"a":"Rice Ball","b":"1F359","j":["ball","Japanese","rice","food","japanese"]},"cooked-rice":{"a":"Cooked Rice","b":"1F35A","j":["cooked","rice","food","china","asian"]},"curry-rice":{"a":"Curry Rice","b":"1F35B","j":["curry","rice","food","spicy","hot","indian"]},"steaming-bowl":{"a":"Steaming Bowl","b":"1F35C","j":["bowl","noodle","ramen","steaming","food","japanese","chopsticks"]},"spaghetti":{"a":"Spaghetti","b":"1F35D","j":["pasta","food","italian","noodle"]},"roasted-sweet-potato":{"a":"Roasted Sweet Potato","b":"1F360","j":["potato","roasted","sweet","food","nature"]},"oden":{"a":"Oden","b":"1F362","j":["kebab","seafood","skewer","stick","food","japanese"]},"sushi":{"a":"Sushi","b":"1F363","j":["food","fish","japanese","rice"]},"fried-shrimp":{"a":"Fried Shrimp","b":"1F364","j":["fried","prawn","shrimp","tempura","food","animal","appetizer","summer"]},"fish-cake-with-swirl":{"a":"Fish Cake with Swirl","b":"1F365","j":["cake","fish","pastry","swirl","food","japan","sea","beach","narutomaki","pink","kamaboko","surimi","ramen"]},"moon-cake":{"a":"Moon Cake","b":"1F96E","j":["autumn","festival","yuèbǐng","food"]},"dango":{"a":"Dango","b":"1F361","j":["dessert","Japanese","skewer","stick","sweet","food","japanese","barbecue","meat"]},"dumpling":{"a":"Dumpling","b":"1F95F","j":["empanada","gyōza","jiaozi","pierogi","potsticker","food"]},"fortune-cookie":{"a":"Fortune Cookie","b":"1F960","j":["prophecy","food"]},"takeout-box":{"a":"Takeout Box","b":"1F961","j":["oyster pail","food","leftovers"]},"crab":{"a":"Crab","b":"1F980","j":["Cancer","zodiac","animal","crustacean"]},"lobster":{"a":"Lobster","b":"1F99E","j":["bisque","claws","seafood","animal","nature"]},"shrimp":{"a":"Shrimp","b":"1F990","j":["food","shellfish","small","animal","ocean","nature","seafood"]},"squid":{"a":"Squid","b":"1F991","j":["food","molusc","animal","nature","ocean","sea"]},"oyster":{"a":"Oyster","b":"1F9AA","j":["diving","pearl","food"]},"soft-ice-cream":{"a":"Soft Ice Cream","b":"1F366","j":["cream","dessert","ice","icecream","soft","sweet","food","hot","summer"]},"shaved-ice":{"a":"Shaved Ice","b":"1F367","j":["dessert","ice","shaved","sweet","hot","summer"]},"ice-cream":{"a":"Ice Cream","b":"1F368","j":["cream","dessert","ice","sweet","food","hot"]},"doughnut":{"a":"Doughnut","b":"1F369","j":["breakfast","dessert","donut","sweet","food","snack"]},"cookie":{"a":"Cookie","b":"1F36A","j":["dessert","sweet","food","snack","oreo","chocolate"]},"birthday-cake":{"a":"Birthday Cake","b":"1F382","j":["birthday","cake","celebration","dessert","pastry","sweet","food"]},"shortcake":{"a":"Shortcake","b":"1F370","j":["cake","dessert","pastry","slice","sweet","food"]},"cupcake":{"a":"Cupcake","b":"1F9C1","j":["bakery","sweet","food","dessert"]},"pie":{"a":"Pie","b":"1F967","j":["filling","pastry","fruit","meat","food","dessert"]},"chocolate-bar":{"a":"Chocolate Bar","b":"1F36B","j":["bar","chocolate","dessert","sweet","food","snack"]},"candy":{"a":"Candy","b":"1F36C","j":["dessert","sweet","snack","lolly"]},"lollipop":{"a":"Lollipop","b":"1F36D","j":["candy","dessert","sweet","food","snack"]},"custard":{"a":"Custard","b":"1F36E","j":["dessert","pudding","sweet","food"]},"honey-pot":{"a":"Honey Pot","b":"1F36F","j":["honey","honeypot","pot","sweet","bees","kitchen"]},"baby-bottle":{"a":"Baby Bottle","b":"1F37C","j":["baby","bottle","drink","milk","food","container"]},"glass-of-milk":{"a":"Glass of Milk","b":"1F95B","j":["drink","glass","milk","beverage","cow"]},"hot-beverage":{"a":"Hot Beverage","b":"2615","j":["beverage","coffee","drink","hot","steaming","tea","caffeine","latte","espresso"]},"teapot":{"a":"Teapot","b":"1FAD6","j":["drink","pot","tea","hot"]},"teacup-without-handle":{"a":"Teacup Without Handle","b":"1F375","j":["beverage","cup","drink","tea","teacup","bowl","breakfast","green","british"]},"sake":{"a":"Sake","b":"1F376","j":["bar","beverage","bottle","cup","drink","wine","drunk","japanese","alcohol","booze"]},"bottle-with-popping-cork":{"a":"Bottle with Popping Cork","b":"1F37E","j":["bar","bottle","cork","drink","popping","wine","celebration"]},"wine-glass":{"a":"Wine Glass","b":"1F377","j":["bar","beverage","drink","glass","wine","drunk","alcohol","booze"]},"cocktail-glass":{"a":"Cocktail Glass","b":"1F378","j":["bar","cocktail","drink","glass","drunk","alcohol","beverage","booze","mojito"]},"tropical-drink":{"a":"Tropical Drink","b":"1F379","j":["bar","drink","tropical","beverage","cocktail","summer","beach","alcohol","booze","mojito"]},"beer-mug":{"a":"Beer Mug","b":"1F37A","j":["bar","beer","drink","mug","relax","beverage","drunk","party","pub","summer","alcohol","booze"]},"clinking-beer-mugs":{"a":"Clinking Beer Mugs","b":"1F37B","j":["bar","beer","clink","drink","mug","relax","beverage","drunk","party","pub","summer","alcohol","booze"]},"clinking-glasses":{"a":"Clinking Glasses","b":"1F942","j":["celebrate","clink","drink","glass","beverage","party","alcohol","cheers","wine","champagne","toast"]},"tumbler-glass":{"a":"Tumbler Glass","b":"1F943","j":["glass","liquor","shot","tumbler","whisky","drink","beverage","drunk","alcohol","booze","bourbon","scotch"]},"pouring-liquid":{"a":"⊛ Pouring Liquid","b":"1FAD7","j":["drink","empty","glass","spill"]},"cup-with-straw":{"a":"Cup with Straw","b":"1F964","j":["juice","soda","malt","soft drink","water","drink"]},"bubble-tea":{"a":"Bubble Tea","b":"1F9CB","j":["bubble","milk","pearl","tea","taiwan","boba","milk tea","straw"]},"beverage-box":{"a":"Beverage Box","b":"1F9C3","j":["beverage","box","juice","straw","sweet","drink"]},"mate":{"a":"Mate","b":"1F9C9","j":["drink","tea","beverage"]},"ice":{"a":"Ice","b":"1F9CA","j":["cold","ice cube","iceberg","water"]},"chopsticks":{"a":"Chopsticks","b":"1F962","j":["hashi","jeotgarak","kuaizi","food"]},"fork-and-knife-with-plate":{"a":"Fork and Knife with Plate","b":"1F37D","j":["cooking","fork","knife","plate","food","eat","meal","lunch","dinner","restaurant"]},"fork-and-knife":{"a":"Fork and Knife","b":"1F374","j":["cooking","cutlery","fork","knife","kitchen"]},"spoon":{"a":"Spoon","b":"1F944","j":["tableware","cutlery","kitchen"]},"kitchen-knife":{"a":"Kitchen Knife","b":"1F52A","j":["cooking","hocho","knife","tool","weapon","blade","cutlery","kitchen"]},"jar":{"a":"⊛ Jar","b":"1FAD9","j":["condiment","container","empty","sauce","store"]},"amphora":{"a":"Amphora","b":"1F3FA","j":["Aquarius","cooking","drink","jug","zodiac","vase","jar"]},"globe-showing-europeafrica":{"a":"Globe Showing Europe-Africa","b":"1F30D","j":["Africa","earth","Europe","globe","globe showing Europe-Africa","world","globe_showing_europe_africa","international"]},"globe-showing-americas":{"a":"Globe Showing Americas","b":"1F30E","j":["Americas","earth","globe","globe showing Americas","world","USA","international"]},"globe-showing-asiaaustralia":{"a":"Globe Showing Asia-Australia","b":"1F30F","j":["Asia","Australia","earth","globe","globe showing Asia-Australia","world","globe_showing_asia_australia","east","international"]},"globe-with-meridians":{"a":"Globe with Meridians","b":"1F310","j":["earth","globe","meridians","world","international","internet","interweb","i18n"]},"world-map":{"a":"World Map","b":"1F5FA","j":["map","world","location","direction"]},"map-of-japan":{"a":"Map of Japan","b":"1F5FE","j":["Japan","map","map of Japan","nation","country","japanese","asia"]},"compass":{"a":"Compass","b":"1F9ED","j":["magnetic","navigation","orienteering"]},"snowcapped-mountain":{"a":"Snow-Capped Mountain","b":"1F3D4","j":["cold","mountain","snow","snow-capped mountain","snow_capped_mountain","photo","nature","environment","winter"]},"mountain":{"a":"Mountain","b":"26F0","j":["photo","nature","environment"]},"volcano":{"a":"Volcano","b":"1F30B","j":["eruption","mountain","photo","nature","disaster"]},"mount-fuji":{"a":"Mount Fuji","b":"1F5FB","j":["fuji","mountain","photo","nature","japanese"]},"camping":{"a":"Camping","b":"1F3D5","j":["photo","outdoors","tent"]},"beach-with-umbrella":{"a":"Beach with Umbrella","b":"1F3D6","j":["beach","umbrella","weather","summer","sunny","sand","mojito"]},"desert":{"a":"Desert","b":"1F3DC","j":["photo","warm","saharah"]},"desert-island":{"a":"Desert Island","b":"1F3DD","j":["desert","island","photo","tropical","mojito"]},"national-park":{"a":"National Park","b":"1F3DE","j":["park","photo","environment","nature"]},"stadium":{"a":"Stadium","b":"1F3DF","j":["photo","place","sports","concert","venue"]},"classical-building":{"a":"Classical Building","b":"1F3DB","j":["classical","art","culture","history"]},"building-construction":{"a":"Building Construction","b":"1F3D7","j":["construction","wip","working","progress"]},"brick":{"a":"Brick","b":"1F9F1","j":["bricks","clay","mortar","wall"]},"rock":{"a":"Rock","b":"1FAA8","j":["boulder","heavy","solid","stone"]},"wood":{"a":"Wood","b":"1FAB5","j":["log","lumber","timber","nature","trunk"]},"hut":{"a":"Hut","b":"1F6D6","j":["house","roundhouse","yurt","structure"]},"houses":{"a":"Houses","b":"1F3D8","j":["buildings","photo"]},"derelict-house":{"a":"Derelict House","b":"1F3DA","j":["derelict","house","abandon","evict","broken","building"]},"house":{"a":"House","b":"1F3E0","j":["home","building"]},"house-with-garden":{"a":"House with Garden","b":"1F3E1","j":["garden","home","house","plant","nature"]},"office-building":{"a":"Office Building","b":"1F3E2","j":["building","bureau","work"]},"japanese-post-office":{"a":"Japanese Post Office","b":"1F3E3","j":["Japanese","Japanese post office","post","building","envelope","communication"]},"post-office":{"a":"Post Office","b":"1F3E4","j":["European","post","building","email"]},"hospital":{"a":"Hospital","b":"1F3E5","j":["doctor","medicine","building","health","surgery"]},"bank":{"a":"Bank","b":"1F3E6","j":["building","money","sales","cash","business","enterprise"]},"hotel":{"a":"Hotel","b":"1F3E8","j":["building","accomodation","checkin"]},"love-hotel":{"a":"Love Hotel","b":"1F3E9","j":["hotel","love","like","affection","dating"]},"convenience-store":{"a":"Convenience Store","b":"1F3EA","j":["convenience","store","building","shopping","groceries"]},"school":{"a":"School","b":"1F3EB","j":["building","student","education","learn","teach"]},"department-store":{"a":"Department Store","b":"1F3EC","j":["department","store","building","shopping","mall"]},"factory":{"a":"Factory","b":"1F3ED","j":["building","industry","pollution","smoke"]},"japanese-castle":{"a":"Japanese Castle","b":"1F3EF","j":["castle","Japanese","photo","building"]},"castle":{"a":"Castle","b":"1F3F0","j":["European","building","royalty","history"]},"wedding":{"a":"Wedding","b":"1F492","j":["chapel","romance","love","like","affection","couple","marriage","bride","groom"]},"tokyo-tower":{"a":"Tokyo Tower","b":"1F5FC","j":["Tokyo","tower","photo","japanese"]},"statue-of-liberty":{"a":"Statue of Liberty","b":"1F5FD","j":["liberty","statue","american","newyork"]},"church":{"a":"Church","b":"26EA","j":["Christian","cross","religion","building","christ"]},"mosque":{"a":"Mosque","b":"1F54C","j":["islam","Muslim","religion","worship","minaret"]},"hindu-temple":{"a":"Hindu Temple","b":"1F6D5","j":["hindu","temple","religion"]},"synagogue":{"a":"Synagogue","b":"1F54D","j":["Jew","Jewish","religion","temple","judaism","worship","jewish"]},"shinto-shrine":{"a":"Shinto Shrine","b":"26E9","j":["religion","shinto","shrine","temple","japan","kyoto"]},"kaaba":{"a":"Kaaba","b":"1F54B","j":["islam","Muslim","religion","mecca","mosque"]},"fountain":{"a":"Fountain","b":"26F2","j":["photo","summer","water","fresh"]},"tent":{"a":"Tent","b":"26FA","j":["camping","photo","outdoors"]},"foggy":{"a":"Foggy","b":"1F301","j":["fog","photo","mountain"]},"night-with-stars":{"a":"Night with Stars","b":"1F303","j":["night","star","evening","city","downtown"]},"cityscape":{"a":"Cityscape","b":"1F3D9","j":["city","photo","night life","urban"]},"sunrise-over-mountains":{"a":"Sunrise over Mountains","b":"1F304","j":["morning","mountain","sun","sunrise","view","vacation","photo"]},"sunrise":{"a":"Sunrise","b":"1F305","j":["morning","sun","view","vacation","photo"]},"cityscape-at-dusk":{"a":"Cityscape at Dusk","b":"1F306","j":["city","dusk","evening","landscape","sunset","photo","sky","buildings"]},"sunset":{"a":"Sunset","b":"1F307","j":["dusk","sun","photo","good morning","dawn"]},"bridge-at-night":{"a":"Bridge at Night","b":"1F309","j":["bridge","night","photo","sanfrancisco"]},"hot-springs":{"a":"Hot Springs","b":"2668","j":["hot","hotsprings","springs","steaming","bath","warm","relax"]},"carousel-horse":{"a":"Carousel Horse","b":"1F3A0","j":["carousel","horse","photo","carnival"]},"playground-slide":{"a":"⊛ Playground Slide","b":"1F6DD","j":["amusement park","play"]},"ferris-wheel":{"a":"Ferris Wheel","b":"1F3A1","j":["amusement park","ferris","wheel","photo","carnival","londoneye"]},"roller-coaster":{"a":"Roller Coaster","b":"1F3A2","j":["amusement park","coaster","roller","carnival","playground","photo","fun"]},"barber-pole":{"a":"Barber Pole","b":"1F488","j":["barber","haircut","pole","hair","salon","style"]},"circus-tent":{"a":"Circus Tent","b":"1F3AA","j":["circus","tent","festival","carnival","party"]},"locomotive":{"a":"Locomotive","b":"1F682","j":["engine","railway","steam","train","transportation","vehicle"]},"railway-car":{"a":"Railway Car","b":"1F683","j":["car","electric","railway","train","tram","trolleybus","transportation","vehicle"]},"highspeed-train":{"a":"High-Speed Train","b":"1F684","j":["high-speed train","railway","shinkansen","speed","train","high_speed_train","transportation","vehicle"]},"bullet-train":{"a":"Bullet Train","b":"1F685","j":["bullet","railway","shinkansen","speed","train","transportation","vehicle","fast","public","travel"]},"train":{"a":"Train","b":"1F686","j":["railway","transportation","vehicle"]},"metro":{"a":"Metro","b":"1F687","j":["subway","transportation","blue-square","mrt","underground","tube"]},"light-rail":{"a":"Light Rail","b":"1F688","j":["railway","transportation","vehicle"]},"station":{"a":"Station","b":"1F689","j":["railway","train","transportation","vehicle","public"]},"tram":{"a":"Tram","b":"1F68A","j":["trolleybus","transportation","vehicle"]},"monorail":{"a":"Monorail","b":"1F69D","j":["vehicle","transportation"]},"mountain-railway":{"a":"Mountain Railway","b":"1F69E","j":["car","mountain","railway","transportation","vehicle"]},"tram-car":{"a":"Tram Car","b":"1F68B","j":["car","tram","trolleybus","transportation","vehicle","carriage","public","travel"]},"bus":{"a":"Bus","b":"1F68C","j":["vehicle","car","transportation"]},"oncoming-bus":{"a":"Oncoming Bus","b":"1F68D","j":["bus","oncoming","vehicle","transportation"]},"trolleybus":{"a":"Trolleybus","b":"1F68E","j":["bus","tram","trolley","bart","transportation","vehicle"]},"minibus":{"a":"Minibus","b":"1F690","j":["bus","vehicle","car","transportation"]},"ambulance":{"a":"Ambulance","b":"1F691","j":["vehicle","health","911","hospital"]},"fire-engine":{"a":"Fire Engine","b":"1F692","j":["engine","fire","truck","transportation","cars","vehicle"]},"police-car":{"a":"Police Car","b":"1F693","j":["car","patrol","police","vehicle","cars","transportation","law","legal","enforcement"]},"oncoming-police-car":{"a":"Oncoming Police Car","b":"1F694","j":["car","oncoming","police","vehicle","law","legal","enforcement","911"]},"taxi":{"a":"Taxi","b":"1F695","j":["vehicle","uber","cars","transportation"]},"oncoming-taxi":{"a":"Oncoming Taxi","b":"1F696","j":["oncoming","taxi","vehicle","cars","uber"]},"automobile":{"a":"Automobile","b":"1F697","j":["car","red","transportation","vehicle"]},"oncoming-automobile":{"a":"Oncoming Automobile","b":"1F698","j":["automobile","car","oncoming","vehicle","transportation"]},"sport-utility-vehicle":{"a":"Sport Utility Vehicle","b":"1F699","j":["recreational","sport utility","transportation","vehicle"]},"pickup-truck":{"a":"Pickup Truck","b":"1F6FB","j":["pick-up","pickup","truck","car","transportation"]},"delivery-truck":{"a":"Delivery Truck","b":"1F69A","j":["delivery","truck","cars","transportation"]},"articulated-lorry":{"a":"Articulated Lorry","b":"1F69B","j":["lorry","semi","truck","vehicle","cars","transportation","express"]},"tractor":{"a":"Tractor","b":"1F69C","j":["vehicle","car","farming","agriculture"]},"racing-car":{"a":"Racing Car","b":"1F3CE","j":["car","racing","sports","race","fast","formula","f1"]},"motorcycle":{"a":"Motorcycle","b":"1F3CD","j":["racing","race","sports","fast"]},"motor-scooter":{"a":"Motor Scooter","b":"1F6F5","j":["motor","scooter","vehicle","vespa","sasha"]},"manual-wheelchair":{"a":"Manual Wheelchair","b":"1F9BD","j":["accessibility"]},"motorized-wheelchair":{"a":"Motorized Wheelchair","b":"1F9BC","j":["accessibility"]},"auto-rickshaw":{"a":"Auto Rickshaw","b":"1F6FA","j":["tuk tuk","move","transportation"]},"bicycle":{"a":"Bicycle","b":"1F6B2","j":["bike","sports","exercise","hipster"]},"kick-scooter":{"a":"Kick Scooter","b":"1F6F4","j":["kick","scooter","vehicle","razor"]},"skateboard":{"a":"Skateboard","b":"1F6F9","j":["board"]},"roller-skate":{"a":"Roller Skate","b":"1F6FC","j":["roller","skate","footwear","sports"]},"bus-stop":{"a":"Bus Stop","b":"1F68F","j":["bus","stop","transportation","wait"]},"motorway":{"a":"Motorway","b":"1F6E3","j":["highway","road","cupertino","interstate"]},"railway-track":{"a":"Railway Track","b":"1F6E4","j":["railway","train","transportation"]},"oil-drum":{"a":"Oil Drum","b":"1F6E2","j":["drum","oil","barrell"]},"fuel-pump":{"a":"Fuel Pump","b":"26FD","j":["diesel","fuel","fuelpump","gas","pump","station","gas station","petroleum"]},"wheel":{"a":"⊛ Wheel","b":"1F6DE","j":["circle","tire","turn"]},"police-car-light":{"a":"Police Car Light","b":"1F6A8","j":["beacon","car","light","police","revolving","ambulance","911","emergency","alert","error","pinged","law","legal"]},"horizontal-traffic-light":{"a":"Horizontal Traffic Light","b":"1F6A5","j":["light","signal","traffic","transportation"]},"vertical-traffic-light":{"a":"Vertical Traffic Light","b":"1F6A6","j":["light","signal","traffic","transportation","driving"]},"stop-sign":{"a":"Stop Sign","b":"1F6D1","j":["octagonal","sign","stop"]},"construction":{"a":"Construction","b":"1F6A7","j":["barrier","wip","progress","caution","warning"]},"anchor":{"a":"Anchor","b":"2693","j":["ship","tool","ferry","sea","boat"]},"ring-buoy":{"a":"⊛ Ring Buoy","b":"1F6DF","j":["float","life preserver","life saver","rescue","safety"]},"sailboat":{"a":"Sailboat","b":"26F5","j":["boat","resort","sea","yacht","ship","summer","transportation","water","sailing"]},"canoe":{"a":"Canoe","b":"1F6F6","j":["boat","paddle","water","ship"]},"speedboat":{"a":"Speedboat","b":"1F6A4","j":["boat","ship","transportation","vehicle","summer"]},"passenger-ship":{"a":"Passenger Ship","b":"1F6F3","j":["passenger","ship","yacht","cruise","ferry"]},"ferry":{"a":"Ferry","b":"26F4","j":["boat","passenger","ship","yacht"]},"motor-boat":{"a":"Motor Boat","b":"1F6E5","j":["boat","motorboat","ship"]},"ship":{"a":"Ship","b":"1F6A2","j":["boat","passenger","transportation","titanic","deploy"]},"airplane":{"a":"Airplane","b":"2708","j":["aeroplane","vehicle","transportation","flight","fly"]},"small-airplane":{"a":"Small Airplane","b":"1F6E9","j":["aeroplane","airplane","flight","transportation","fly","vehicle"]},"airplane-departure":{"a":"Airplane Departure","b":"1F6EB","j":["aeroplane","airplane","check-in","departure","departures","airport","flight","landing"]},"airplane-arrival":{"a":"Airplane Arrival","b":"1F6EC","j":["aeroplane","airplane","arrivals","arriving","landing","airport","flight","boarding"]},"parachute":{"a":"Parachute","b":"1FA82","j":["hang-glide","parasail","skydive","fly","glide"]},"seat":{"a":"Seat","b":"1F4BA","j":["chair","sit","airplane","transport","bus","flight","fly"]},"helicopter":{"a":"Helicopter","b":"1F681","j":["vehicle","transportation","fly"]},"suspension-railway":{"a":"Suspension Railway","b":"1F69F","j":["railway","suspension","vehicle","transportation"]},"mountain-cableway":{"a":"Mountain Cableway","b":"1F6A0","j":["cable","gondola","mountain","transportation","vehicle","ski"]},"aerial-tramway":{"a":"Aerial Tramway","b":"1F6A1","j":["aerial","cable","car","gondola","tramway","transportation","vehicle","ski"]},"satellite":{"a":"Satellite","b":"1F6F0","j":["space","communication","gps","orbit","spaceflight","NASA","ISS"]},"rocket":{"a":"Rocket","b":"1F680","j":["space","launch","ship","staffmode","NASA","outer space","outer_space","fly"]},"flying-saucer":{"a":"Flying Saucer","b":"1F6F8","j":["UFO","transportation","vehicle","ufo"]},"bellhop-bell":{"a":"Bellhop Bell","b":"1F6CE","j":["bell","bellhop","hotel","service"]},"luggage":{"a":"Luggage","b":"1F9F3","j":["packing","travel"]},"hourglass-done":{"a":"Hourglass Done","b":"231B","j":["sand","timer","time","clock","oldschool","limit","exam","quiz","test"]},"hourglass-not-done":{"a":"Hourglass Not Done","b":"23F3","j":["hourglass","sand","timer","oldschool","time","countdown"]},"watch":{"a":"Watch","b":"231A","j":["clock","time","accessories"]},"alarm-clock":{"a":"Alarm Clock","b":"23F0","j":["alarm","clock","time","wake"]},"stopwatch":{"a":"Stopwatch","b":"23F1","j":["clock","time","deadline"]},"timer-clock":{"a":"Timer Clock","b":"23F2","j":["clock","timer","alarm"]},"mantelpiece-clock":{"a":"Mantelpiece Clock","b":"1F570","j":["clock","time"]},"twelve-oclock":{"a":"Twelve O’Clock","b":"1F55B","j":["00","12","12:00","clock","o’clock","twelve","twelve_o_clock","time","noon","midnight","midday","late","early","schedule"]},"twelvethirty":{"a":"Twelve-Thirty","b":"1F567","j":["12","12:30","clock","thirty","twelve","twelve-thirty","twelve_thirty","time","late","early","schedule"]},"one-oclock":{"a":"One O’Clock","b":"1F550","j":["00","1","1:00","clock","o’clock","one","one_o_clock","time","late","early","schedule"]},"onethirty":{"a":"One-Thirty","b":"1F55C","j":["1","1:30","clock","one","one-thirty","thirty","one_thirty","time","late","early","schedule"]},"two-oclock":{"a":"Two O’Clock","b":"1F551","j":["00","2","2:00","clock","o’clock","two","two_o_clock","time","late","early","schedule"]},"twothirty":{"a":"Two-Thirty","b":"1F55D","j":["2","2:30","clock","thirty","two","two-thirty","two_thirty","time","late","early","schedule"]},"three-oclock":{"a":"Three O’Clock","b":"1F552","j":["00","3","3:00","clock","o’clock","three","three_o_clock","time","late","early","schedule"]},"threethirty":{"a":"Three-Thirty","b":"1F55E","j":["3","3:30","clock","thirty","three","three-thirty","three_thirty","time","late","early","schedule"]},"four-oclock":{"a":"Four O’Clock","b":"1F553","j":["00","4","4:00","clock","four","o’clock","four_o_clock","time","late","early","schedule"]},"fourthirty":{"a":"Four-Thirty","b":"1F55F","j":["4","4:30","clock","four","four-thirty","thirty","four_thirty","time","late","early","schedule"]},"five-oclock":{"a":"Five O’Clock","b":"1F554","j":["00","5","5:00","clock","five","o’clock","five_o_clock","time","late","early","schedule"]},"fivethirty":{"a":"Five-Thirty","b":"1F560","j":["5","5:30","clock","five","five-thirty","thirty","five_thirty","time","late","early","schedule"]},"six-oclock":{"a":"Six O’Clock","b":"1F555","j":["00","6","6:00","clock","o’clock","six","six_o_clock","time","late","early","schedule","dawn","dusk"]},"sixthirty":{"a":"Six-Thirty","b":"1F561","j":["6","6:30","clock","six","six-thirty","thirty","six_thirty","time","late","early","schedule"]},"seven-oclock":{"a":"Seven O’Clock","b":"1F556","j":["00","7","7:00","clock","o’clock","seven","seven_o_clock","time","late","early","schedule"]},"seventhirty":{"a":"Seven-Thirty","b":"1F562","j":["7","7:30","clock","seven","seven-thirty","thirty","seven_thirty","time","late","early","schedule"]},"eight-oclock":{"a":"Eight O’Clock","b":"1F557","j":["00","8","8:00","clock","eight","o’clock","eight_o_clock","time","late","early","schedule"]},"eightthirty":{"a":"Eight-Thirty","b":"1F563","j":["8","8:30","clock","eight","eight-thirty","thirty","eight_thirty","time","late","early","schedule"]},"nine-oclock":{"a":"Nine O’Clock","b":"1F558","j":["00","9","9:00","clock","nine","o’clock","nine_o_clock","time","late","early","schedule"]},"ninethirty":{"a":"Nine-Thirty","b":"1F564","j":["9","9:30","clock","nine","nine-thirty","thirty","nine_thirty","time","late","early","schedule"]},"ten-oclock":{"a":"Ten O’Clock","b":"1F559","j":["00","10","10:00","clock","o’clock","ten","ten_o_clock","time","late","early","schedule"]},"tenthirty":{"a":"Ten-Thirty","b":"1F565","j":["10","10:30","clock","ten","ten-thirty","thirty","ten_thirty","time","late","early","schedule"]},"eleven-oclock":{"a":"Eleven O’Clock","b":"1F55A","j":["00","11","11:00","clock","eleven","o’clock","eleven_o_clock","time","late","early","schedule"]},"eleventhirty":{"a":"Eleven-Thirty","b":"1F566","j":["11","11:30","clock","eleven","eleven-thirty","thirty","eleven_thirty","time","late","early","schedule"]},"new-moon":{"a":"New Moon","b":"1F311","j":["dark","moon","nature","twilight","planet","space","night","evening","sleep"]},"waxing-crescent-moon":{"a":"Waxing Crescent Moon","b":"1F312","j":["crescent","moon","waxing","nature","twilight","planet","space","night","evening","sleep"]},"first-quarter-moon":{"a":"First Quarter Moon","b":"1F313","j":["moon","quarter","nature","twilight","planet","space","night","evening","sleep"]},"waxing-gibbous-moon":{"a":"Waxing Gibbous Moon","b":"1F314","j":["gibbous","moon","waxing","nature","night","sky","gray","twilight","planet","space","evening","sleep"]},"full-moon":{"a":"Full Moon","b":"1F315","j":["full","moon","nature","yellow","twilight","planet","space","night","evening","sleep"]},"waning-gibbous-moon":{"a":"Waning Gibbous Moon","b":"1F316","j":["gibbous","moon","waning","nature","twilight","planet","space","night","evening","sleep","waxing_gibbous_moon"]},"last-quarter-moon":{"a":"Last Quarter Moon","b":"1F317","j":["moon","quarter","nature","twilight","planet","space","night","evening","sleep"]},"waning-crescent-moon":{"a":"Waning Crescent Moon","b":"1F318","j":["crescent","moon","waning","nature","twilight","planet","space","night","evening","sleep"]},"crescent-moon":{"a":"Crescent Moon","b":"1F319","j":["crescent","moon","night","sleep","sky","evening","magic"]},"new-moon-face":{"a":"New Moon Face","b":"1F31A","j":["face","moon","nature","twilight","planet","space","night","evening","sleep"]},"first-quarter-moon-face":{"a":"First Quarter Moon Face","b":"1F31B","j":["face","moon","quarter","nature","twilight","planet","space","night","evening","sleep"]},"last-quarter-moon-face":{"a":"Last Quarter Moon Face","b":"1F31C","j":["face","moon","quarter","nature","twilight","planet","space","night","evening","sleep"]},"thermometer":{"a":"Thermometer","b":"1F321","j":["weather","temperature","hot","cold"]},"sun":{"a":"Sun","b":"2600","j":["bright","rays","sunny","weather","nature","brightness","summer","beach","spring"]},"full-moon-face":{"a":"Full Moon Face","b":"1F31D","j":["bright","face","full","moon","nature","twilight","planet","space","night","evening","sleep"]},"sun-with-face":{"a":"Sun with Face","b":"1F31E","j":["bright","face","sun","nature","morning","sky"]},"ringed-planet":{"a":"Ringed Planet","b":"1FA90","j":["saturn","saturnine","outerspace"]},"star":{"a":"Star","b":"2B50","j":["night","yellow"]},"glowing-star":{"a":"Glowing Star","b":"1F31F","j":["glittery","glow","shining","sparkle","star","night","awesome","good","magic"]},"shooting-star":{"a":"Shooting Star","b":"1F320","j":["falling","shooting","star","night","photo"]},"milky-way":{"a":"Milky Way","b":"1F30C","j":["space","photo","stars"]},"cloud":{"a":"Cloud","b":"2601","j":["weather","sky"]},"sun-behind-cloud":{"a":"Sun Behind Cloud","b":"26C5","j":["cloud","sun","weather","nature","cloudy","morning","fall","spring"]},"cloud-with-lightning-and-rain":{"a":"Cloud with Lightning and Rain","b":"26C8","j":["cloud","rain","thunder","weather","lightning"]},"sun-behind-small-cloud":{"a":"Sun Behind Small Cloud","b":"1F324","j":["cloud","sun","weather"]},"sun-behind-large-cloud":{"a":"Sun Behind Large Cloud","b":"1F325","j":["cloud","sun","weather"]},"sun-behind-rain-cloud":{"a":"Sun Behind Rain Cloud","b":"1F326","j":["cloud","rain","sun","weather"]},"cloud-with-rain":{"a":"Cloud with Rain","b":"1F327","j":["cloud","rain","weather"]},"cloud-with-snow":{"a":"Cloud with Snow","b":"1F328","j":["cloud","cold","snow","weather"]},"cloud-with-lightning":{"a":"Cloud with Lightning","b":"1F329","j":["cloud","lightning","weather","thunder"]},"tornado":{"a":"Tornado","b":"1F32A","j":["cloud","whirlwind","weather","cyclone","twister"]},"fog":{"a":"Fog","b":"1F32B","j":["cloud","weather"]},"wind-face":{"a":"Wind Face","b":"1F32C","j":["blow","cloud","face","wind","gust","air"]},"cyclone":{"a":"Cyclone","b":"1F300","j":["dizzy","hurricane","twister","typhoon","weather","swirl","blue","cloud","vortex","spiral","whirlpool","spin","tornado"]},"rainbow":{"a":"Rainbow","b":"1F308","j":["rain","nature","happy","unicorn_face","photo","sky","spring"]},"closed-umbrella":{"a":"Closed Umbrella","b":"1F302","j":["clothing","rain","umbrella","weather","drizzle"]},"umbrella":{"a":"Umbrella","b":"2602","j":["clothing","rain","weather","spring"]},"umbrella-with-rain-drops":{"a":"Umbrella with Rain Drops","b":"2614","j":["clothing","drop","rain","umbrella","rainy","weather","spring"]},"umbrella-on-ground":{"a":"Umbrella on Ground","b":"26F1","j":["rain","sun","umbrella","weather","summer"]},"high-voltage":{"a":"High Voltage","b":"26A1","j":["danger","electric","lightning","voltage","zap","thunder","weather","lightning bolt","fast"]},"snowflake":{"a":"Snowflake","b":"2744","j":["cold","snow","winter","season","weather","christmas","xmas"]},"snowman":{"a":"Snowman","b":"2603","j":["cold","snow","winter","season","weather","christmas","xmas","frozen"]},"snowman-without-snow":{"a":"Snowman Without Snow","b":"26C4","j":["cold","snow","snowman","winter","season","weather","christmas","xmas","frozen","without_snow"]},"comet":{"a":"Comet","b":"2604","j":["space"]},"fire":{"a":"Fire","b":"1F525","j":["flame","tool","hot","cook"]},"droplet":{"a":"Droplet","b":"1F4A7","j":["cold","comic","drop","sweat","water","drip","faucet","spring"]},"water-wave":{"a":"Water Wave","b":"1F30A","j":["ocean","water","wave","sea","nature","tsunami","disaster"]},"jackolantern":{"a":"Jack-O-Lantern","b":"1F383","j":["celebration","halloween","jack","jack-o-lantern","lantern","jack_o_lantern","light","pumpkin","creepy","fall"]},"christmas-tree":{"a":"Christmas Tree","b":"1F384","j":["celebration","Christmas","tree","festival","vacation","december","xmas"]},"fireworks":{"a":"Fireworks","b":"1F386","j":["celebration","photo","festival","carnival","congratulations"]},"sparkler":{"a":"Sparkler","b":"1F387","j":["celebration","fireworks","sparkle","stars","night","shine"]},"firecracker":{"a":"Firecracker","b":"1F9E8","j":["dynamite","explosive","fireworks","boom","explode","explosion"]},"sparkles":{"a":"Sparkles","b":"2728","j":["*","sparkle","star","stars","shine","shiny","cool","awesome","good","magic"]},"balloon":{"a":"Balloon","b":"1F388","j":["celebration","party","birthday","circus"]},"party-popper":{"a":"Party Popper","b":"1F389","j":["celebration","party","popper","tada","congratulations","birthday","magic","circus"]},"confetti-ball":{"a":"Confetti Ball","b":"1F38A","j":["ball","celebration","confetti","festival","party","birthday","circus"]},"tanabata-tree":{"a":"Tanabata Tree","b":"1F38B","j":["banner","celebration","Japanese","tree","plant","nature","branch","summer"]},"pine-decoration":{"a":"Pine Decoration","b":"1F38D","j":["bamboo","celebration","Japanese","pine","plant","nature","vegetable","panda"]},"japanese-dolls":{"a":"Japanese Dolls","b":"1F38E","j":["celebration","doll","festival","Japanese","Japanese dolls","japanese","toy","kimono"]},"carp-streamer":{"a":"Carp Streamer","b":"1F38F","j":["carp","celebration","streamer","fish","japanese","koinobori","banner"]},"wind-chime":{"a":"Wind Chime","b":"1F390","j":["bell","celebration","chime","wind","nature","ding","spring"]},"moon-viewing-ceremony":{"a":"Moon Viewing Ceremony","b":"1F391","j":["celebration","ceremony","moon","photo","japan","asia","tsukimi"]},"red-envelope":{"a":"Red Envelope","b":"1F9E7","j":["gift","good luck","hóngbāo","lai see","money"]},"ribbon":{"a":"Ribbon","b":"1F380","j":["celebration","decoration","pink","girl","bowtie"]},"wrapped-gift":{"a":"Wrapped Gift","b":"1F381","j":["box","celebration","gift","present","wrapped","birthday","christmas","xmas"]},"reminder-ribbon":{"a":"Reminder Ribbon","b":"1F397","j":["celebration","reminder","ribbon","sports","cause","support","awareness"]},"admission-tickets":{"a":"Admission Tickets","b":"1F39F","j":["admission","ticket","sports","concert","entrance"]},"ticket":{"a":"Ticket","b":"1F3AB","j":["admission","event","concert","pass"]},"military-medal":{"a":"Military Medal","b":"1F396","j":["celebration","medal","military","award","winning","army"]},"trophy":{"a":"Trophy","b":"1F3C6","j":["prize","win","award","contest","place","ftw","ceremony"]},"sports-medal":{"a":"Sports Medal","b":"1F3C5","j":["medal","award","winning"]},"1st-place-medal":{"a":"1st Place Medal","b":"1F947","j":["first","gold","medal","award","winning"]},"2nd-place-medal":{"a":"2nd Place Medal","b":"1F948","j":["medal","second","silver","award"]},"3rd-place-medal":{"a":"3rd Place Medal","b":"1F949","j":["bronze","medal","third","award"]},"soccer-ball":{"a":"Soccer Ball","b":"26BD","j":["ball","football","soccer","sports"]},"baseball":{"a":"Baseball","b":"26BE","j":["ball","sports","balls"]},"softball":{"a":"Softball","b":"1F94E","j":["ball","glove","underarm","sports","balls"]},"basketball":{"a":"Basketball","b":"1F3C0","j":["ball","hoop","sports","balls","NBA"]},"volleyball":{"a":"Volleyball","b":"1F3D0","j":["ball","game","sports","balls"]},"american-football":{"a":"American Football","b":"1F3C8","j":["american","ball","football","sports","balls","NFL"]},"rugby-football":{"a":"Rugby Football","b":"1F3C9","j":["ball","football","rugby","sports","team"]},"tennis":{"a":"Tennis","b":"1F3BE","j":["ball","racquet","sports","balls","green"]},"flying-disc":{"a":"Flying Disc","b":"1F94F","j":["ultimate","sports","frisbee"]},"bowling":{"a":"Bowling","b":"1F3B3","j":["ball","game","sports","fun","play"]},"cricket-game":{"a":"Cricket Game","b":"1F3CF","j":["ball","bat","game","sports"]},"field-hockey":{"a":"Field Hockey","b":"1F3D1","j":["ball","field","game","hockey","stick","sports"]},"ice-hockey":{"a":"Ice Hockey","b":"1F3D2","j":["game","hockey","ice","puck","stick","sports"]},"lacrosse":{"a":"Lacrosse","b":"1F94D","j":["ball","goal","stick","sports"]},"ping-pong":{"a":"Ping Pong","b":"1F3D3","j":["ball","bat","game","paddle","table tennis","sports","pingpong"]},"badminton":{"a":"Badminton","b":"1F3F8","j":["birdie","game","racquet","shuttlecock","sports"]},"boxing-glove":{"a":"Boxing Glove","b":"1F94A","j":["boxing","glove","sports","fighting"]},"martial-arts-uniform":{"a":"Martial Arts Uniform","b":"1F94B","j":["judo","karate","martial arts","taekwondo","uniform"]},"goal-net":{"a":"Goal Net","b":"1F945","j":["goal","net","sports"]},"flag-in-hole":{"a":"Flag in Hole","b":"26F3","j":["golf","hole","sports","business","flag","summer"]},"ice-skate":{"a":"Ice Skate","b":"26F8","j":["ice","skate","sports"]},"fishing-pole":{"a":"Fishing Pole","b":"1F3A3","j":["fish","pole","food","hobby","summer"]},"diving-mask":{"a":"Diving Mask","b":"1F93F","j":["diving","scuba","snorkeling","sport","ocean"]},"running-shirt":{"a":"Running Shirt","b":"1F3BD","j":["athletics","running","sash","shirt","play","pageant"]},"skis":{"a":"Skis","b":"1F3BF","j":["ski","snow","sports","winter","cold"]},"sled":{"a":"Sled","b":"1F6F7","j":["sledge","sleigh","luge","toboggan"]},"curling-stone":{"a":"Curling Stone","b":"1F94C","j":["game","rock","sports"]},"bullseye":{"a":"Bullseye","b":"1F3AF","j":["dart","direct hit","game","hit","target","direct_hit","play","bar"]},"yoyo":{"a":"Yo-Yo","b":"1FA80","j":["fluctuate","toy","yo-yo","yo_yo"]},"kite":{"a":"Kite","b":"1FA81","j":["fly","soar","wind"]},"pool-8-ball":{"a":"Pool 8 Ball","b":"1F3B1","j":["8","ball","billiard","eight","game","pool","hobby","luck","magic"]},"crystal-ball":{"a":"Crystal Ball","b":"1F52E","j":["ball","crystal","fairy tale","fantasy","fortune","tool","disco","party","magic","circus","fortune_teller"]},"magic-wand":{"a":"Magic Wand","b":"1FA84","j":["magic","witch","wizard","supernature","power"]},"nazar-amulet":{"a":"Nazar Amulet","b":"1F9FF","j":["bead","charm","evil-eye","nazar","talisman"]},"hamsa":{"a":"⊛ Hamsa","b":"1FAAC","j":["amulet","Fatima","hand","Mary","Miriam","protection"]},"video-game":{"a":"Video Game","b":"1F3AE","j":["controller","game","play","console","PS4"]},"joystick":{"a":"Joystick","b":"1F579","j":["game","video game","play"]},"slot-machine":{"a":"Slot Machine","b":"1F3B0","j":["game","slot","bet","gamble","vegas","fruit machine","luck","casino"]},"game-die":{"a":"Game Die","b":"1F3B2","j":["dice","die","game","random","tabletop","play","luck"]},"puzzle-piece":{"a":"Puzzle Piece","b":"1F9E9","j":["clue","interlocking","jigsaw","piece","puzzle"]},"teddy-bear":{"a":"Teddy Bear","b":"1F9F8","j":["plaything","plush","stuffed","toy"]},"piata":{"a":"Piñata","b":"1FA85","j":["celebration","party","piñata","pinata","mexico","candy"]},"mirror-ball":{"a":"⊛ Mirror Ball","b":"1FAA9","j":["dance","disco","glitter","party"]},"nesting-dolls":{"a":"Nesting Dolls","b":"1FA86","j":["doll","nesting","russia","matryoshka","toy"]},"spade-suit":{"a":"Spade Suit","b":"2660","j":["card","game","poker","cards","suits","magic"]},"heart-suit":{"a":"Heart Suit","b":"2665","j":["card","game","poker","cards","magic","suits"]},"diamond-suit":{"a":"Diamond Suit","b":"2666","j":["card","game","poker","cards","magic","suits"]},"club-suit":{"a":"Club Suit","b":"2663","j":["card","game","poker","cards","magic","suits"]},"chess-pawn":{"a":"Chess Pawn","b":"265F","j":["chess","dupe","expendable"]},"joker":{"a":"Joker","b":"1F0CF","j":["card","game","wildcard","poker","cards","play","magic"]},"mahjong-red-dragon":{"a":"Mahjong Red Dragon","b":"1F004","j":["game","mahjong","red","play","chinese","kanji"]},"flower-playing-cards":{"a":"Flower Playing Cards","b":"1F3B4","j":["card","flower","game","Japanese","playing","sunset","red"]},"performing-arts":{"a":"Performing Arts","b":"1F3AD","j":["art","mask","performing","theater","theatre","acting","drama"]},"framed-picture":{"a":"Framed Picture","b":"1F5BC","j":["art","frame","museum","painting","picture","photography"]},"artist-palette":{"a":"Artist Palette","b":"1F3A8","j":["art","museum","painting","palette","design","paint","draw","colors"]},"thread":{"a":"Thread","b":"1F9F5","j":["needle","sewing","spool","string"]},"sewing-needle":{"a":"Sewing Needle","b":"1FAA1","j":["embroidery","needle","sewing","stitches","sutures","tailoring"]},"yarn":{"a":"Yarn","b":"1F9F6","j":["ball","crochet","knit"]},"knot":{"a":"Knot","b":"1FAA2","j":["rope","tangled","tie","twine","twist","scout"]},"glasses":{"a":"Glasses","b":"1F453","j":["clothing","eye","eyeglasses","eyewear","fashion","accessories","eyesight","nerdy","dork","geek"]},"sunglasses":{"a":"Sunglasses","b":"1F576","j":["dark","eye","eyewear","glasses","face","cool","accessories"]},"goggles":{"a":"Goggles","b":"1F97D","j":["eye protection","swimming","welding","eyes","protection","safety"]},"lab-coat":{"a":"Lab Coat","b":"1F97C","j":["doctor","experiment","scientist","chemist"]},"safety-vest":{"a":"Safety Vest","b":"1F9BA","j":["emergency","safety","vest","protection"]},"necktie":{"a":"Necktie","b":"1F454","j":["clothing","tie","shirt","suitup","formal","fashion","cloth","business"]},"tshirt":{"a":"T-Shirt","b":"1F455","j":["clothing","shirt","t-shirt","t_shirt","fashion","cloth","casual","tee"]},"jeans":{"a":"Jeans","b":"1F456","j":["clothing","pants","trousers","fashion","shopping"]},"scarf":{"a":"Scarf","b":"1F9E3","j":["neck","winter","clothes"]},"gloves":{"a":"Gloves","b":"1F9E4","j":["hand","hands","winter","clothes"]},"coat":{"a":"Coat","b":"1F9E5","j":["jacket"]},"socks":{"a":"Socks","b":"1F9E6","j":["stocking","stockings","clothes"]},"dress":{"a":"Dress","b":"1F457","j":["clothing","clothes","fashion","shopping"]},"kimono":{"a":"Kimono","b":"1F458","j":["clothing","dress","fashion","women","female","japanese"]},"sari":{"a":"Sari","b":"1F97B","j":["clothing","dress"]},"onepiece-swimsuit":{"a":"One-Piece Swimsuit","b":"1FA71","j":["bathing suit","one-piece swimsuit","one_piece_swimsuit","fashion"]},"briefs":{"a":"Briefs","b":"1FA72","j":["bathing suit","one-piece","swimsuit","underwear","clothing"]},"shorts":{"a":"Shorts","b":"1FA73","j":["bathing suit","pants","underwear","clothing"]},"bikini":{"a":"Bikini","b":"1F459","j":["clothing","swim","swimming","female","woman","girl","fashion","beach","summer"]},"womans-clothes":{"a":"Woman’S Clothes","b":"1F45A","j":["clothing","woman","woman’s clothes","woman_s_clothes","fashion","shopping_bags","female"]},"purse":{"a":"Purse","b":"1F45B","j":["clothing","coin","fashion","accessories","money","sales","shopping"]},"handbag":{"a":"Handbag","b":"1F45C","j":["bag","clothing","purse","fashion","accessory","accessories","shopping"]},"clutch-bag":{"a":"Clutch Bag","b":"1F45D","j":["bag","clothing","pouch","accessories","shopping"]},"shopping-bags":{"a":"Shopping Bags","b":"1F6CD","j":["bag","hotel","shopping","mall","buy","purchase"]},"backpack":{"a":"Backpack","b":"1F392","j":["bag","rucksack","satchel","school","student","education"]},"thong-sandal":{"a":"Thong Sandal","b":"1FA74","j":["beach sandals","sandals","thong sandals","thongs","zōri","footwear","summer"]},"mans-shoe":{"a":"Man’S Shoe","b":"1F45E","j":["clothing","man","man’s shoe","shoe","man_s_shoe","fashion","male"]},"running-shoe":{"a":"Running Shoe","b":"1F45F","j":["athletic","clothing","shoe","sneaker","shoes","sports","sneakers"]},"hiking-boot":{"a":"Hiking Boot","b":"1F97E","j":["backpacking","boot","camping","hiking"]},"flat-shoe":{"a":"Flat Shoe","b":"1F97F","j":["ballet flat","slip-on","slipper","ballet"]},"highheeled-shoe":{"a":"High-Heeled Shoe","b":"1F460","j":["clothing","heel","high-heeled shoe","shoe","woman","high_heeled_shoe","fashion","shoes","female","pumps","stiletto"]},"womans-sandal":{"a":"Woman’S Sandal","b":"1F461","j":["clothing","sandal","shoe","woman","woman’s sandal","woman_s_sandal","shoes","fashion","flip flops"]},"ballet-shoes":{"a":"Ballet Shoes","b":"1FA70","j":["ballet","dance"]},"womans-boot":{"a":"Woman’S Boot","b":"1F462","j":["boot","clothing","shoe","woman","woman’s boot","woman_s_boot","shoes","fashion"]},"crown":{"a":"Crown","b":"1F451","j":["clothing","king","queen","kod","leader","royalty","lord"]},"womans-hat":{"a":"Woman’S Hat","b":"1F452","j":["clothing","hat","woman","woman’s hat","woman_s_hat","fashion","accessories","female","lady","spring"]},"top-hat":{"a":"Top Hat","b":"1F3A9","j":["clothing","hat","top","tophat","magic","gentleman","classy","circus"]},"graduation-cap":{"a":"Graduation Cap","b":"1F393","j":["cap","celebration","clothing","graduation","hat","school","college","degree","university","legal","learn","education"]},"billed-cap":{"a":"Billed Cap","b":"1F9E2","j":["baseball cap","cap","baseball"]},"military-helmet":{"a":"Military Helmet","b":"1FA96","j":["army","helmet","military","soldier","warrior","protection"]},"rescue-workers-helmet":{"a":"Rescue Worker’S Helmet","b":"26D1","j":["aid","cross","face","hat","helmet","rescue worker’s helmet","rescue_worker_s_helmet","construction","build"]},"prayer-beads":{"a":"Prayer Beads","b":"1F4FF","j":["beads","clothing","necklace","prayer","religion","dhikr","religious"]},"lipstick":{"a":"Lipstick","b":"1F484","j":["cosmetics","makeup","female","girl","fashion","woman"]},"ring":{"a":"Ring","b":"1F48D","j":["diamond","wedding","propose","marriage","valentines","fashion","jewelry","gem","engagement"]},"gem-stone":{"a":"Gem Stone","b":"1F48E","j":["diamond","gem","jewel","blue","ruby","jewelry"]},"muted-speaker":{"a":"Muted Speaker","b":"1F507","j":["mute","quiet","silent","speaker","sound","volume","silence"]},"speaker-low-volume":{"a":"Speaker Low Volume","b":"1F508","j":["soft","sound","volume","silence","broadcast"]},"speaker-medium-volume":{"a":"Speaker Medium Volume","b":"1F509","j":["medium","volume","speaker","broadcast"]},"speaker-high-volume":{"a":"Speaker High Volume","b":"1F50A","j":["loud","volume","noise","noisy","speaker","broadcast"]},"loudspeaker":{"a":"Loudspeaker","b":"1F4E2","j":["loud","public address","volume","sound"]},"megaphone":{"a":"Megaphone","b":"1F4E3","j":["cheering","sound","speaker","volume"]},"postal-horn":{"a":"Postal Horn","b":"1F4EF","j":["horn","post","postal","instrument","music"]},"bell":{"a":"Bell","b":"1F514","j":["sound","notification","christmas","xmas","chime"]},"bell-with-slash":{"a":"Bell with Slash","b":"1F515","j":["bell","forbidden","mute","quiet","silent","sound","volume"]},"musical-score":{"a":"Musical Score","b":"1F3BC","j":["music","score","treble","clef","compose"]},"musical-note":{"a":"Musical Note","b":"1F3B5","j":["music","note","score","tone","sound"]},"musical-notes":{"a":"Musical Notes","b":"1F3B6","j":["music","note","notes","score"]},"studio-microphone":{"a":"Studio Microphone","b":"1F399","j":["mic","microphone","music","studio","sing","recording","artist","talkshow"]},"level-slider":{"a":"Level Slider","b":"1F39A","j":["level","music","slider","scale"]},"control-knobs":{"a":"Control Knobs","b":"1F39B","j":["control","knobs","music","dial"]},"microphone":{"a":"Microphone","b":"1F3A4","j":["karaoke","mic","sound","music","PA","sing","talkshow"]},"headphone":{"a":"Headphone","b":"1F3A7","j":["earbud","music","score","gadgets"]},"radio":{"a":"Radio","b":"1F4FB","j":["video","communication","music","podcast","program"]},"saxophone":{"a":"Saxophone","b":"1F3B7","j":["instrument","music","sax","jazz","blues"]},"accordion":{"a":"Accordion","b":"1FA97","j":["concertina","squeeze box","music"]},"guitar":{"a":"Guitar","b":"1F3B8","j":["instrument","music"]},"musical-keyboard":{"a":"Musical Keyboard","b":"1F3B9","j":["instrument","keyboard","music","piano","compose"]},"trumpet":{"a":"Trumpet","b":"1F3BA","j":["instrument","music","brass"]},"violin":{"a":"Violin","b":"1F3BB","j":["instrument","music","orchestra","symphony"]},"banjo":{"a":"Banjo","b":"1FA95","j":["music","stringed","instructment"]},"drum":{"a":"Drum","b":"1F941","j":["drumsticks","music","instrument","snare"]},"long-drum":{"a":"Long Drum","b":"1FA98","j":["beat","conga","drum","rhythm","music"]},"mobile-phone":{"a":"Mobile Phone","b":"1F4F1","j":["cell","mobile","phone","telephone","technology","apple","gadgets","dial"]},"mobile-phone-with-arrow":{"a":"Mobile Phone with Arrow","b":"1F4F2","j":["arrow","cell","mobile","phone","receive","iphone","incoming"]},"telephone":{"a":"Telephone","b":"260E","j":["phone","technology","communication","dial"]},"telephone-receiver":{"a":"Telephone Receiver","b":"1F4DE","j":["phone","receiver","telephone","technology","communication","dial"]},"pager":{"a":"Pager","b":"1F4DF","j":["bbcall","oldschool","90s"]},"fax-machine":{"a":"Fax Machine","b":"1F4E0","j":["fax","communication","technology"]},"battery":{"a":"Battery","b":"1F50B","j":["power","energy","sustain"]},"low-battery":{"a":"⊛ Low Battery","b":"1FAAB","j":["electronic","low energy"]},"electric-plug":{"a":"Electric Plug","b":"1F50C","j":["electric","electricity","plug","charger","power"]},"laptop":{"a":"Laptop","b":"1F4BB","j":["computer","pc","personal","technology","screen","display","monitor"]},"desktop-computer":{"a":"Desktop Computer","b":"1F5A5","j":["computer","desktop","technology","computing","screen"]},"printer":{"a":"Printer","b":"1F5A8","j":["computer","paper","ink"]},"keyboard":{"a":"Keyboard","b":"2328","j":["computer","technology","type","input","text"]},"computer-mouse":{"a":"Computer Mouse","b":"1F5B1","j":["computer","click"]},"trackball":{"a":"Trackball","b":"1F5B2","j":["computer","technology","trackpad"]},"computer-disk":{"a":"Computer Disk","b":"1F4BD","j":["computer","disk","minidisk","optical","technology","record","data","90s"]},"floppy-disk":{"a":"Floppy Disk","b":"1F4BE","j":["computer","disk","floppy","oldschool","technology","save","90s","80s"]},"optical-disk":{"a":"Optical Disk","b":"1F4BF","j":["cd","computer","disk","optical","technology","dvd","disc","90s"]},"dvd":{"a":"Dvd","b":"1F4C0","j":["blu-ray","computer","disk","optical","cd","disc"]},"abacus":{"a":"Abacus","b":"1F9EE","j":["calculation"]},"movie-camera":{"a":"Movie Camera","b":"1F3A5","j":["camera","cinema","movie","film","record"]},"film-frames":{"a":"Film Frames","b":"1F39E","j":["cinema","film","frames","movie"]},"film-projector":{"a":"Film Projector","b":"1F4FD","j":["cinema","film","movie","projector","video","tape","record"]},"clapper-board":{"a":"Clapper Board","b":"1F3AC","j":["clapper","movie","film","record"]},"television":{"a":"Television","b":"1F4FA","j":["tv","video","technology","program","oldschool","show"]},"camera":{"a":"Camera","b":"1F4F7","j":["video","gadgets","photography"]},"camera-with-flash":{"a":"Camera with Flash","b":"1F4F8","j":["camera","flash","video","photography","gadgets"]},"video-camera":{"a":"Video Camera","b":"1F4F9","j":["camera","video","film","record"]},"videocassette":{"a":"Videocassette","b":"1F4FC","j":["tape","vhs","video","record","oldschool","90s","80s"]},"magnifying-glass-tilted-left":{"a":"Magnifying Glass Tilted Left","b":"1F50D","j":["glass","magnifying","search","tool","zoom","find","detective"]},"magnifying-glass-tilted-right":{"a":"Magnifying Glass Tilted Right","b":"1F50E","j":["glass","magnifying","search","tool","zoom","find","detective"]},"candle":{"a":"Candle","b":"1F56F","j":["light","fire","wax"]},"light-bulb":{"a":"Light Bulb","b":"1F4A1","j":["bulb","comic","electric","idea","light","electricity"]},"flashlight":{"a":"Flashlight","b":"1F526","j":["electric","light","tool","torch","dark","camping","sight","night"]},"red-paper-lantern":{"a":"Red Paper Lantern","b":"1F3EE","j":["bar","lantern","light","red","paper","halloween","spooky"]},"diya-lamp":{"a":"Diya Lamp","b":"1FA94","j":["diya","lamp","oil","lighting"]},"notebook-with-decorative-cover":{"a":"Notebook with Decorative Cover","b":"1F4D4","j":["book","cover","decorated","notebook","classroom","notes","record","paper","study"]},"closed-book":{"a":"Closed Book","b":"1F4D5","j":["book","closed","read","library","knowledge","textbook","learn"]},"open-book":{"a":"Open Book","b":"1F4D6","j":["book","open","read","library","knowledge","literature","learn","study"]},"green-book":{"a":"Green Book","b":"1F4D7","j":["book","green","read","library","knowledge","study"]},"blue-book":{"a":"Blue Book","b":"1F4D8","j":["blue","book","read","library","knowledge","learn","study"]},"orange-book":{"a":"Orange Book","b":"1F4D9","j":["book","orange","read","library","knowledge","textbook","study"]},"books":{"a":"Books","b":"1F4DA","j":["book","literature","library","study"]},"notebook":{"a":"Notebook","b":"1F4D3","j":["stationery","record","notes","paper","study"]},"ledger":{"a":"Ledger","b":"1F4D2","j":["notebook","notes","paper"]},"page-with-curl":{"a":"Page with Curl","b":"1F4C3","j":["curl","document","page","documents","office","paper"]},"scroll":{"a":"Scroll","b":"1F4DC","j":["paper","documents","ancient","history"]},"page-facing-up":{"a":"Page Facing Up","b":"1F4C4","j":["document","page","documents","office","paper","information"]},"newspaper":{"a":"Newspaper","b":"1F4F0","j":["news","paper","press","headline"]},"rolledup-newspaper":{"a":"Rolled-Up Newspaper","b":"1F5DE","j":["news","newspaper","paper","rolled","rolled-up newspaper","rolled_up_newspaper","press","headline"]},"bookmark-tabs":{"a":"Bookmark Tabs","b":"1F4D1","j":["bookmark","mark","marker","tabs","favorite","save","order","tidy"]},"bookmark":{"a":"Bookmark","b":"1F516","j":["mark","favorite","label","save"]},"label":{"a":"Label","b":"1F3F7","j":["sale","tag"]},"money-bag":{"a":"Money Bag","b":"1F4B0","j":["bag","dollar","money","moneybag","payment","coins","sale"]},"coin":{"a":"Coin","b":"1FA99","j":["gold","metal","money","silver","treasure","currency"]},"yen-banknote":{"a":"Yen Banknote","b":"1F4B4","j":["banknote","bill","currency","money","note","yen","sales","japanese","dollar"]},"dollar-banknote":{"a":"Dollar Banknote","b":"1F4B5","j":["banknote","bill","currency","dollar","money","note","sales"]},"euro-banknote":{"a":"Euro Banknote","b":"1F4B6","j":["banknote","bill","currency","euro","money","note","sales","dollar"]},"pound-banknote":{"a":"Pound Banknote","b":"1F4B7","j":["banknote","bill","currency","money","note","pound","british","sterling","sales","bills","uk","england"]},"money-with-wings":{"a":"Money with Wings","b":"1F4B8","j":["banknote","bill","fly","money","wings","dollar","bills","payment","sale"]},"credit-card":{"a":"Credit Card","b":"1F4B3","j":["card","credit","money","sales","dollar","bill","payment","shopping"]},"receipt":{"a":"Receipt","b":"1F9FE","j":["accounting","bookkeeping","evidence","proof","expenses"]},"chart-increasing-with-yen":{"a":"Chart Increasing with Yen","b":"1F4B9","j":["chart","graph","growth","money","yen","green-square","presentation","stats"]},"envelope":{"a":"Envelope","b":"2709","j":["email","letter","postal","inbox","communication"]},"email":{"a":"E-Mail","b":"1F4E7","j":["e-mail","letter","mail","e_mail","communication","inbox"]},"incoming-envelope":{"a":"Incoming Envelope","b":"1F4E8","j":["e-mail","email","envelope","incoming","letter","receive","inbox"]},"envelope-with-arrow":{"a":"Envelope with Arrow","b":"1F4E9","j":["arrow","e-mail","email","envelope","outgoing","communication"]},"outbox-tray":{"a":"Outbox Tray","b":"1F4E4","j":["box","letter","mail","outbox","sent","tray","inbox","email"]},"inbox-tray":{"a":"Inbox Tray","b":"1F4E5","j":["box","inbox","letter","mail","receive","tray","email","documents"]},"package":{"a":"Package","b":"1F4E6","j":["box","parcel","mail","gift","cardboard","moving"]},"closed-mailbox-with-raised-flag":{"a":"Closed Mailbox with Raised Flag","b":"1F4EB","j":["closed","mail","mailbox","postbox","email","inbox","communication"]},"closed-mailbox-with-lowered-flag":{"a":"Closed Mailbox with Lowered Flag","b":"1F4EA","j":["closed","lowered","mail","mailbox","postbox","email","communication","inbox"]},"open-mailbox-with-raised-flag":{"a":"Open Mailbox with Raised Flag","b":"1F4EC","j":["mail","mailbox","open","postbox","email","inbox","communication"]},"open-mailbox-with-lowered-flag":{"a":"Open Mailbox with Lowered Flag","b":"1F4ED","j":["lowered","mail","mailbox","open","postbox","email","inbox"]},"postbox":{"a":"Postbox","b":"1F4EE","j":["mail","mailbox","email","letter","envelope"]},"ballot-box-with-ballot":{"a":"Ballot Box with Ballot","b":"1F5F3","j":["ballot","box","election","vote"]},"pencil":{"a":"Pencil","b":"270F","j":["stationery","write","paper","writing","school","study"]},"black-nib":{"a":"Black Nib","b":"2712","j":["nib","pen","stationery","writing","write"]},"fountain-pen":{"a":"Fountain Pen","b":"1F58B","j":["fountain","pen","stationery","writing","write"]},"pen":{"a":"Pen","b":"1F58A","j":["ballpoint","stationery","writing","write"]},"paintbrush":{"a":"Paintbrush","b":"1F58C","j":["painting","drawing","creativity","art"]},"crayon":{"a":"Crayon","b":"1F58D","j":["drawing","creativity"]},"memo":{"a":"Memo","b":"1F4DD","j":["pencil","write","documents","stationery","paper","writing","legal","exam","quiz","test","study","compose"]},"briefcase":{"a":"Briefcase","b":"1F4BC","j":["business","documents","work","law","legal","job","career"]},"file-folder":{"a":"File Folder","b":"1F4C1","j":["file","folder","documents","business","office"]},"open-file-folder":{"a":"Open File Folder","b":"1F4C2","j":["file","folder","open","documents","load"]},"card-index-dividers":{"a":"Card Index Dividers","b":"1F5C2","j":["card","dividers","index","organizing","business","stationery"]},"calendar":{"a":"Calendar","b":"1F4C5","j":["date","schedule"]},"tearoff-calendar":{"a":"Tear-off Calendar","b":"1F4C6","j":["calendar","tear-off calendar","tear_off_calendar","schedule","date","planning"]},"spiral-notepad":{"a":"Spiral Notepad","b":"1F5D2","j":["note","pad","spiral","memo","stationery"]},"spiral-calendar":{"a":"Spiral Calendar","b":"1F5D3","j":["calendar","pad","spiral","date","schedule","planning"]},"card-index":{"a":"Card Index","b":"1F4C7","j":["card","index","rolodex","business","stationery"]},"chart-increasing":{"a":"Chart Increasing","b":"1F4C8","j":["chart","graph","growth","trend","upward","presentation","stats","recovery","business","economics","money","sales","good","success"]},"chart-decreasing":{"a":"Chart Decreasing","b":"1F4C9","j":["chart","down","graph","trend","presentation","stats","recession","business","economics","money","sales","bad","failure"]},"bar-chart":{"a":"Bar Chart","b":"1F4CA","j":["bar","chart","graph","presentation","stats"]},"clipboard":{"a":"Clipboard","b":"1F4CB","j":["stationery","documents"]},"pushpin":{"a":"Pushpin","b":"1F4CC","j":["pin","stationery","mark","here"]},"round-pushpin":{"a":"Round Pushpin","b":"1F4CD","j":["pin","pushpin","stationery","location","map","here"]},"paperclip":{"a":"Paperclip","b":"1F4CE","j":["documents","stationery"]},"linked-paperclips":{"a":"Linked Paperclips","b":"1F587","j":["link","paperclip","documents","stationery"]},"straight-ruler":{"a":"Straight Ruler","b":"1F4CF","j":["ruler","straight edge","stationery","calculate","length","math","school","drawing","architect","sketch"]},"triangular-ruler":{"a":"Triangular Ruler","b":"1F4D0","j":["ruler","set","triangle","stationery","math","architect","sketch"]},"scissors":{"a":"Scissors","b":"2702","j":["cutting","tool","stationery","cut"]},"card-file-box":{"a":"Card File Box","b":"1F5C3","j":["box","card","file","business","stationery"]},"file-cabinet":{"a":"File Cabinet","b":"1F5C4","j":["cabinet","file","filing","organizing"]},"wastebasket":{"a":"Wastebasket","b":"1F5D1","j":["bin","trash","rubbish","garbage","toss"]},"locked":{"a":"Locked","b":"1F512","j":["closed","security","password","padlock"]},"unlocked":{"a":"Unlocked","b":"1F513","j":["lock","open","unlock","privacy","security"]},"locked-with-pen":{"a":"Locked with Pen","b":"1F50F","j":["ink","lock","nib","pen","privacy","security","secret"]},"locked-with-key":{"a":"Locked with Key","b":"1F510","j":["closed","key","lock","secure","security","privacy"]},"key":{"a":"Key","b":"1F511","j":["lock","password","door"]},"old-key":{"a":"Old Key","b":"1F5DD","j":["clue","key","lock","old","door","password"]},"hammer":{"a":"Hammer","b":"1F528","j":["tool","tools","build","create"]},"axe":{"a":"Axe","b":"1FA93","j":["chop","hatchet","split","wood","tool","cut"]},"pick":{"a":"Pick","b":"26CF","j":["mining","tool","tools","dig"]},"hammer-and-pick":{"a":"Hammer and Pick","b":"2692","j":["hammer","pick","tool","tools","build","create"]},"hammer-and-wrench":{"a":"Hammer and Wrench","b":"1F6E0","j":["hammer","spanner","tool","wrench","tools","build","create"]},"dagger":{"a":"Dagger","b":"1F5E1","j":["knife","weapon"]},"crossed-swords":{"a":"Crossed Swords","b":"2694","j":["crossed","swords","weapon"]},"water-pistol":{"a":"Water Pistol","b":"1F52B","j":["gun","handgun","pistol","revolver","tool","water","weapon","violence"]},"boomerang":{"a":"Boomerang","b":"1FA83","j":["australia","rebound","repercussion","weapon"]},"bow-and-arrow":{"a":"Bow and Arrow","b":"1F3F9","j":["archer","arrow","bow","Sagittarius","zodiac","sports"]},"shield":{"a":"Shield","b":"1F6E1","j":["weapon","protection","security"]},"carpentry-saw":{"a":"Carpentry Saw","b":"1FA9A","j":["carpenter","lumber","saw","tool","cut","chop"]},"wrench":{"a":"Wrench","b":"1F527","j":["spanner","tool","tools","diy","ikea","fix","maintainer"]},"screwdriver":{"a":"Screwdriver","b":"1FA9B","j":["screw","tool","tools"]},"nut-and-bolt":{"a":"Nut and Bolt","b":"1F529","j":["bolt","nut","tool","handy","tools","fix"]},"gear":{"a":"Gear","b":"2699","j":["cog","cogwheel","tool"]},"clamp":{"a":"Clamp","b":"1F5DC","j":["compress","tool","vice"]},"balance-scale":{"a":"Balance Scale","b":"2696","j":["balance","justice","Libra","scale","zodiac","law","fairness","weight"]},"white-cane":{"a":"White Cane","b":"1F9AF","j":["accessibility","blind","probing_cane"]},"link":{"a":"Link","b":"1F517","j":["rings","url"]},"chains":{"a":"Chains","b":"26D3","j":["chain","lock","arrest"]},"hook":{"a":"Hook","b":"1FA9D","j":["catch","crook","curve","ensnare","selling point","tools"]},"toolbox":{"a":"Toolbox","b":"1F9F0","j":["chest","mechanic","tool","tools","diy","fix","maintainer"]},"magnet":{"a":"Magnet","b":"1F9F2","j":["attraction","horseshoe","magnetic"]},"ladder":{"a":"Ladder","b":"1FA9C","j":["climb","rung","step","tools"]},"alembic":{"a":"Alembic","b":"2697","j":["chemistry","tool","distilling","science","experiment"]},"test-tube":{"a":"Test Tube","b":"1F9EA","j":["chemist","chemistry","experiment","lab","science"]},"petri-dish":{"a":"Petri Dish","b":"1F9EB","j":["bacteria","biologist","biology","culture","lab"]},"dna":{"a":"Dna","b":"1F9EC","j":["biologist","evolution","gene","genetics","life"]},"microscope":{"a":"Microscope","b":"1F52C","j":["science","tool","laboratory","experiment","zoomin","study"]},"telescope":{"a":"Telescope","b":"1F52D","j":["science","tool","stars","space","zoom","astronomy"]},"satellite-antenna":{"a":"Satellite Antenna","b":"1F4E1","j":["antenna","dish","satellite","communication","future","radio","space"]},"syringe":{"a":"Syringe","b":"1F489","j":["medicine","needle","shot","sick","health","hospital","drugs","blood","doctor","nurse"]},"drop-of-blood":{"a":"Drop of Blood","b":"1FA78","j":["bleed","blood donation","injury","medicine","menstruation","period","hurt","harm","wound"]},"pill":{"a":"Pill","b":"1F48A","j":["doctor","medicine","sick","health","pharmacy","drug"]},"adhesive-bandage":{"a":"Adhesive Bandage","b":"1FA79","j":["bandage","heal"]},"crutch":{"a":"⊛ Crutch","b":"1FA7C","j":["cane","disability","hurt","mobility aid","stick"]},"stethoscope":{"a":"Stethoscope","b":"1FA7A","j":["doctor","heart","medicine","health"]},"xray":{"a":"⊛ X-Ray","b":"1FA7B","j":["bones","doctor","medical","skeleton"]},"door":{"a":"Door","b":"1F6AA","j":["house","entry","exit"]},"elevator":{"a":"Elevator","b":"1F6D7","j":["accessibility","hoist","lift"]},"mirror":{"a":"Mirror","b":"1FA9E","j":["reflection","reflector","speculum"]},"window":{"a":"Window","b":"1FA9F","j":["frame","fresh air","opening","transparent","view","scenery"]},"bed":{"a":"Bed","b":"1F6CF","j":["hotel","sleep","rest"]},"couch-and-lamp":{"a":"Couch and Lamp","b":"1F6CB","j":["couch","hotel","lamp","read","chill"]},"chair":{"a":"Chair","b":"1FA91","j":["seat","sit","furniture"]},"toilet":{"a":"Toilet","b":"1F6BD","j":["restroom","wc","washroom","bathroom","potty"]},"plunger":{"a":"Plunger","b":"1FAA0","j":["force cup","plumber","suction","toilet"]},"shower":{"a":"Shower","b":"1F6BF","j":["water","clean","bathroom"]},"bathtub":{"a":"Bathtub","b":"1F6C1","j":["bath","clean","shower","bathroom"]},"mouse-trap":{"a":"Mouse Trap","b":"1FAA4","j":["bait","mousetrap","snare","trap","cheese"]},"razor":{"a":"Razor","b":"1FA92","j":["sharp","shave","cut"]},"lotion-bottle":{"a":"Lotion Bottle","b":"1F9F4","j":["lotion","moisturizer","shampoo","sunscreen"]},"safety-pin":{"a":"Safety Pin","b":"1F9F7","j":["diaper","punk rock"]},"broom":{"a":"Broom","b":"1F9F9","j":["cleaning","sweeping","witch"]},"basket":{"a":"Basket","b":"1F9FA","j":["farming","laundry","picnic"]},"roll-of-paper":{"a":"Roll of Paper","b":"1F9FB","j":["paper towels","toilet paper","roll"]},"bucket":{"a":"Bucket","b":"1FAA3","j":["cask","pail","vat","water","container"]},"soap":{"a":"Soap","b":"1F9FC","j":["bar","bathing","cleaning","lather","soapdish"]},"bubbles":{"a":"⊛ Bubbles","b":"1FAE7","j":["burp","clean","soap","underwater"]},"toothbrush":{"a":"Toothbrush","b":"1FAA5","j":["bathroom","brush","clean","dental","hygiene","teeth"]},"sponge":{"a":"Sponge","b":"1F9FD","j":["absorbing","cleaning","porous"]},"fire-extinguisher":{"a":"Fire Extinguisher","b":"1F9EF","j":["extinguish","fire","quench"]},"shopping-cart":{"a":"Shopping Cart","b":"1F6D2","j":["cart","shopping","trolley"]},"cigarette":{"a":"Cigarette","b":"1F6AC","j":["smoking","kills","tobacco","joint","smoke"]},"coffin":{"a":"Coffin","b":"26B0","j":["death","vampire","dead","die","rip","graveyard","cemetery","casket","funeral","box"]},"headstone":{"a":"Headstone","b":"1FAA6","j":["cemetery","grave","graveyard","tombstone","death","rip"]},"funeral-urn":{"a":"Funeral Urn","b":"26B1","j":["ashes","death","funeral","urn","dead","die","rip"]},"moai":{"a":"Moai","b":"1F5FF","j":["face","moyai","statue","rock","easter island"]},"placard":{"a":"Placard","b":"1FAA7","j":["demonstration","picket","protest","sign","announcement"]},"identification-card":{"a":"⊛ Identification Card","b":"1FAAA","j":["credentials","ID","license","security"]},"atm-sign":{"a":"Atm Sign","b":"1F3E7","j":["atm","ATM sign","automated","bank","teller","money","sales","cash","blue-square","payment"]},"litter-in-bin-sign":{"a":"Litter in Bin Sign","b":"1F6AE","j":["litter","litter bin","blue-square","sign","human","info"]},"potable-water":{"a":"Potable Water","b":"1F6B0","j":["drinking","potable","water","blue-square","liquid","restroom","cleaning","faucet"]},"wheelchair-symbol":{"a":"Wheelchair Symbol","b":"267F","j":["access","blue-square","disabled","accessibility"]},"mens-room":{"a":"Men’S Room","b":"1F6B9","j":["lavatory","man","men’s room","restroom","wc","men_s_room","toilet","blue-square","gender","male"]},"womens-room":{"a":"Women’S Room","b":"1F6BA","j":["lavatory","restroom","wc","woman","women’s room","women_s_room","purple-square","female","toilet","loo","gender"]},"restroom":{"a":"Restroom","b":"1F6BB","j":["lavatory","WC","blue-square","toilet","refresh","wc","gender"]},"baby-symbol":{"a":"Baby Symbol","b":"1F6BC","j":["baby","changing","orange-square","child"]},"water-closet":{"a":"Water Closet","b":"1F6BE","j":["closet","lavatory","restroom","water","wc","toilet","blue-square"]},"passport-control":{"a":"Passport Control","b":"1F6C2","j":["control","passport","custom","blue-square"]},"customs":{"a":"Customs","b":"1F6C3","j":["passport","border","blue-square"]},"baggage-claim":{"a":"Baggage Claim","b":"1F6C4","j":["baggage","claim","blue-square","airport","transport"]},"left-luggage":{"a":"Left Luggage","b":"1F6C5","j":["baggage","locker","luggage","blue-square","travel"]},"warning":{"a":"Warning","b":"26A0","j":["exclamation","wip","alert","error","problem","issue"]},"children-crossing":{"a":"Children Crossing","b":"1F6B8","j":["child","crossing","pedestrian","traffic","school","warning","danger","sign","driving","yellow-diamond"]},"no-entry":{"a":"No Entry","b":"26D4","j":["entry","forbidden","no","not","prohibited","traffic","limit","security","privacy","bad","denied","stop","circle"]},"prohibited":{"a":"Prohibited","b":"1F6AB","j":["entry","forbidden","no","not","forbid","stop","limit","denied","disallow","circle"]},"no-bicycles":{"a":"No Bicycles","b":"1F6B3","j":["bicycle","bike","forbidden","no","prohibited","cyclist","circle"]},"no-smoking":{"a":"No Smoking","b":"1F6AD","j":["forbidden","no","not","prohibited","smoking","cigarette","blue-square","smell","smoke"]},"no-littering":{"a":"No Littering","b":"1F6AF","j":["forbidden","litter","no","not","prohibited","trash","bin","garbage","circle"]},"nonpotable-water":{"a":"Non-Potable Water","b":"1F6B1","j":["non-drinking","non-potable","water","non_potable_water","drink","faucet","tap","circle"]},"no-pedestrians":{"a":"No Pedestrians","b":"1F6B7","j":["forbidden","no","not","pedestrian","prohibited","rules","crossing","walking","circle"]},"no-mobile-phones":{"a":"No Mobile Phones","b":"1F4F5","j":["cell","forbidden","mobile","no","phone","iphone","mute","circle"]},"no-one-under-eighteen":{"a":"No One Under Eighteen","b":"1F51E","j":["18","age restriction","eighteen","prohibited","underage","drink","pub","night","minor","circle"]},"radioactive":{"a":"Radioactive","b":"2622","j":["sign","nuclear","danger"]},"biohazard":{"a":"Biohazard","b":"2623","j":["sign","danger"]},"up-arrow":{"a":"Up Arrow","b":"2B06","j":["arrow","cardinal","direction","north","blue-square","continue","top"]},"upright-arrow":{"a":"Up-Right Arrow","b":"2197","j":["arrow","direction","intercardinal","northeast","up-right arrow","up_right_arrow","blue-square","point","diagonal"]},"right-arrow":{"a":"Right Arrow","b":"27A1","j":["arrow","cardinal","direction","east","blue-square","next"]},"downright-arrow":{"a":"Down-Right Arrow","b":"2198","j":["arrow","direction","down-right arrow","intercardinal","southeast","down_right_arrow","blue-square","diagonal"]},"down-arrow":{"a":"Down Arrow","b":"2B07","j":["arrow","cardinal","direction","down","south","blue-square","bottom"]},"downleft-arrow":{"a":"Down-Left Arrow","b":"2199","j":["arrow","direction","down-left arrow","intercardinal","southwest","down_left_arrow","blue-square","diagonal"]},"left-arrow":{"a":"Left Arrow","b":"2B05","j":["arrow","cardinal","direction","west","blue-square","previous","back"]},"upleft-arrow":{"a":"Up-Left Arrow","b":"2196","j":["arrow","direction","intercardinal","northwest","up-left arrow","up_left_arrow","blue-square","point","diagonal"]},"updown-arrow":{"a":"Up-Down Arrow","b":"2195","j":["arrow","up-down arrow","up_down_arrow","blue-square","direction","way","vertical"]},"leftright-arrow":{"a":"Left-Right Arrow","b":"2194","j":["arrow","left-right arrow","left_right_arrow","shape","direction","horizontal","sideways"]},"right-arrow-curving-left":{"a":"Right Arrow Curving Left","b":"21A9","j":["arrow","back","return","blue-square","undo","enter"]},"left-arrow-curving-right":{"a":"Left Arrow Curving Right","b":"21AA","j":["arrow","blue-square","return","rotate","direction"]},"right-arrow-curving-up":{"a":"Right Arrow Curving Up","b":"2934","j":["arrow","blue-square","direction","top"]},"right-arrow-curving-down":{"a":"Right Arrow Curving Down","b":"2935","j":["arrow","down","blue-square","direction","bottom"]},"clockwise-vertical-arrows":{"a":"Clockwise Vertical Arrows","b":"1F503","j":["arrow","clockwise","reload","sync","cycle","round","repeat"]},"counterclockwise-arrows-button":{"a":"Counterclockwise Arrows Button","b":"1F504","j":["anticlockwise","arrow","counterclockwise","withershins","blue-square","sync","cycle"]},"back-arrow":{"a":"Back Arrow","b":"1F519","j":["arrow","back","BACK arrow","words","return"]},"end-arrow":{"a":"End Arrow","b":"1F51A","j":["arrow","end","END arrow","words"]},"on-arrow":{"a":"On! Arrow","b":"1F51B","j":["arrow","mark","on","ON! arrow","words"]},"soon-arrow":{"a":"Soon Arrow","b":"1F51C","j":["arrow","soon","SOON arrow","words"]},"top-arrow":{"a":"Top Arrow","b":"1F51D","j":["arrow","top","TOP arrow","up","words","blue-square"]},"place-of-worship":{"a":"Place of Worship","b":"1F6D0","j":["religion","worship","church","temple","prayer"]},"atom-symbol":{"a":"Atom Symbol","b":"269B","j":["atheist","atom","science","physics","chemistry"]},"om":{"a":"Om","b":"1F549","j":["Hindu","religion","hinduism","buddhism","sikhism","jainism"]},"star-of-david":{"a":"Star of David","b":"2721","j":["David","Jew","Jewish","religion","star","star of David","judaism"]},"wheel-of-dharma":{"a":"Wheel of Dharma","b":"2638","j":["Buddhist","dharma","religion","wheel","hinduism","buddhism","sikhism","jainism"]},"yin-yang":{"a":"Yin Yang","b":"262F","j":["religion","tao","taoist","yang","yin","balance"]},"latin-cross":{"a":"Latin Cross","b":"271D","j":["Christian","cross","religion","christianity"]},"orthodox-cross":{"a":"Orthodox Cross","b":"2626","j":["Christian","cross","religion","suppedaneum"]},"star-and-crescent":{"a":"Star and Crescent","b":"262A","j":["islam","Muslim","religion"]},"peace-symbol":{"a":"Peace Symbol","b":"262E","j":["peace","hippie"]},"menorah":{"a":"Menorah","b":"1F54E","j":["candelabrum","candlestick","religion","hanukkah","candles","jewish"]},"dotted-sixpointed-star":{"a":"Dotted Six-Pointed Star","b":"1F52F","j":["dotted six-pointed star","fortune","star","dotted_six_pointed_star","purple-square","religion","jewish","hexagram"]},"aries":{"a":"Aries","b":"2648","j":["ram","zodiac","sign","purple-square","astrology"]},"taurus":{"a":"Taurus","b":"2649","j":["bull","ox","zodiac","purple-square","sign","astrology"]},"gemini":{"a":"Gemini","b":"264A","j":["twins","zodiac","sign","purple-square","astrology"]},"cancer":{"a":"Cancer","b":"264B","j":["crab","zodiac","sign","purple-square","astrology"]},"leo":{"a":"Leo","b":"264C","j":["lion","zodiac","sign","purple-square","astrology"]},"virgo":{"a":"Virgo","b":"264D","j":["zodiac","sign","purple-square","astrology"]},"libra":{"a":"Libra","b":"264E","j":["balance","justice","scales","zodiac","sign","purple-square","astrology"]},"scorpio":{"a":"Scorpio","b":"264F","j":["scorpion","scorpius","zodiac","sign","purple-square","astrology"]},"sagittarius":{"a":"Sagittarius","b":"2650","j":["archer","zodiac","sign","purple-square","astrology"]},"capricorn":{"a":"Capricorn","b":"2651","j":["goat","zodiac","sign","purple-square","astrology"]},"aquarius":{"a":"Aquarius","b":"2652","j":["bearer","water","zodiac","sign","purple-square","astrology"]},"pisces":{"a":"Pisces","b":"2653","j":["fish","zodiac","purple-square","sign","astrology"]},"ophiuchus":{"a":"Ophiuchus","b":"26CE","j":["bearer","serpent","snake","zodiac","sign","purple-square","constellation","astrology"]},"shuffle-tracks-button":{"a":"Shuffle Tracks Button","b":"1F500","j":["arrow","crossed","blue-square","shuffle","music","random"]},"repeat-button":{"a":"Repeat Button","b":"1F501","j":["arrow","clockwise","repeat","loop","record"]},"repeat-single-button":{"a":"Repeat Single Button","b":"1F502","j":["arrow","clockwise","once","blue-square","loop"]},"play-button":{"a":"Play Button","b":"25B6","j":["arrow","play","right","triangle","blue-square","direction"]},"fastforward-button":{"a":"Fast-Forward Button","b":"23E9","j":["arrow","double","fast","fast-forward button","forward","fast_forward_button","blue-square","play","speed","continue"]},"next-track-button":{"a":"Next Track Button","b":"23ED","j":["arrow","next scene","next track","triangle","forward","next","blue-square"]},"play-or-pause-button":{"a":"Play or Pause Button","b":"23EF","j":["arrow","pause","play","right","triangle","blue-square"]},"reverse-button":{"a":"Reverse Button","b":"25C0","j":["arrow","left","reverse","triangle","blue-square","direction"]},"fast-reverse-button":{"a":"Fast Reverse Button","b":"23EA","j":["arrow","double","rewind","play","blue-square"]},"last-track-button":{"a":"Last Track Button","b":"23EE","j":["arrow","previous scene","previous track","triangle","backward"]},"upwards-button":{"a":"Upwards Button","b":"1F53C","j":["arrow","button","red","blue-square","triangle","direction","point","forward","top"]},"fast-up-button":{"a":"Fast Up Button","b":"23EB","j":["arrow","double","blue-square","direction","top"]},"downwards-button":{"a":"Downwards Button","b":"1F53D","j":["arrow","button","down","red","blue-square","direction","bottom"]},"fast-down-button":{"a":"Fast Down Button","b":"23EC","j":["arrow","double","down","blue-square","direction","bottom"]},"pause-button":{"a":"Pause Button","b":"23F8","j":["bar","double","pause","vertical","blue-square"]},"stop-button":{"a":"Stop Button","b":"23F9","j":["square","stop","blue-square"]},"record-button":{"a":"Record Button","b":"23FA","j":["circle","record","blue-square"]},"eject-button":{"a":"Eject Button","b":"23CF","j":["eject","blue-square"]},"cinema":{"a":"Cinema","b":"1F3A6","j":["camera","film","movie","blue-square","record","curtain","stage","theater"]},"dim-button":{"a":"Dim Button","b":"1F505","j":["brightness","dim","low","sun","afternoon","warm","summer"]},"bright-button":{"a":"Bright Button","b":"1F506","j":["bright","brightness","sun","light"]},"antenna-bars":{"a":"Antenna Bars","b":"1F4F6","j":["antenna","bar","cell","mobile","phone","blue-square","reception","internet","connection","wifi","bluetooth","bars"]},"vibration-mode":{"a":"Vibration Mode","b":"1F4F3","j":["cell","mobile","mode","phone","telephone","vibration","orange-square"]},"mobile-phone-off":{"a":"Mobile Phone off","b":"1F4F4","j":["cell","mobile","off","phone","telephone","mute","orange-square","silence","quiet"]},"female-sign":{"a":"Female Sign","b":"2640","j":["woman","women","lady","girl"]},"male-sign":{"a":"Male Sign","b":"2642","j":["man","boy","men"]},"transgender-symbol":{"a":"Transgender Symbol","b":"26A7","j":["transgender","lgbtq"]},"multiply":{"a":"Multiply","b":"2716","j":["×","cancel","multiplication","sign","x","multiplication_sign","math","calculation"]},"plus":{"a":"Plus","b":"2795","j":["+","math","sign","plus_sign","calculation","addition","more","increase"]},"minus":{"a":"Minus","b":"2796","j":["-","−","math","sign","minus_sign","calculation","subtract","less"]},"divide":{"a":"Divide","b":"2797","j":["÷","division","math","sign","division_sign","calculation"]},"heavy-equals-sign":{"a":"⊛ Heavy Equals Sign","b":"1F7F0","j":["equality","math"]},"infinity":{"a":"Infinity","b":"267E","j":["forever","unbounded","universal"]},"double-exclamation-mark":{"a":"Double Exclamation Mark","b":"203C","j":["!","!!","bangbang","exclamation","mark","surprise"]},"exclamation-question-mark":{"a":"Exclamation Question Mark","b":"2049","j":["!","!?","?","exclamation","interrobang","mark","punctuation","question","wat","surprise"]},"red-question-mark":{"a":"Red Question Mark","b":"2753","j":["?","mark","punctuation","question","question_mark","doubt","confused"]},"white-question-mark":{"a":"White Question Mark","b":"2754","j":["?","mark","outlined","punctuation","question","doubts","gray","huh","confused"]},"white-exclamation-mark":{"a":"White Exclamation Mark","b":"2755","j":["!","exclamation","mark","outlined","punctuation","surprise","gray","wow","warning"]},"red-exclamation-mark":{"a":"Red Exclamation Mark","b":"2757","j":["!","exclamation","mark","punctuation","exclamation_mark","heavy_exclamation_mark","danger","surprise","wow","warning"]},"wavy-dash":{"a":"Wavy Dash","b":"3030","j":["dash","punctuation","wavy","draw","line","moustache","mustache","squiggle","scribble"]},"currency-exchange":{"a":"Currency Exchange","b":"1F4B1","j":["bank","currency","exchange","money","sales","dollar","travel"]},"heavy-dollar-sign":{"a":"Heavy Dollar Sign","b":"1F4B2","j":["currency","dollar","money","sales","payment","buck"]},"medical-symbol":{"a":"Medical Symbol","b":"2695","j":["aesculapius","medicine","staff","health","hospital"]},"recycling-symbol":{"a":"Recycling Symbol","b":"267B","j":["recycle","arrow","environment","garbage","trash"]},"fleurdelis":{"a":"Fleur-De-Lis","b":"269C","j":["fleur-de-lis","fleur_de_lis","decorative","scout"]},"trident-emblem":{"a":"Trident Emblem","b":"1F531","j":["anchor","emblem","ship","tool","trident","weapon","spear"]},"name-badge":{"a":"Name Badge","b":"1F4DB","j":["badge","name","fire","forbid"]},"japanese-symbol-for-beginner":{"a":"Japanese Symbol for Beginner","b":"1F530","j":["beginner","chevron","Japanese","Japanese symbol for beginner","leaf","badge","shield"]},"hollow-red-circle":{"a":"Hollow Red Circle","b":"2B55","j":["circle","large","o","red","round"]},"check-mark-button":{"a":"Check Mark Button","b":"2705","j":["✓","button","check","mark","green-square","ok","agree","vote","election","answer","tick"]},"check-box-with-check":{"a":"Check Box with Check","b":"2611","j":["✓","box","check","ok","agree","confirm","black-square","vote","election","yes","tick"]},"check-mark":{"a":"Check Mark","b":"2714","j":["✓","check","mark","ok","nike","answer","yes","tick"]},"cross-mark":{"a":"Cross Mark","b":"274C","j":["×","cancel","cross","mark","multiplication","multiply","x","no","delete","remove","red"]},"cross-mark-button":{"a":"Cross Mark Button","b":"274E","j":["×","mark","square","x","green-square","no","deny"]},"curly-loop":{"a":"Curly Loop","b":"27B0","j":["curl","loop","scribble","draw","shape","squiggle"]},"double-curly-loop":{"a":"Double Curly Loop","b":"27BF","j":["curl","double","loop","tape","cassette"]},"part-alternation-mark":{"a":"Part Alternation Mark","b":"303D","j":["mark","part","graph","presentation","stats","business","economics","bad"]},"eightspoked-asterisk":{"a":"Eight-Spoked Asterisk","b":"2733","j":["*","asterisk","eight-spoked asterisk","eight_spoked_asterisk","star","sparkle","green-square"]},"eightpointed-star":{"a":"Eight-Pointed Star","b":"2734","j":["*","eight-pointed star","star","eight_pointed_star","orange-square","shape","polygon"]},"sparkle":{"a":"Sparkle","b":"2747","j":["*","stars","green-square","awesome","good","fireworks"]},"copyright":{"a":"Copyright","b":"00A9","j":["c","ip","license","circle","law","legal"]},"registered":{"a":"Registered","b":"00AE","j":["r","alphabet","circle"]},"trade-mark":{"a":"Trade Mark","b":"2122","j":["mark","tm","trademark","brand","law","legal"]},"keycap":{"a":"Keycap: *","b":"002A-FE0F-20E3","j":["keycap_","star"]},"keycap-0":{"a":"Keycap: 0","b":"0030-FE0F-20E3","j":["keycap","0","numbers","blue-square","null"]},"keycap-1":{"a":"Keycap: 1","b":"0031-FE0F-20E3","j":["keycap","blue-square","numbers","1"]},"keycap-2":{"a":"Keycap: 2","b":"0032-FE0F-20E3","j":["keycap","numbers","2","prime","blue-square"]},"keycap-3":{"a":"Keycap: 3","b":"0033-FE0F-20E3","j":["keycap","3","numbers","prime","blue-square"]},"keycap-4":{"a":"Keycap: 4","b":"0034-FE0F-20E3","j":["keycap","4","numbers","blue-square"]},"keycap-5":{"a":"Keycap: 5","b":"0035-FE0F-20E3","j":["keycap","5","numbers","blue-square","prime"]},"keycap-6":{"a":"Keycap: 6","b":"0036-FE0F-20E3","j":["keycap","6","numbers","blue-square"]},"keycap-7":{"a":"Keycap: 7","b":"0037-FE0F-20E3","j":["keycap","7","numbers","blue-square","prime"]},"keycap-8":{"a":"Keycap: 8","b":"0038-FE0F-20E3","j":["keycap","8","blue-square","numbers"]},"keycap-9":{"a":"Keycap: 9","b":"0039-FE0F-20E3","j":["keycap","blue-square","numbers","9"]},"keycap-10":{"a":"Keycap: 10","b":"1F51F","j":["keycap","numbers","10","blue-square"]},"input-latin-uppercase":{"a":"Input Latin Uppercase","b":"1F520","j":["ABCD","input","latin","letters","uppercase","alphabet","words","blue-square"]},"input-latin-lowercase":{"a":"Input Latin Lowercase","b":"1F521","j":["abcd","input","latin","letters","lowercase","blue-square","alphabet"]},"input-numbers":{"a":"Input Numbers","b":"1F522","j":["1234","input","numbers","blue-square"]},"input-symbols":{"a":"Input Symbols","b":"1F523","j":["〒♪&%","input","blue-square","music","note","ampersand","percent","glyphs","characters"]},"input-latin-letters":{"a":"Input Latin Letters","b":"1F524","j":["abc","alphabet","input","latin","letters","blue-square"]},"a-button-blood-type":{"a":"A Button (Blood Type)","b":"1F170","j":["a","A button (blood type)","blood type","a_button","red-square","alphabet","letter"]},"ab-button-blood-type":{"a":"Ab Button (Blood Type)","b":"1F18E","j":["ab","AB button (blood type)","blood type","ab_button","red-square","alphabet"]},"b-button-blood-type":{"a":"B Button (Blood Type)","b":"1F171","j":["b","B button (blood type)","blood type","b_button","red-square","alphabet","letter"]},"cl-button":{"a":"Cl Button","b":"1F191","j":["cl","CL button","alphabet","words","red-square"]},"cool-button":{"a":"Cool Button","b":"1F192","j":["cool","COOL button","words","blue-square"]},"free-button":{"a":"Free Button","b":"1F193","j":["free","FREE button","blue-square","words"]},"information":{"a":"Information","b":"2139","j":["i","blue-square","alphabet","letter"]},"id-button":{"a":"Id Button","b":"1F194","j":["id","ID button","identity","purple-square","words"]},"circled-m":{"a":"Circled M","b":"24C2","j":["circle","circled M","m","alphabet","blue-circle","letter"]},"new-button":{"a":"New Button","b":"1F195","j":["new","NEW button","blue-square","words","start"]},"ng-button":{"a":"Ng Button","b":"1F196","j":["ng","NG button","blue-square","words","shape","icon"]},"o-button-blood-type":{"a":"O Button (Blood Type)","b":"1F17E","j":["blood type","o","O button (blood type)","o_button","alphabet","red-square","letter"]},"ok-button":{"a":"Ok Button","b":"1F197","j":["OK","OK button","good","agree","yes","blue-square"]},"p-button":{"a":"P Button","b":"1F17F","j":["P button","parking","cars","blue-square","alphabet","letter"]},"sos-button":{"a":"Sos Button","b":"1F198","j":["help","sos","SOS button","red-square","words","emergency","911"]},"up-button":{"a":"Up! Button","b":"1F199","j":["mark","up","UP! button","blue-square","above","high"]},"vs-button":{"a":"Vs Button","b":"1F19A","j":["versus","vs","VS button","words","orange-square"]},"japanese-here-button":{"a":"Japanese “Here” Button","b":"1F201","j":["“here”","Japanese","Japanese “here” button","katakana","ココ","blue-square","here","japanese","destination"]},"japanese-service-charge-button":{"a":"Japanese “Service Charge” Button","b":"1F202","j":["“service charge”","Japanese","Japanese “service charge” button","katakana","サ","japanese","blue-square"]},"japanese-monthly-amount-button":{"a":"Japanese “Monthly Amount” Button","b":"1F237","j":["“monthly amount”","ideograph","Japanese","Japanese “monthly amount” button","月","chinese","month","moon","japanese","orange-square","kanji"]},"japanese-not-free-of-charge-button":{"a":"Japanese “Not Free of Charge” Button","b":"1F236","j":["“not free of charge”","ideograph","Japanese","Japanese “not free of charge” button","有","orange-square","chinese","have","kanji"]},"japanese-reserved-button":{"a":"Japanese “Reserved” Button","b":"1F22F","j":["“reserved”","ideograph","Japanese","Japanese “reserved” button","指","chinese","point","green-square","kanji"]},"japanese-bargain-button":{"a":"Japanese “Bargain” Button","b":"1F250","j":["“bargain”","ideograph","Japanese","Japanese “bargain” button","得","chinese","kanji","obtain","get","circle"]},"japanese-discount-button":{"a":"Japanese “Discount” Button","b":"1F239","j":["“discount”","ideograph","Japanese","Japanese “discount” button","割","cut","divide","chinese","kanji","pink-square"]},"japanese-free-of-charge-button":{"a":"Japanese “Free of Charge” Button","b":"1F21A","j":["“free of charge”","ideograph","Japanese","Japanese “free of charge” button","無","nothing","chinese","kanji","japanese","orange-square"]},"japanese-prohibited-button":{"a":"Japanese “Prohibited” Button","b":"1F232","j":["“prohibited”","ideograph","Japanese","Japanese “prohibited” button","禁","kanji","japanese","chinese","forbidden","limit","restricted","red-square"]},"japanese-acceptable-button":{"a":"Japanese “Acceptable” Button","b":"1F251","j":["“acceptable”","ideograph","Japanese","Japanese “acceptable” button","可","ok","good","chinese","kanji","agree","yes","orange-circle"]},"japanese-application-button":{"a":"Japanese “Application” Button","b":"1F238","j":["“application”","ideograph","Japanese","Japanese “application” button","申","chinese","japanese","kanji","orange-square"]},"japanese-passing-grade-button":{"a":"Japanese “Passing Grade” Button","b":"1F234","j":["“passing grade”","ideograph","Japanese","Japanese “passing grade” button","合","japanese","chinese","join","kanji","red-square"]},"japanese-vacancy-button":{"a":"Japanese “Vacancy” Button","b":"1F233","j":["“vacancy”","ideograph","Japanese","Japanese “vacancy” button","空","kanji","japanese","chinese","empty","sky","blue-square"]},"japanese-congratulations-button":{"a":"Japanese “Congratulations” Button","b":"3297","j":["“congratulations”","ideograph","Japanese","Japanese “congratulations” button","祝","chinese","kanji","japanese","red-circle"]},"japanese-secret-button":{"a":"Japanese “Secret” Button","b":"3299","j":["“secret”","ideograph","Japanese","Japanese “secret” button","秘","privacy","chinese","sshh","kanji","red-circle"]},"japanese-open-for-business-button":{"a":"Japanese “Open for Business” Button","b":"1F23A","j":["“open for business”","ideograph","Japanese","Japanese “open for business” button","営","japanese","opening hours","orange-square"]},"japanese-no-vacancy-button":{"a":"Japanese “No Vacancy” Button","b":"1F235","j":["“no vacancy”","ideograph","Japanese","Japanese “no vacancy” button","満","full","chinese","japanese","red-square","kanji"]},"red-circle":{"a":"Red Circle","b":"1F534","j":["circle","geometric","red","shape","error","danger"]},"orange-circle":{"a":"Orange Circle","b":"1F7E0","j":["circle","orange","round"]},"yellow-circle":{"a":"Yellow Circle","b":"1F7E1","j":["circle","yellow","round"]},"green-circle":{"a":"Green Circle","b":"1F7E2","j":["circle","green","round"]},"blue-circle":{"a":"Blue Circle","b":"1F535","j":["blue","circle","geometric","shape","icon","button"]},"purple-circle":{"a":"Purple Circle","b":"1F7E3","j":["circle","purple","round"]},"brown-circle":{"a":"Brown Circle","b":"1F7E4","j":["brown","circle","round"]},"black-circle":{"a":"Black Circle","b":"26AB","j":["circle","geometric","shape","button","round"]},"white-circle":{"a":"White Circle","b":"26AA","j":["circle","geometric","shape","round"]},"red-square":{"a":"Red Square","b":"1F7E5","j":["red","square"]},"orange-square":{"a":"Orange Square","b":"1F7E7","j":["orange","square"]},"yellow-square":{"a":"Yellow Square","b":"1F7E8","j":["square","yellow"]},"green-square":{"a":"Green Square","b":"1F7E9","j":["green","square"]},"blue-square":{"a":"Blue Square","b":"1F7E6","j":["blue","square"]},"purple-square":{"a":"Purple Square","b":"1F7EA","j":["purple","square"]},"brown-square":{"a":"Brown Square","b":"1F7EB","j":["brown","square"]},"black-large-square":{"a":"Black Large Square","b":"2B1B","j":["geometric","square","shape","icon","button"]},"white-large-square":{"a":"White Large Square","b":"2B1C","j":["geometric","square","shape","icon","stone","button"]},"black-medium-square":{"a":"Black Medium Square","b":"25FC","j":["geometric","square","shape","button","icon"]},"white-medium-square":{"a":"White Medium Square","b":"25FB","j":["geometric","square","shape","stone","icon"]},"black-mediumsmall-square":{"a":"Black Medium-Small Square","b":"25FE","j":["black medium-small square","geometric","square","black_medium_small_square","icon","shape","button"]},"white-mediumsmall-square":{"a":"White Medium-Small Square","b":"25FD","j":["geometric","square","white medium-small square","white_medium_small_square","shape","stone","icon","button"]},"black-small-square":{"a":"Black Small Square","b":"25AA","j":["geometric","square","shape","icon"]},"white-small-square":{"a":"White Small Square","b":"25AB","j":["geometric","square","shape","icon"]},"large-orange-diamond":{"a":"Large Orange Diamond","b":"1F536","j":["diamond","geometric","orange","shape","jewel","gem"]},"large-blue-diamond":{"a":"Large Blue Diamond","b":"1F537","j":["blue","diamond","geometric","shape","jewel","gem"]},"small-orange-diamond":{"a":"Small Orange Diamond","b":"1F538","j":["diamond","geometric","orange","shape","jewel","gem"]},"small-blue-diamond":{"a":"Small Blue Diamond","b":"1F539","j":["blue","diamond","geometric","shape","jewel","gem"]},"red-triangle-pointed-up":{"a":"Red Triangle Pointed Up","b":"1F53A","j":["geometric","red","shape","direction","up","top"]},"red-triangle-pointed-down":{"a":"Red Triangle Pointed Down","b":"1F53B","j":["down","geometric","red","shape","direction","bottom"]},"diamond-with-a-dot":{"a":"Diamond with a Dot","b":"1F4A0","j":["comic","diamond","geometric","inside","jewel","blue","gem","crystal","fancy"]},"radio-button":{"a":"Radio Button","b":"1F518","j":["button","geometric","radio","input","old","music","circle"]},"white-square-button":{"a":"White Square Button","b":"1F533","j":["button","geometric","outlined","square","shape","input"]},"black-square-button":{"a":"Black Square Button","b":"1F532","j":["button","geometric","square","shape","input","frame"]},"chequered-flag":{"a":"Chequered Flag","b":"1F3C1","j":["checkered","chequered","racing","contest","finishline","race","gokart"]},"triangular-flag":{"a":"Triangular Flag","b":"1F6A9","j":["post","mark","milestone","place"]},"crossed-flags":{"a":"Crossed Flags","b":"1F38C","j":["celebration","cross","crossed","Japanese","japanese","nation","country","border"]},"black-flag":{"a":"Black Flag","b":"1F3F4","j":["waving","pirate"]},"white-flag":{"a":"White Flag","b":"1F3F3","j":["waving","losing","loser","lost","surrender","give up","fail"]},"rainbow-flag":{"a":"Rainbow Flag","b":"1F3F3-FE0F-200D-1F308","j":["pride","rainbow","flag","gay","lgbt","glbt","queer","homosexual","lesbian","bisexual","transgender"]},"transgender-flag":{"a":"Transgender Flag","b":"1F3F3-FE0F-200D-26A7-FE0F","j":["flag","light blue","pink","transgender","white","lgbtq"]},"pirate-flag":{"a":"Pirate Flag","b":"1F3F4-200D-2620-FE0F","j":["Jolly Roger","pirate","plunder","treasure","skull","crossbones","flag","banner"]},"flag-ascension-island":{"a":"Flag: Ascension Island","b":"1F1E6-1F1E8","j":["flag"]},"flag-andorra":{"a":"Flag: Andorra","b":"1F1E6-1F1E9","j":["flag","ad","nation","country","banner","andorra"]},"flag-united-arab-emirates":{"a":"Flag: United Arab Emirates","b":"1F1E6-1F1EA","j":["flag","united","arab","emirates","nation","country","banner","united_arab_emirates"]},"flag-afghanistan":{"a":"Flag: Afghanistan","b":"1F1E6-1F1EB","j":["flag","af","nation","country","banner","afghanistan"]},"flag-antigua--barbuda":{"a":"Flag: Antigua & Barbuda","b":"1F1E6-1F1EC","j":["flag","flag_antigua_barbuda","antigua","barbuda","nation","country","banner","antigua_barbuda"]},"flag-anguilla":{"a":"Flag: Anguilla","b":"1F1E6-1F1EE","j":["flag","ai","nation","country","banner","anguilla"]},"flag-albania":{"a":"Flag: Albania","b":"1F1E6-1F1F1","j":["flag","al","nation","country","banner","albania"]},"flag-armenia":{"a":"Flag: Armenia","b":"1F1E6-1F1F2","j":["flag","am","nation","country","banner","armenia"]},"flag-angola":{"a":"Flag: Angola","b":"1F1E6-1F1F4","j":["flag","ao","nation","country","banner","angola"]},"flag-antarctica":{"a":"Flag: Antarctica","b":"1F1E6-1F1F6","j":["flag","aq","nation","country","banner","antarctica"]},"flag-argentina":{"a":"Flag: Argentina","b":"1F1E6-1F1F7","j":["flag","ar","nation","country","banner","argentina"]},"flag-american-samoa":{"a":"Flag: American Samoa","b":"1F1E6-1F1F8","j":["flag","american","ws","nation","country","banner","american_samoa"]},"flag-austria":{"a":"Flag: Austria","b":"1F1E6-1F1F9","j":["flag","at","nation","country","banner","austria"]},"flag-australia":{"a":"Flag: Australia","b":"1F1E6-1F1FA","j":["flag","au","nation","country","banner","australia"]},"flag-aruba":{"a":"Flag: Aruba","b":"1F1E6-1F1FC","j":["flag","aw","nation","country","banner","aruba"]},"flag-land-islands":{"a":"Flag: Åland Islands","b":"1F1E6-1F1FD","j":["flag","flag_aland_islands","Åland","islands","nation","country","banner","aland_islands"]},"flag-azerbaijan":{"a":"Flag: Azerbaijan","b":"1F1E6-1F1FF","j":["flag","az","nation","country","banner","azerbaijan"]},"flag-bosnia--herzegovina":{"a":"Flag: Bosnia & Herzegovina","b":"1F1E7-1F1E6","j":["flag","flag_bosnia_herzegovina","bosnia","herzegovina","nation","country","banner","bosnia_herzegovina"]},"flag-barbados":{"a":"Flag: Barbados","b":"1F1E7-1F1E7","j":["flag","bb","nation","country","banner","barbados"]},"flag-bangladesh":{"a":"Flag: Bangladesh","b":"1F1E7-1F1E9","j":["flag","bd","nation","country","banner","bangladesh"]},"flag-belgium":{"a":"Flag: Belgium","b":"1F1E7-1F1EA","j":["flag","be","nation","country","banner","belgium"]},"flag-burkina-faso":{"a":"Flag: Burkina Faso","b":"1F1E7-1F1EB","j":["flag","burkina","faso","nation","country","banner","burkina_faso"]},"flag-bulgaria":{"a":"Flag: Bulgaria","b":"1F1E7-1F1EC","j":["flag","bg","nation","country","banner","bulgaria"]},"flag-bahrain":{"a":"Flag: Bahrain","b":"1F1E7-1F1ED","j":["flag","bh","nation","country","banner","bahrain"]},"flag-burundi":{"a":"Flag: Burundi","b":"1F1E7-1F1EE","j":["flag","bi","nation","country","banner","burundi"]},"flag-benin":{"a":"Flag: Benin","b":"1F1E7-1F1EF","j":["flag","bj","nation","country","banner","benin"]},"flag-st-barthlemy":{"a":"Flag: St. Barthélemy","b":"1F1E7-1F1F1","j":["flag","flag_st_barthelemy","saint","barthélemy","nation","country","banner","st_barthelemy"]},"flag-bermuda":{"a":"Flag: Bermuda","b":"1F1E7-1F1F2","j":["flag","bm","nation","country","banner","bermuda"]},"flag-brunei":{"a":"Flag: Brunei","b":"1F1E7-1F1F3","j":["flag","bn","darussalam","nation","country","banner","brunei"]},"flag-bolivia":{"a":"Flag: Bolivia","b":"1F1E7-1F1F4","j":["flag","bo","nation","country","banner","bolivia"]},"flag-caribbean-netherlands":{"a":"Flag: Caribbean Netherlands","b":"1F1E7-1F1F6","j":["flag","bonaire","nation","country","banner","caribbean_netherlands"]},"flag-brazil":{"a":"Flag: Brazil","b":"1F1E7-1F1F7","j":["flag","br","nation","country","banner","brazil"]},"flag-bahamas":{"a":"Flag: Bahamas","b":"1F1E7-1F1F8","j":["flag","bs","nation","country","banner","bahamas"]},"flag-bhutan":{"a":"Flag: Bhutan","b":"1F1E7-1F1F9","j":["flag","bt","nation","country","banner","bhutan"]},"flag-bouvet-island":{"a":"Flag: Bouvet Island","b":"1F1E7-1F1FB","j":["flag","norway"]},"flag-botswana":{"a":"Flag: Botswana","b":"1F1E7-1F1FC","j":["flag","bw","nation","country","banner","botswana"]},"flag-belarus":{"a":"Flag: Belarus","b":"1F1E7-1F1FE","j":["flag","by","nation","country","banner","belarus"]},"flag-belize":{"a":"Flag: Belize","b":"1F1E7-1F1FF","j":["flag","bz","nation","country","banner","belize"]},"flag-canada":{"a":"Flag: Canada","b":"1F1E8-1F1E6","j":["flag","ca","nation","country","banner","canada"]},"flag-cocos-keeling-islands":{"a":"Flag: Cocos (Keeling) Islands","b":"1F1E8-1F1E8","j":["flag","flag_cocos_islands","cocos","keeling","islands","nation","country","banner","cocos_islands"]},"flag-congo--kinshasa":{"a":"Flag: Congo - Kinshasa","b":"1F1E8-1F1E9","j":["flag","flag_congo_kinshasa","congo","democratic","republic","nation","country","banner","congo_kinshasa"]},"flag-central-african-republic":{"a":"Flag: Central African Republic","b":"1F1E8-1F1EB","j":["flag","central","african","republic","nation","country","banner","central_african_republic"]},"flag-congo--brazzaville":{"a":"Flag: Congo - Brazzaville","b":"1F1E8-1F1EC","j":["flag","flag_congo_brazzaville","congo","nation","country","banner","congo_brazzaville"]},"flag-switzerland":{"a":"Flag: Switzerland","b":"1F1E8-1F1ED","j":["flag","ch","nation","country","banner","switzerland"]},"flag-cte-divoire":{"a":"Flag: Côte D’Ivoire","b":"1F1E8-1F1EE","j":["flag","flag_cote_d_ivoire","ivory","coast","nation","country","banner","cote_d_ivoire"]},"flag-cook-islands":{"a":"Flag: Cook Islands","b":"1F1E8-1F1F0","j":["flag","cook","islands","nation","country","banner","cook_islands"]},"flag-chile":{"a":"Flag: Chile","b":"1F1E8-1F1F1","j":["flag","nation","country","banner","chile"]},"flag-cameroon":{"a":"Flag: Cameroon","b":"1F1E8-1F1F2","j":["flag","cm","nation","country","banner","cameroon"]},"flag-china":{"a":"Flag: China","b":"1F1E8-1F1F3","j":["flag","china","chinese","prc","country","nation","banner"]},"flag-colombia":{"a":"Flag: Colombia","b":"1F1E8-1F1F4","j":["flag","co","nation","country","banner","colombia"]},"flag-clipperton-island":{"a":"Flag: Clipperton Island","b":"1F1E8-1F1F5","j":["flag"]},"flag-costa-rica":{"a":"Flag: Costa Rica","b":"1F1E8-1F1F7","j":["flag","costa","rica","nation","country","banner","costa_rica"]},"flag-cuba":{"a":"Flag: Cuba","b":"1F1E8-1F1FA","j":["flag","cu","nation","country","banner","cuba"]},"flag-cape-verde":{"a":"Flag: Cape Verde","b":"1F1E8-1F1FB","j":["flag","cabo","verde","nation","country","banner","cape_verde"]},"flag-curaao":{"a":"Flag: Curaçao","b":"1F1E8-1F1FC","j":["flag","flag_curacao","curaçao","nation","country","banner","curacao"]},"flag-christmas-island":{"a":"Flag: Christmas Island","b":"1F1E8-1F1FD","j":["flag","christmas","island","nation","country","banner","christmas_island"]},"flag-cyprus":{"a":"Flag: Cyprus","b":"1F1E8-1F1FE","j":["flag","cy","nation","country","banner","cyprus"]},"flag-czechia":{"a":"Flag: Czechia","b":"1F1E8-1F1FF","j":["flag","cz","nation","country","banner","czechia"]},"flag-germany":{"a":"Flag: Germany","b":"1F1E9-1F1EA","j":["flag","german","nation","country","banner","germany"]},"flag-diego-garcia":{"a":"Flag: Diego Garcia","b":"1F1E9-1F1EC","j":["flag"]},"flag-djibouti":{"a":"Flag: Djibouti","b":"1F1E9-1F1EF","j":["flag","dj","nation","country","banner","djibouti"]},"flag-denmark":{"a":"Flag: Denmark","b":"1F1E9-1F1F0","j":["flag","dk","nation","country","banner","denmark"]},"flag-dominica":{"a":"Flag: Dominica","b":"1F1E9-1F1F2","j":["flag","dm","nation","country","banner","dominica"]},"flag-dominican-republic":{"a":"Flag: Dominican Republic","b":"1F1E9-1F1F4","j":["flag","dominican","republic","nation","country","banner","dominican_republic"]},"flag-algeria":{"a":"Flag: Algeria","b":"1F1E9-1F1FF","j":["flag","dz","nation","country","banner","algeria"]},"flag-ceuta--melilla":{"a":"Flag: Ceuta & Melilla","b":"1F1EA-1F1E6","j":["flag","flag_ceuta_melilla"]},"flag-ecuador":{"a":"Flag: Ecuador","b":"1F1EA-1F1E8","j":["flag","ec","nation","country","banner","ecuador"]},"flag-estonia":{"a":"Flag: Estonia","b":"1F1EA-1F1EA","j":["flag","ee","nation","country","banner","estonia"]},"flag-egypt":{"a":"Flag: Egypt","b":"1F1EA-1F1EC","j":["flag","eg","nation","country","banner","egypt"]},"flag-western-sahara":{"a":"Flag: Western Sahara","b":"1F1EA-1F1ED","j":["flag","western","sahara","nation","country","banner","western_sahara"]},"flag-eritrea":{"a":"Flag: Eritrea","b":"1F1EA-1F1F7","j":["flag","er","nation","country","banner","eritrea"]},"flag-spain":{"a":"Flag: Spain","b":"1F1EA-1F1F8","j":["flag","spain","nation","country","banner"]},"flag-ethiopia":{"a":"Flag: Ethiopia","b":"1F1EA-1F1F9","j":["flag","et","nation","country","banner","ethiopia"]},"flag-european-union":{"a":"Flag: European Union","b":"1F1EA-1F1FA","j":["flag","european","union","banner"]},"flag-finland":{"a":"Flag: Finland","b":"1F1EB-1F1EE","j":["flag","fi","nation","country","banner","finland"]},"flag-fiji":{"a":"Flag: Fiji","b":"1F1EB-1F1EF","j":["flag","fj","nation","country","banner","fiji"]},"flag-falkland-islands":{"a":"Flag: Falkland Islands","b":"1F1EB-1F1F0","j":["flag","falkland","islands","malvinas","nation","country","banner","falkland_islands"]},"flag-micronesia":{"a":"Flag: Micronesia","b":"1F1EB-1F1F2","j":["flag","micronesia","federated","states","nation","country","banner"]},"flag-faroe-islands":{"a":"Flag: Faroe Islands","b":"1F1EB-1F1F4","j":["flag","faroe","islands","nation","country","banner","faroe_islands"]},"flag-france":{"a":"Flag: France","b":"1F1EB-1F1F7","j":["flag","banner","nation","france","french","country"]},"flag-gabon":{"a":"Flag: Gabon","b":"1F1EC-1F1E6","j":["flag","ga","nation","country","banner","gabon"]},"flag-united-kingdom":{"a":"Flag: United Kingdom","b":"1F1EC-1F1E7","j":["flag","united","kingdom","great","britain","northern","ireland","nation","country","banner","british","UK","english","england","union jack","united_kingdom"]},"flag-grenada":{"a":"Flag: Grenada","b":"1F1EC-1F1E9","j":["flag","gd","nation","country","banner","grenada"]},"flag-georgia":{"a":"Flag: Georgia","b":"1F1EC-1F1EA","j":["flag","ge","nation","country","banner","georgia"]},"flag-french-guiana":{"a":"Flag: French Guiana","b":"1F1EC-1F1EB","j":["flag","french","guiana","nation","country","banner","french_guiana"]},"flag-guernsey":{"a":"Flag: Guernsey","b":"1F1EC-1F1EC","j":["flag","gg","nation","country","banner","guernsey"]},"flag-ghana":{"a":"Flag: Ghana","b":"1F1EC-1F1ED","j":["flag","gh","nation","country","banner","ghana"]},"flag-gibraltar":{"a":"Flag: Gibraltar","b":"1F1EC-1F1EE","j":["flag","gi","nation","country","banner","gibraltar"]},"flag-greenland":{"a":"Flag: Greenland","b":"1F1EC-1F1F1","j":["flag","gl","nation","country","banner","greenland"]},"flag-gambia":{"a":"Flag: Gambia","b":"1F1EC-1F1F2","j":["flag","gm","nation","country","banner","gambia"]},"flag-guinea":{"a":"Flag: Guinea","b":"1F1EC-1F1F3","j":["flag","gn","nation","country","banner","guinea"]},"flag-guadeloupe":{"a":"Flag: Guadeloupe","b":"1F1EC-1F1F5","j":["flag","gp","nation","country","banner","guadeloupe"]},"flag-equatorial-guinea":{"a":"Flag: Equatorial Guinea","b":"1F1EC-1F1F6","j":["flag","equatorial","gn","nation","country","banner","equatorial_guinea"]},"flag-greece":{"a":"Flag: Greece","b":"1F1EC-1F1F7","j":["flag","gr","nation","country","banner","greece"]},"flag-south-georgia--south-sandwich-islands":{"a":"Flag: South Georgia & South Sandwich Islands","b":"1F1EC-1F1F8","j":["flag","flag_south_georgia_south_sandwich_islands","south","georgia","sandwich","islands","nation","country","banner","south_georgia_south_sandwich_islands"]},"flag-guatemala":{"a":"Flag: Guatemala","b":"1F1EC-1F1F9","j":["flag","gt","nation","country","banner","guatemala"]},"flag-guam":{"a":"Flag: Guam","b":"1F1EC-1F1FA","j":["flag","gu","nation","country","banner","guam"]},"flag-guineabissau":{"a":"Flag: Guinea-Bissau","b":"1F1EC-1F1FC","j":["flag","flag_guinea_bissau","gw","bissau","nation","country","banner","guinea_bissau"]},"flag-guyana":{"a":"Flag: Guyana","b":"1F1EC-1F1FE","j":["flag","gy","nation","country","banner","guyana"]},"flag-hong-kong-sar-china":{"a":"Flag: Hong Kong Sar China","b":"1F1ED-1F1F0","j":["flag","hong","kong","nation","country","banner","hong_kong_sar_china"]},"flag-heard--mcdonald-islands":{"a":"Flag: Heard & Mcdonald Islands","b":"1F1ED-1F1F2","j":["flag","flag_heard_mcdonald_islands"]},"flag-honduras":{"a":"Flag: Honduras","b":"1F1ED-1F1F3","j":["flag","hn","nation","country","banner","honduras"]},"flag-croatia":{"a":"Flag: Croatia","b":"1F1ED-1F1F7","j":["flag","hr","nation","country","banner","croatia"]},"flag-haiti":{"a":"Flag: Haiti","b":"1F1ED-1F1F9","j":["flag","ht","nation","country","banner","haiti"]},"flag-hungary":{"a":"Flag: Hungary","b":"1F1ED-1F1FA","j":["flag","hu","nation","country","banner","hungary"]},"flag-canary-islands":{"a":"Flag: Canary Islands","b":"1F1EE-1F1E8","j":["flag","canary","islands","nation","country","banner","canary_islands"]},"flag-indonesia":{"a":"Flag: Indonesia","b":"1F1EE-1F1E9","j":["flag","nation","country","banner","indonesia"]},"flag-ireland":{"a":"Flag: Ireland","b":"1F1EE-1F1EA","j":["flag","ie","nation","country","banner","ireland"]},"flag-israel":{"a":"Flag: Israel","b":"1F1EE-1F1F1","j":["flag","il","nation","country","banner","israel"]},"flag-isle-of-man":{"a":"Flag: Isle of Man","b":"1F1EE-1F1F2","j":["flag","isle","man","nation","country","banner","isle_of_man"]},"flag-india":{"a":"Flag: India","b":"1F1EE-1F1F3","j":["flag","in","nation","country","banner","india"]},"flag-british-indian-ocean-territory":{"a":"Flag: British Indian Ocean Territory","b":"1F1EE-1F1F4","j":["flag","british","indian","ocean","territory","nation","country","banner","british_indian_ocean_territory"]},"flag-iraq":{"a":"Flag: Iraq","b":"1F1EE-1F1F6","j":["flag","iq","nation","country","banner","iraq"]},"flag-iran":{"a":"Flag: Iran","b":"1F1EE-1F1F7","j":["flag","iran","islamic","republic","nation","country","banner"]},"flag-iceland":{"a":"Flag: Iceland","b":"1F1EE-1F1F8","j":["flag","is","nation","country","banner","iceland"]},"flag-italy":{"a":"Flag: Italy","b":"1F1EE-1F1F9","j":["flag","italy","nation","country","banner"]},"flag-jersey":{"a":"Flag: Jersey","b":"1F1EF-1F1EA","j":["flag","je","nation","country","banner","jersey"]},"flag-jamaica":{"a":"Flag: Jamaica","b":"1F1EF-1F1F2","j":["flag","jm","nation","country","banner","jamaica"]},"flag-jordan":{"a":"Flag: Jordan","b":"1F1EF-1F1F4","j":["flag","jo","nation","country","banner","jordan"]},"flag-japan":{"a":"Flag: Japan","b":"1F1EF-1F1F5","j":["flag","japanese","nation","country","banner","japan"]},"flag-kenya":{"a":"Flag: Kenya","b":"1F1F0-1F1EA","j":["flag","ke","nation","country","banner","kenya"]},"flag-kyrgyzstan":{"a":"Flag: Kyrgyzstan","b":"1F1F0-1F1EC","j":["flag","kg","nation","country","banner","kyrgyzstan"]},"flag-cambodia":{"a":"Flag: Cambodia","b":"1F1F0-1F1ED","j":["flag","kh","nation","country","banner","cambodia"]},"flag-kiribati":{"a":"Flag: Kiribati","b":"1F1F0-1F1EE","j":["flag","ki","nation","country","banner","kiribati"]},"flag-comoros":{"a":"Flag: Comoros","b":"1F1F0-1F1F2","j":["flag","km","nation","country","banner","comoros"]},"flag-st-kitts--nevis":{"a":"Flag: St. Kitts & Nevis","b":"1F1F0-1F1F3","j":["flag","flag_st_kitts_nevis","saint","kitts","nevis","nation","country","banner","st_kitts_nevis"]},"flag-north-korea":{"a":"Flag: North Korea","b":"1F1F0-1F1F5","j":["flag","north","korea","nation","country","banner","north_korea"]},"flag-south-korea":{"a":"Flag: South Korea","b":"1F1F0-1F1F7","j":["flag","south","korea","nation","country","banner","south_korea"]},"flag-kuwait":{"a":"Flag: Kuwait","b":"1F1F0-1F1FC","j":["flag","kw","nation","country","banner","kuwait"]},"flag-cayman-islands":{"a":"Flag: Cayman Islands","b":"1F1F0-1F1FE","j":["flag","cayman","islands","nation","country","banner","cayman_islands"]},"flag-kazakhstan":{"a":"Flag: Kazakhstan","b":"1F1F0-1F1FF","j":["flag","kz","nation","country","banner","kazakhstan"]},"flag-laos":{"a":"Flag: Laos","b":"1F1F1-1F1E6","j":["flag","lao","democratic","republic","nation","country","banner","laos"]},"flag-lebanon":{"a":"Flag: Lebanon","b":"1F1F1-1F1E7","j":["flag","lb","nation","country","banner","lebanon"]},"flag-st-lucia":{"a":"Flag: St. Lucia","b":"1F1F1-1F1E8","j":["flag","saint","lucia","nation","country","banner","st_lucia"]},"flag-liechtenstein":{"a":"Flag: Liechtenstein","b":"1F1F1-1F1EE","j":["flag","li","nation","country","banner","liechtenstein"]},"flag-sri-lanka":{"a":"Flag: Sri Lanka","b":"1F1F1-1F1F0","j":["flag","sri","lanka","nation","country","banner","sri_lanka"]},"flag-liberia":{"a":"Flag: Liberia","b":"1F1F1-1F1F7","j":["flag","lr","nation","country","banner","liberia"]},"flag-lesotho":{"a":"Flag: Lesotho","b":"1F1F1-1F1F8","j":["flag","ls","nation","country","banner","lesotho"]},"flag-lithuania":{"a":"Flag: Lithuania","b":"1F1F1-1F1F9","j":["flag","lt","nation","country","banner","lithuania"]},"flag-luxembourg":{"a":"Flag: Luxembourg","b":"1F1F1-1F1FA","j":["flag","lu","nation","country","banner","luxembourg"]},"flag-latvia":{"a":"Flag: Latvia","b":"1F1F1-1F1FB","j":["flag","lv","nation","country","banner","latvia"]},"flag-libya":{"a":"Flag: Libya","b":"1F1F1-1F1FE","j":["flag","ly","nation","country","banner","libya"]},"flag-morocco":{"a":"Flag: Morocco","b":"1F1F2-1F1E6","j":["flag","ma","nation","country","banner","morocco"]},"flag-monaco":{"a":"Flag: Monaco","b":"1F1F2-1F1E8","j":["flag","mc","nation","country","banner","monaco"]},"flag-moldova":{"a":"Flag: Moldova","b":"1F1F2-1F1E9","j":["flag","moldova","republic","nation","country","banner"]},"flag-montenegro":{"a":"Flag: Montenegro","b":"1F1F2-1F1EA","j":["flag","me","nation","country","banner","montenegro"]},"flag-st-martin":{"a":"Flag: St. Martin","b":"1F1F2-1F1EB","j":["flag"]},"flag-madagascar":{"a":"Flag: Madagascar","b":"1F1F2-1F1EC","j":["flag","mg","nation","country","banner","madagascar"]},"flag-marshall-islands":{"a":"Flag: Marshall Islands","b":"1F1F2-1F1ED","j":["flag","marshall","islands","nation","country","banner","marshall_islands"]},"flag-north-macedonia":{"a":"Flag: North Macedonia","b":"1F1F2-1F1F0","j":["flag","macedonia","nation","country","banner","north_macedonia"]},"flag-mali":{"a":"Flag: Mali","b":"1F1F2-1F1F1","j":["flag","ml","nation","country","banner","mali"]},"flag-myanmar-burma":{"a":"Flag: Myanmar (Burma)","b":"1F1F2-1F1F2","j":["flag","flag_myanmar","mm","nation","country","banner","myanmar"]},"flag-mongolia":{"a":"Flag: Mongolia","b":"1F1F2-1F1F3","j":["flag","mn","nation","country","banner","mongolia"]},"flag-macao-sar-china":{"a":"Flag: Macao Sar China","b":"1F1F2-1F1F4","j":["flag","macao","nation","country","banner","macao_sar_china"]},"flag-northern-mariana-islands":{"a":"Flag: Northern Mariana Islands","b":"1F1F2-1F1F5","j":["flag","northern","mariana","islands","nation","country","banner","northern_mariana_islands"]},"flag-martinique":{"a":"Flag: Martinique","b":"1F1F2-1F1F6","j":["flag","mq","nation","country","banner","martinique"]},"flag-mauritania":{"a":"Flag: Mauritania","b":"1F1F2-1F1F7","j":["flag","mr","nation","country","banner","mauritania"]},"flag-montserrat":{"a":"Flag: Montserrat","b":"1F1F2-1F1F8","j":["flag","ms","nation","country","banner","montserrat"]},"flag-malta":{"a":"Flag: Malta","b":"1F1F2-1F1F9","j":["flag","mt","nation","country","banner","malta"]},"flag-mauritius":{"a":"Flag: Mauritius","b":"1F1F2-1F1FA","j":["flag","mu","nation","country","banner","mauritius"]},"flag-maldives":{"a":"Flag: Maldives","b":"1F1F2-1F1FB","j":["flag","mv","nation","country","banner","maldives"]},"flag-malawi":{"a":"Flag: Malawi","b":"1F1F2-1F1FC","j":["flag","mw","nation","country","banner","malawi"]},"flag-mexico":{"a":"Flag: Mexico","b":"1F1F2-1F1FD","j":["flag","mx","nation","country","banner","mexico"]},"flag-malaysia":{"a":"Flag: Malaysia","b":"1F1F2-1F1FE","j":["flag","my","nation","country","banner","malaysia"]},"flag-mozambique":{"a":"Flag: Mozambique","b":"1F1F2-1F1FF","j":["flag","mz","nation","country","banner","mozambique"]},"flag-namibia":{"a":"Flag: Namibia","b":"1F1F3-1F1E6","j":["flag","na","nation","country","banner","namibia"]},"flag-new-caledonia":{"a":"Flag: New Caledonia","b":"1F1F3-1F1E8","j":["flag","new","caledonia","nation","country","banner","new_caledonia"]},"flag-niger":{"a":"Flag: Niger","b":"1F1F3-1F1EA","j":["flag","ne","nation","country","banner","niger"]},"flag-norfolk-island":{"a":"Flag: Norfolk Island","b":"1F1F3-1F1EB","j":["flag","norfolk","island","nation","country","banner","norfolk_island"]},"flag-nigeria":{"a":"Flag: Nigeria","b":"1F1F3-1F1EC","j":["flag","nation","country","banner","nigeria"]},"flag-nicaragua":{"a":"Flag: Nicaragua","b":"1F1F3-1F1EE","j":["flag","ni","nation","country","banner","nicaragua"]},"flag-netherlands":{"a":"Flag: Netherlands","b":"1F1F3-1F1F1","j":["flag","nl","nation","country","banner","netherlands"]},"flag-norway":{"a":"Flag: Norway","b":"1F1F3-1F1F4","j":["flag","no","nation","country","banner","norway"]},"flag-nepal":{"a":"Flag: Nepal","b":"1F1F3-1F1F5","j":["flag","np","nation","country","banner","nepal"]},"flag-nauru":{"a":"Flag: Nauru","b":"1F1F3-1F1F7","j":["flag","nr","nation","country","banner","nauru"]},"flag-niue":{"a":"Flag: Niue","b":"1F1F3-1F1FA","j":["flag","nu","nation","country","banner","niue"]},"flag-new-zealand":{"a":"Flag: New Zealand","b":"1F1F3-1F1FF","j":["flag","new","zealand","nation","country","banner","new_zealand"]},"flag-oman":{"a":"Flag: Oman","b":"1F1F4-1F1F2","j":["flag","om_symbol","nation","country","banner","oman"]},"flag-panama":{"a":"Flag: Panama","b":"1F1F5-1F1E6","j":["flag","pa","nation","country","banner","panama"]},"flag-peru":{"a":"Flag: Peru","b":"1F1F5-1F1EA","j":["flag","pe","nation","country","banner","peru"]},"flag-french-polynesia":{"a":"Flag: French Polynesia","b":"1F1F5-1F1EB","j":["flag","french","polynesia","nation","country","banner","french_polynesia"]},"flag-papua-new-guinea":{"a":"Flag: Papua New Guinea","b":"1F1F5-1F1EC","j":["flag","papua","new","guinea","nation","country","banner","papua_new_guinea"]},"flag-philippines":{"a":"Flag: Philippines","b":"1F1F5-1F1ED","j":["flag","ph","nation","country","banner","philippines"]},"flag-pakistan":{"a":"Flag: Pakistan","b":"1F1F5-1F1F0","j":["flag","pk","nation","country","banner","pakistan"]},"flag-poland":{"a":"Flag: Poland","b":"1F1F5-1F1F1","j":["flag","pl","nation","country","banner","poland"]},"flag-st-pierre--miquelon":{"a":"Flag: St. Pierre & Miquelon","b":"1F1F5-1F1F2","j":["flag","flag_st_pierre_miquelon","saint","pierre","miquelon","nation","country","banner","st_pierre_miquelon"]},"flag-pitcairn-islands":{"a":"Flag: Pitcairn Islands","b":"1F1F5-1F1F3","j":["flag","pitcairn","nation","country","banner","pitcairn_islands"]},"flag-puerto-rico":{"a":"Flag: Puerto Rico","b":"1F1F5-1F1F7","j":["flag","puerto","rico","nation","country","banner","puerto_rico"]},"flag-palestinian-territories":{"a":"Flag: Palestinian Territories","b":"1F1F5-1F1F8","j":["flag","palestine","palestinian","territories","nation","country","banner","palestinian_territories"]},"flag-portugal":{"a":"Flag: Portugal","b":"1F1F5-1F1F9","j":["flag","pt","nation","country","banner","portugal"]},"flag-palau":{"a":"Flag: Palau","b":"1F1F5-1F1FC","j":["flag","pw","nation","country","banner","palau"]},"flag-paraguay":{"a":"Flag: Paraguay","b":"1F1F5-1F1FE","j":["flag","py","nation","country","banner","paraguay"]},"flag-qatar":{"a":"Flag: Qatar","b":"1F1F6-1F1E6","j":["flag","qa","nation","country","banner","qatar"]},"flag-runion":{"a":"Flag: Réunion","b":"1F1F7-1F1EA","j":["flag","flag_reunion","réunion","nation","country","banner","reunion"]},"flag-romania":{"a":"Flag: Romania","b":"1F1F7-1F1F4","j":["flag","ro","nation","country","banner","romania"]},"flag-serbia":{"a":"Flag: Serbia","b":"1F1F7-1F1F8","j":["flag","rs","nation","country","banner","serbia"]},"flag-russia":{"a":"Flag: Russia","b":"1F1F7-1F1FA","j":["flag","russian","federation","nation","country","banner","russia"]},"flag-rwanda":{"a":"Flag: Rwanda","b":"1F1F7-1F1FC","j":["flag","rw","nation","country","banner","rwanda"]},"flag-saudi-arabia":{"a":"Flag: Saudi Arabia","b":"1F1F8-1F1E6","j":["flag","nation","country","banner","saudi_arabia"]},"flag-solomon-islands":{"a":"Flag: Solomon Islands","b":"1F1F8-1F1E7","j":["flag","solomon","islands","nation","country","banner","solomon_islands"]},"flag-seychelles":{"a":"Flag: Seychelles","b":"1F1F8-1F1E8","j":["flag","sc","nation","country","banner","seychelles"]},"flag-sudan":{"a":"Flag: Sudan","b":"1F1F8-1F1E9","j":["flag","sd","nation","country","banner","sudan"]},"flag-sweden":{"a":"Flag: Sweden","b":"1F1F8-1F1EA","j":["flag","se","nation","country","banner","sweden"]},"flag-singapore":{"a":"Flag: Singapore","b":"1F1F8-1F1EC","j":["flag","sg","nation","country","banner","singapore"]},"flag-st-helena":{"a":"Flag: St. Helena","b":"1F1F8-1F1ED","j":["flag","saint","helena","ascension","tristan","cunha","nation","country","banner","st_helena"]},"flag-slovenia":{"a":"Flag: Slovenia","b":"1F1F8-1F1EE","j":["flag","si","nation","country","banner","slovenia"]},"flag-svalbard--jan-mayen":{"a":"Flag: Svalbard & Jan Mayen","b":"1F1F8-1F1EF","j":["flag","flag_svalbard_jan_mayen"]},"flag-slovakia":{"a":"Flag: Slovakia","b":"1F1F8-1F1F0","j":["flag","sk","nation","country","banner","slovakia"]},"flag-sierra-leone":{"a":"Flag: Sierra Leone","b":"1F1F8-1F1F1","j":["flag","sierra","leone","nation","country","banner","sierra_leone"]},"flag-san-marino":{"a":"Flag: San Marino","b":"1F1F8-1F1F2","j":["flag","san","marino","nation","country","banner","san_marino"]},"flag-senegal":{"a":"Flag: Senegal","b":"1F1F8-1F1F3","j":["flag","sn","nation","country","banner","senegal"]},"flag-somalia":{"a":"Flag: Somalia","b":"1F1F8-1F1F4","j":["flag","so","nation","country","banner","somalia"]},"flag-suriname":{"a":"Flag: Suriname","b":"1F1F8-1F1F7","j":["flag","sr","nation","country","banner","suriname"]},"flag-south-sudan":{"a":"Flag: South Sudan","b":"1F1F8-1F1F8","j":["flag","south","sd","nation","country","banner","south_sudan"]},"flag-so-tom--prncipe":{"a":"Flag: São Tomé & Príncipe","b":"1F1F8-1F1F9","j":["flag","flag_sao_tome_principe","sao","tome","principe","nation","country","banner","sao_tome_principe"]},"flag-el-salvador":{"a":"Flag: El Salvador","b":"1F1F8-1F1FB","j":["flag","el","salvador","nation","country","banner","el_salvador"]},"flag-sint-maarten":{"a":"Flag: Sint Maarten","b":"1F1F8-1F1FD","j":["flag","sint","maarten","dutch","nation","country","banner","sint_maarten"]},"flag-syria":{"a":"Flag: Syria","b":"1F1F8-1F1FE","j":["flag","syrian","arab","republic","nation","country","banner","syria"]},"flag-eswatini":{"a":"Flag: Eswatini","b":"1F1F8-1F1FF","j":["flag","sz","nation","country","banner","eswatini"]},"flag-tristan-da-cunha":{"a":"Flag: Tristan Da Cunha","b":"1F1F9-1F1E6","j":["flag"]},"flag-turks--caicos-islands":{"a":"Flag: Turks & Caicos Islands","b":"1F1F9-1F1E8","j":["flag","flag_turks_caicos_islands","turks","caicos","islands","nation","country","banner","turks_caicos_islands"]},"flag-chad":{"a":"Flag: Chad","b":"1F1F9-1F1E9","j":["flag","td","nation","country","banner","chad"]},"flag-french-southern-territories":{"a":"Flag: French Southern Territories","b":"1F1F9-1F1EB","j":["flag","french","southern","territories","nation","country","banner","french_southern_territories"]},"flag-togo":{"a":"Flag: Togo","b":"1F1F9-1F1EC","j":["flag","tg","nation","country","banner","togo"]},"flag-thailand":{"a":"Flag: Thailand","b":"1F1F9-1F1ED","j":["flag","th","nation","country","banner","thailand"]},"flag-tajikistan":{"a":"Flag: Tajikistan","b":"1F1F9-1F1EF","j":["flag","tj","nation","country","banner","tajikistan"]},"flag-tokelau":{"a":"Flag: Tokelau","b":"1F1F9-1F1F0","j":["flag","tk","nation","country","banner","tokelau"]},"flag-timorleste":{"a":"Flag: Timor-Leste","b":"1F1F9-1F1F1","j":["flag","flag_timor_leste","timor","leste","nation","country","banner","timor_leste"]},"flag-turkmenistan":{"a":"Flag: Turkmenistan","b":"1F1F9-1F1F2","j":["flag","nation","country","banner","turkmenistan"]},"flag-tunisia":{"a":"Flag: Tunisia","b":"1F1F9-1F1F3","j":["flag","tn","nation","country","banner","tunisia"]},"flag-tonga":{"a":"Flag: Tonga","b":"1F1F9-1F1F4","j":["flag","to","nation","country","banner","tonga"]},"flag-turkey":{"a":"Flag: Turkey","b":"1F1F9-1F1F7","j":["flag","turkey","nation","country","banner"]},"flag-trinidad--tobago":{"a":"Flag: Trinidad & Tobago","b":"1F1F9-1F1F9","j":["flag","flag_trinidad_tobago","trinidad","tobago","nation","country","banner","trinidad_tobago"]},"flag-tuvalu":{"a":"Flag: Tuvalu","b":"1F1F9-1F1FB","j":["flag","nation","country","banner","tuvalu"]},"flag-taiwan":{"a":"Flag: Taiwan","b":"1F1F9-1F1FC","j":["flag","tw","nation","country","banner","taiwan"]},"flag-tanzania":{"a":"Flag: Tanzania","b":"1F1F9-1F1FF","j":["flag","tanzania","united","republic","nation","country","banner"]},"flag-ukraine":{"a":"Flag: Ukraine","b":"1F1FA-1F1E6","j":["flag","ua","nation","country","banner","ukraine"]},"flag-uganda":{"a":"Flag: Uganda","b":"1F1FA-1F1EC","j":["flag","ug","nation","country","banner","uganda"]},"flag-us-outlying-islands":{"a":"Flag: U.S. Outlying Islands","b":"1F1FA-1F1F2","j":["flag","flag_u_s_outlying_islands"]},"flag-united-nations":{"a":"Flag: United Nations","b":"1F1FA-1F1F3","j":["flag","un","banner"]},"flag-united-states":{"a":"Flag: United States","b":"1F1FA-1F1F8","j":["flag","united","states","america","nation","country","banner","united_states"]},"flag-uruguay":{"a":"Flag: Uruguay","b":"1F1FA-1F1FE","j":["flag","uy","nation","country","banner","uruguay"]},"flag-uzbekistan":{"a":"Flag: Uzbekistan","b":"1F1FA-1F1FF","j":["flag","uz","nation","country","banner","uzbekistan"]},"flag-vatican-city":{"a":"Flag: Vatican City","b":"1F1FB-1F1E6","j":["flag","vatican","city","nation","country","banner","vatican_city"]},"flag-st-vincent--grenadines":{"a":"Flag: St. Vincent & Grenadines","b":"1F1FB-1F1E8","j":["flag","flag_st_vincent_grenadines","saint","vincent","grenadines","nation","country","banner","st_vincent_grenadines"]},"flag-venezuela":{"a":"Flag: Venezuela","b":"1F1FB-1F1EA","j":["flag","ve","bolivarian","republic","nation","country","banner","venezuela"]},"flag-british-virgin-islands":{"a":"Flag: British Virgin Islands","b":"1F1FB-1F1EC","j":["flag","british","virgin","islands","bvi","nation","country","banner","british_virgin_islands"]},"flag-us-virgin-islands":{"a":"Flag: U.S. Virgin Islands","b":"1F1FB-1F1EE","j":["flag","flag_u_s_virgin_islands","virgin","islands","us","nation","country","banner","u_s_virgin_islands"]},"flag-vietnam":{"a":"Flag: Vietnam","b":"1F1FB-1F1F3","j":["flag","viet","nam","nation","country","banner","vietnam"]},"flag-vanuatu":{"a":"Flag: Vanuatu","b":"1F1FB-1F1FA","j":["flag","vu","nation","country","banner","vanuatu"]},"flag-wallis--futuna":{"a":"Flag: Wallis & Futuna","b":"1F1FC-1F1EB","j":["flag","flag_wallis_futuna","wallis","futuna","nation","country","banner","wallis_futuna"]},"flag-samoa":{"a":"Flag: Samoa","b":"1F1FC-1F1F8","j":["flag","ws","nation","country","banner","samoa"]},"flag-kosovo":{"a":"Flag: Kosovo","b":"1F1FD-1F1F0","j":["flag","xk","nation","country","banner","kosovo"]},"flag-yemen":{"a":"Flag: Yemen","b":"1F1FE-1F1EA","j":["flag","ye","nation","country","banner","yemen"]},"flag-mayotte":{"a":"Flag: Mayotte","b":"1F1FE-1F1F9","j":["flag","yt","nation","country","banner","mayotte"]},"flag-south-africa":{"a":"Flag: South Africa","b":"1F1FF-1F1E6","j":["flag","south","africa","nation","country","banner","south_africa"]},"flag-zambia":{"a":"Flag: Zambia","b":"1F1FF-1F1F2","j":["flag","zm","nation","country","banner","zambia"]},"flag-zimbabwe":{"a":"Flag: Zimbabwe","b":"1F1FF-1F1FC","j":["flag","zw","nation","country","banner","zimbabwe"]},"flag-england":{"a":"Flag: England","b":"1F3F4-E0067-E0062-E0065-E006E-E0067-E007F","j":["flag","english"]},"flag-scotland":{"a":"Flag: Scotland","b":"1F3F4-E0067-E0062-E0073-E0063-E0074-E007F","j":["flag","scottish"]},"flag-wales":{"a":"Flag: Wales","b":"1F3F4-E0067-E0062-E0077-E006C-E0073-E007F","j":["flag","welsh"]}},"aliases":{}}
\ No newline at end of file
+{"compressed":true,"categories":[{"id":"smileys_&_emotion","name":"Smileys & Emotion","emojis":["grinning-face","grinning-face-with-big-eyes","grinning-face-with-smiling-eyes","beaming-face-with-smiling-eyes","grinning-squinting-face","grinning-face-with-sweat","rolling-on-the-floor-laughing","face-with-tears-of-joy","slightly-smiling-face","upsidedown-face","melting-face","winking-face","smiling-face-with-smiling-eyes","smiling-face-with-halo","smiling-face-with-hearts","smiling-face-with-hearteyes","starstruck","face-blowing-a-kiss","kissing-face","smiling-face","kissing-face-with-closed-eyes","kissing-face-with-smiling-eyes","smiling-face-with-tear","face-savoring-food","face-with-tongue","winking-face-with-tongue","zany-face","squinting-face-with-tongue","moneymouth-face","smiling-face-with-open-hands","face-with-hand-over-mouth","face-with-open-eyes-and-hand-over-mouth","face-with-peeking-eye","shushing-face","thinking-face","saluting-face","zippermouth-face","face-with-raised-eyebrow","neutral-face","expressionless-face","face-without-mouth","dotted-line-face","face-in-clouds","smirking-face","unamused-face","face-with-rolling-eyes","grimacing-face","face-exhaling","lying-face","relieved-face","pensive-face","sleepy-face","drooling-face","sleeping-face","face-with-medical-mask","face-with-thermometer","face-with-headbandage","nauseated-face","face-vomiting","sneezing-face","hot-face","cold-face","woozy-face","face-with-crossedout-eyes","face-with-spiral-eyes","exploding-head","cowboy-hat-face","partying-face","disguised-face","smiling-face-with-sunglasses","nerd-face","face-with-monocle","confused-face","face-with-diagonal-mouth","worried-face","slightly-frowning-face","frowning-face","face-with-open-mouth","hushed-face","astonished-face","flushed-face","pleading-face","face-holding-back-tears","frowning-face-with-open-mouth","anguished-face","fearful-face","anxious-face-with-sweat","sad-but-relieved-face","crying-face","loudly-crying-face","face-screaming-in-fear","confounded-face","persevering-face","disappointed-face","downcast-face-with-sweat","weary-face","tired-face","yawning-face","face-with-steam-from-nose","pouting-face","angry-face","face-with-symbols-on-mouth","smiling-face-with-horns","angry-face-with-horns","skull","skull-and-crossbones","pile-of-poo","clown-face","ogre","goblin","ghost","alien","alien-monster","robot","grinning-cat","grinning-cat-with-smiling-eyes","cat-with-tears-of-joy","smiling-cat-with-hearteyes","cat-with-wry-smile","kissing-cat","weary-cat","crying-cat","pouting-cat","seenoevil-monkey","hearnoevil-monkey","speaknoevil-monkey","kiss-mark","love-letter","heart-with-arrow","heart-with-ribbon","sparkling-heart","growing-heart","beating-heart","revolving-hearts","two-hearts","heart-decoration","heart-exclamation","broken-heart","heart-on-fire","mending-heart","red-heart","orange-heart","yellow-heart","green-heart","blue-heart","purple-heart","brown-heart","black-heart","white-heart","hundred-points","anger-symbol","collision","dizzy","sweat-droplets","dashing-away","hole","bomb","speech-balloon","eye-in-speech-bubble","left-speech-bubble","right-anger-bubble","thought-balloon","zzz"]},{"id":"people_&_body","name":"People & Body","emojis":["waving-hand","raised-back-of-hand","hand-with-fingers-splayed","raised-hand","vulcan-salute","rightwards-hand","leftwards-hand","palm-down-hand","palm-up-hand","ok-hand","pinched-fingers","pinching-hand","victory-hand","crossed-fingers","hand-with-index-finger-and-thumb-crossed","loveyou-gesture","sign-of-the-horns","call-me-hand","backhand-index-pointing-left","backhand-index-pointing-right","backhand-index-pointing-up","middle-finger","backhand-index-pointing-down","index-pointing-up","index-pointing-at-the-viewer","thumbs-up","thumbs-down","raised-fist","oncoming-fist","leftfacing-fist","rightfacing-fist","clapping-hands","raising-hands","heart-hands","open-hands","palms-up-together","handshake","folded-hands","writing-hand","nail-polish","selfie","flexed-biceps","mechanical-arm","mechanical-leg","leg","foot","ear","ear-with-hearing-aid","nose","brain","anatomical-heart","lungs","tooth","bone","eyes","eye","tongue","mouth","biting-lip","baby","child","boy","girl","person","person-blond-hair","man","person-beard","man-beard","woman-beard","man-red-hair","man-curly-hair","man-white-hair","man-bald","woman","woman-red-hair","person-red-hair","woman-curly-hair","person-curly-hair","woman-white-hair","person-white-hair","woman-bald","person-bald","woman-blond-hair","man-blond-hair","older-person","old-man","old-woman","person-frowning","man-frowning","woman-frowning","person-pouting","man-pouting","woman-pouting","person-gesturing-no","man-gesturing-no","woman-gesturing-no","person-gesturing-ok","man-gesturing-ok","woman-gesturing-ok","person-tipping-hand","man-tipping-hand","woman-tipping-hand","person-raising-hand","man-raising-hand","woman-raising-hand","deaf-person","deaf-man","deaf-woman","person-bowing","man-bowing","woman-bowing","person-facepalming","man-facepalming","woman-facepalming","person-shrugging","man-shrugging","woman-shrugging","health-worker","man-health-worker","woman-health-worker","student","man-student","woman-student","teacher","man-teacher","woman-teacher","judge","man-judge","woman-judge","farmer","man-farmer","woman-farmer","cook","man-cook","woman-cook","mechanic","man-mechanic","woman-mechanic","factory-worker","man-factory-worker","woman-factory-worker","office-worker","man-office-worker","woman-office-worker","scientist","man-scientist","woman-scientist","technologist","man-technologist","woman-technologist","singer","man-singer","woman-singer","artist","man-artist","woman-artist","pilot","man-pilot","woman-pilot","astronaut","man-astronaut","woman-astronaut","firefighter","man-firefighter","woman-firefighter","police-officer","man-police-officer","woman-police-officer","detective","man-detective","woman-detective","guard","man-guard","woman-guard","ninja","construction-worker","man-construction-worker","woman-construction-worker","person-with-crown","prince","princess","person-wearing-turban","man-wearing-turban","woman-wearing-turban","person-with-skullcap","woman-with-headscarf","person-in-tuxedo","man-in-tuxedo","woman-in-tuxedo","person-with-veil","man-with-veil","woman-with-veil","pregnant-woman","pregnant-man","pregnant-person","breastfeeding","woman-feeding-baby","man-feeding-baby","person-feeding-baby","baby-angel","santa-claus","mrs-claus","mx-claus","superhero","man-superhero","woman-superhero","supervillain","man-supervillain","woman-supervillain","mage","man-mage","woman-mage","fairy","man-fairy","woman-fairy","vampire","man-vampire","woman-vampire","merperson","merman","mermaid","elf","man-elf","woman-elf","genie","man-genie","woman-genie","zombie","man-zombie","woman-zombie","troll","person-getting-massage","man-getting-massage","woman-getting-massage","person-getting-haircut","man-getting-haircut","woman-getting-haircut","person-walking","man-walking","woman-walking","person-standing","man-standing","woman-standing","person-kneeling","man-kneeling","woman-kneeling","person-with-white-cane","man-with-white-cane","woman-with-white-cane","person-in-motorized-wheelchair","man-in-motorized-wheelchair","woman-in-motorized-wheelchair","person-in-manual-wheelchair","man-in-manual-wheelchair","woman-in-manual-wheelchair","person-running","man-running","woman-running","woman-dancing","man-dancing","person-in-suit-levitating","people-with-bunny-ears","men-with-bunny-ears","women-with-bunny-ears","person-in-steamy-room","man-in-steamy-room","woman-in-steamy-room","person-climbing","man-climbing","woman-climbing","person-fencing","horse-racing","skier","snowboarder","person-golfing","man-golfing","woman-golfing","person-surfing","man-surfing","woman-surfing","person-rowing-boat","man-rowing-boat","woman-rowing-boat","person-swimming","man-swimming","woman-swimming","person-bouncing-ball","man-bouncing-ball","woman-bouncing-ball","person-lifting-weights","man-lifting-weights","woman-lifting-weights","person-biking","man-biking","woman-biking","person-mountain-biking","man-mountain-biking","woman-mountain-biking","person-cartwheeling","man-cartwheeling","woman-cartwheeling","people-wrestling","men-wrestling","women-wrestling","person-playing-water-polo","man-playing-water-polo","woman-playing-water-polo","person-playing-handball","man-playing-handball","woman-playing-handball","person-juggling","man-juggling","woman-juggling","person-in-lotus-position","man-in-lotus-position","woman-in-lotus-position","person-taking-bath","person-in-bed","people-holding-hands","women-holding-hands","woman-and-man-holding-hands","men-holding-hands","kiss","kiss-woman-man","kiss-man-man","kiss-woman-woman","couple-with-heart","couple-with-heart-woman-man","couple-with-heart-man-man","couple-with-heart-woman-woman","family","family-man-woman-boy","family-man-woman-girl","family-man-woman-girl-boy","family-man-woman-boy-boy","family-man-woman-girl-girl","family-man-man-boy","family-man-man-girl","family-man-man-girl-boy","family-man-man-boy-boy","family-man-man-girl-girl","family-woman-woman-boy","family-woman-woman-girl","family-woman-woman-girl-boy","family-woman-woman-boy-boy","family-woman-woman-girl-girl","family-man-boy","family-man-boy-boy","family-man-girl","family-man-girl-boy","family-man-girl-girl","family-woman-boy","family-woman-boy-boy","family-woman-girl","family-woman-girl-boy","family-woman-girl-girl","speaking-head","bust-in-silhouette","busts-in-silhouette","people-hugging","footprints"]},{"id":"animals_&_nature","name":"Animals & Nature","emojis":["monkey-face","monkey","gorilla","orangutan","dog-face","dog","guide-dog","service-dog","poodle","wolf","fox","raccoon","cat-face","cat","black-cat","lion","tiger-face","tiger","leopard","horse-face","horse","unicorn","zebra","deer","bison","cow-face","ox","water-buffalo","cow","pig-face","pig","boar","pig-nose","ram","ewe","goat","camel","twohump-camel","llama","giraffe","elephant","mammoth","rhinoceros","hippopotamus","mouse-face","mouse","rat","hamster","rabbit-face","rabbit","chipmunk","beaver","hedgehog","bat","bear","polar-bear","koala","panda","sloth","otter","skunk","kangaroo","badger","paw-prints","turkey","chicken","rooster","hatching-chick","baby-chick","frontfacing-baby-chick","bird","penguin","dove","eagle","duck","swan","owl","dodo","feather","flamingo","peacock","parrot","frog","crocodile","turtle","lizard","snake","dragon-face","dragon","sauropod","trex","spouting-whale","whale","dolphin","seal","fish","tropical-fish","blowfish","shark","octopus","spiral-shell","coral","snail","butterfly","bug","ant","honeybee","beetle","lady-beetle","cricket","cockroach","spider","spider-web","scorpion","mosquito","fly","worm","microbe","bouquet","cherry-blossom","white-flower","lotus","rosette","rose","wilted-flower","hibiscus","sunflower","blossom","tulip","seedling","potted-plant","evergreen-tree","deciduous-tree","palm-tree","cactus","sheaf-of-rice","herb","shamrock","four-leaf-clover","maple-leaf","fallen-leaf","leaf-fluttering-in-wind","empty-nest","nest-with-eggs"]},{"id":"food_&_drink","name":"Food & Drink","emojis":["grapes","melon","watermelon","tangerine","lemon","banana","pineapple","mango","red-apple","green-apple","pear","peach","cherries","strawberry","blueberries","kiwi-fruit","tomato","olive","coconut","avocado","eggplant","potato","carrot","ear-of-corn","hot-pepper","bell-pepper","cucumber","leafy-green","broccoli","garlic","onion","mushroom","peanuts","beans","chestnut","bread","croissant","baguette-bread","flatbread","pretzel","bagel","pancakes","waffle","cheese-wedge","meat-on-bone","poultry-leg","cut-of-meat","bacon","hamburger","french-fries","pizza","hot-dog","sandwich","taco","burrito","tamale","stuffed-flatbread","falafel","egg","cooking","shallow-pan-of-food","pot-of-food","fondue","bowl-with-spoon","green-salad","popcorn","butter","salt","canned-food","bento-box","rice-cracker","rice-ball","cooked-rice","curry-rice","steaming-bowl","spaghetti","roasted-sweet-potato","oden","sushi","fried-shrimp","fish-cake-with-swirl","moon-cake","dango","dumpling","fortune-cookie","takeout-box","crab","lobster","shrimp","squid","oyster","soft-ice-cream","shaved-ice","ice-cream","doughnut","cookie","birthday-cake","shortcake","cupcake","pie","chocolate-bar","candy","lollipop","custard","honey-pot","baby-bottle","glass-of-milk","hot-beverage","teapot","teacup-without-handle","sake","bottle-with-popping-cork","wine-glass","cocktail-glass","tropical-drink","beer-mug","clinking-beer-mugs","clinking-glasses","tumbler-glass","pouring-liquid","cup-with-straw","bubble-tea","beverage-box","mate","ice","chopsticks","fork-and-knife-with-plate","fork-and-knife","spoon","kitchen-knife","jar","amphora"]},{"id":"travel_&_places","name":"Travel & Places","emojis":["globe-showing-europeafrica","globe-showing-americas","globe-showing-asiaaustralia","globe-with-meridians","world-map","map-of-japan","compass","snowcapped-mountain","mountain","volcano","mount-fuji","camping","beach-with-umbrella","desert","desert-island","national-park","stadium","classical-building","building-construction","brick","rock","wood","hut","houses","derelict-house","house","house-with-garden","office-building","japanese-post-office","post-office","hospital","bank","hotel","love-hotel","convenience-store","school","department-store","factory","japanese-castle","castle","wedding","tokyo-tower","statue-of-liberty","church","mosque","hindu-temple","synagogue","shinto-shrine","kaaba","fountain","tent","foggy","night-with-stars","cityscape","sunrise-over-mountains","sunrise","cityscape-at-dusk","sunset","bridge-at-night","hot-springs","carousel-horse","playground-slide","ferris-wheel","roller-coaster","barber-pole","circus-tent","locomotive","railway-car","highspeed-train","bullet-train","train","metro","light-rail","station","tram","monorail","mountain-railway","tram-car","bus","oncoming-bus","trolleybus","minibus","ambulance","fire-engine","police-car","oncoming-police-car","taxi","oncoming-taxi","automobile","oncoming-automobile","sport-utility-vehicle","pickup-truck","delivery-truck","articulated-lorry","tractor","racing-car","motorcycle","motor-scooter","manual-wheelchair","motorized-wheelchair","auto-rickshaw","bicycle","kick-scooter","skateboard","roller-skate","bus-stop","motorway","railway-track","oil-drum","fuel-pump","wheel","police-car-light","horizontal-traffic-light","vertical-traffic-light","stop-sign","construction","anchor","ring-buoy","sailboat","canoe","speedboat","passenger-ship","ferry","motor-boat","ship","airplane","small-airplane","airplane-departure","airplane-arrival","parachute","seat","helicopter","suspension-railway","mountain-cableway","aerial-tramway","satellite","rocket","flying-saucer","bellhop-bell","luggage","hourglass-done","hourglass-not-done","watch","alarm-clock","stopwatch","timer-clock","mantelpiece-clock","twelve-oclock","twelvethirty","one-oclock","onethirty","two-oclock","twothirty","three-oclock","threethirty","four-oclock","fourthirty","five-oclock","fivethirty","six-oclock","sixthirty","seven-oclock","seventhirty","eight-oclock","eightthirty","nine-oclock","ninethirty","ten-oclock","tenthirty","eleven-oclock","eleventhirty","new-moon","waxing-crescent-moon","first-quarter-moon","waxing-gibbous-moon","full-moon","waning-gibbous-moon","last-quarter-moon","waning-crescent-moon","crescent-moon","new-moon-face","first-quarter-moon-face","last-quarter-moon-face","thermometer","sun","full-moon-face","sun-with-face","ringed-planet","star","glowing-star","shooting-star","milky-way","cloud","sun-behind-cloud","cloud-with-lightning-and-rain","sun-behind-small-cloud","sun-behind-large-cloud","sun-behind-rain-cloud","cloud-with-rain","cloud-with-snow","cloud-with-lightning","tornado","fog","wind-face","cyclone","rainbow","closed-umbrella","umbrella","umbrella-with-rain-drops","umbrella-on-ground","high-voltage","snowflake","snowman","snowman-without-snow","comet","fire","droplet","water-wave"]},{"id":"activities","name":"Activities","emojis":["jackolantern","christmas-tree","fireworks","sparkler","firecracker","sparkles","balloon","party-popper","confetti-ball","tanabata-tree","pine-decoration","japanese-dolls","carp-streamer","wind-chime","moon-viewing-ceremony","red-envelope","ribbon","wrapped-gift","reminder-ribbon","admission-tickets","ticket","military-medal","trophy","sports-medal","1st-place-medal","2nd-place-medal","3rd-place-medal","soccer-ball","baseball","softball","basketball","volleyball","american-football","rugby-football","tennis","flying-disc","bowling","cricket-game","field-hockey","ice-hockey","lacrosse","ping-pong","badminton","boxing-glove","martial-arts-uniform","goal-net","flag-in-hole","ice-skate","fishing-pole","diving-mask","running-shirt","skis","sled","curling-stone","bullseye","yoyo","kite","pool-8-ball","crystal-ball","magic-wand","nazar-amulet","hamsa","video-game","joystick","slot-machine","game-die","puzzle-piece","teddy-bear","piata","mirror-ball","nesting-dolls","spade-suit","heart-suit","diamond-suit","club-suit","chess-pawn","joker","mahjong-red-dragon","flower-playing-cards","performing-arts","framed-picture","artist-palette","thread","sewing-needle","yarn","knot"]},{"id":"objects","name":"Objects","emojis":["glasses","sunglasses","goggles","lab-coat","safety-vest","necktie","tshirt","jeans","scarf","gloves","coat","socks","dress","kimono","sari","onepiece-swimsuit","briefs","shorts","bikini","womans-clothes","purse","handbag","clutch-bag","shopping-bags","backpack","thong-sandal","mans-shoe","running-shoe","hiking-boot","flat-shoe","highheeled-shoe","womans-sandal","ballet-shoes","womans-boot","crown","womans-hat","top-hat","graduation-cap","billed-cap","military-helmet","rescue-workers-helmet","prayer-beads","lipstick","ring","gem-stone","muted-speaker","speaker-low-volume","speaker-medium-volume","speaker-high-volume","loudspeaker","megaphone","postal-horn","bell","bell-with-slash","musical-score","musical-note","musical-notes","studio-microphone","level-slider","control-knobs","microphone","headphone","radio","saxophone","accordion","guitar","musical-keyboard","trumpet","violin","banjo","drum","long-drum","mobile-phone","mobile-phone-with-arrow","telephone","telephone-receiver","pager","fax-machine","battery","low-battery","electric-plug","laptop","desktop-computer","printer","keyboard","computer-mouse","trackball","computer-disk","floppy-disk","optical-disk","dvd","abacus","movie-camera","film-frames","film-projector","clapper-board","television","camera","camera-with-flash","video-camera","videocassette","magnifying-glass-tilted-left","magnifying-glass-tilted-right","candle","light-bulb","flashlight","red-paper-lantern","diya-lamp","notebook-with-decorative-cover","closed-book","open-book","green-book","blue-book","orange-book","books","notebook","ledger","page-with-curl","scroll","page-facing-up","newspaper","rolledup-newspaper","bookmark-tabs","bookmark","label","money-bag","coin","yen-banknote","dollar-banknote","euro-banknote","pound-banknote","money-with-wings","credit-card","receipt","chart-increasing-with-yen","envelope","email","incoming-envelope","envelope-with-arrow","outbox-tray","inbox-tray","package","closed-mailbox-with-raised-flag","closed-mailbox-with-lowered-flag","open-mailbox-with-raised-flag","open-mailbox-with-lowered-flag","postbox","ballot-box-with-ballot","pencil","black-nib","fountain-pen","pen","paintbrush","crayon","memo","briefcase","file-folder","open-file-folder","card-index-dividers","calendar","tearoff-calendar","spiral-notepad","spiral-calendar","card-index","chart-increasing","chart-decreasing","bar-chart","clipboard","pushpin","round-pushpin","paperclip","linked-paperclips","straight-ruler","triangular-ruler","scissors","card-file-box","file-cabinet","wastebasket","locked","unlocked","locked-with-pen","locked-with-key","key","old-key","hammer","axe","pick","hammer-and-pick","hammer-and-wrench","dagger","crossed-swords","water-pistol","boomerang","bow-and-arrow","shield","carpentry-saw","wrench","screwdriver","nut-and-bolt","gear","clamp","balance-scale","white-cane","link","chains","hook","toolbox","magnet","ladder","alembic","test-tube","petri-dish","dna","microscope","telescope","satellite-antenna","syringe","drop-of-blood","pill","adhesive-bandage","crutch","stethoscope","xray","door","elevator","mirror","window","bed","couch-and-lamp","chair","toilet","plunger","shower","bathtub","mouse-trap","razor","lotion-bottle","safety-pin","broom","basket","roll-of-paper","bucket","soap","bubbles","toothbrush","sponge","fire-extinguisher","shopping-cart","cigarette","coffin","headstone","funeral-urn","moai","placard","identification-card"]},{"id":"symbols","name":"Symbols","emojis":["atm-sign","litter-in-bin-sign","potable-water","wheelchair-symbol","mens-room","womens-room","restroom","baby-symbol","water-closet","passport-control","customs","baggage-claim","left-luggage","warning","children-crossing","no-entry","prohibited","no-bicycles","no-smoking","no-littering","nonpotable-water","no-pedestrians","no-mobile-phones","no-one-under-eighteen","radioactive","biohazard","up-arrow","upright-arrow","right-arrow","downright-arrow","down-arrow","downleft-arrow","left-arrow","upleft-arrow","updown-arrow","leftright-arrow","right-arrow-curving-left","left-arrow-curving-right","right-arrow-curving-up","right-arrow-curving-down","clockwise-vertical-arrows","counterclockwise-arrows-button","back-arrow","end-arrow","on-arrow","soon-arrow","top-arrow","place-of-worship","atom-symbol","om","star-of-david","wheel-of-dharma","yin-yang","latin-cross","orthodox-cross","star-and-crescent","peace-symbol","menorah","dotted-sixpointed-star","aries","taurus","gemini","cancer","leo","virgo","libra","scorpio","sagittarius","capricorn","aquarius","pisces","ophiuchus","shuffle-tracks-button","repeat-button","repeat-single-button","play-button","fastforward-button","next-track-button","play-or-pause-button","reverse-button","fast-reverse-button","last-track-button","upwards-button","fast-up-button","downwards-button","fast-down-button","pause-button","stop-button","record-button","eject-button","cinema","dim-button","bright-button","antenna-bars","vibration-mode","mobile-phone-off","female-sign","male-sign","transgender-symbol","multiply","plus","minus","divide","heavy-equals-sign","infinity","double-exclamation-mark","exclamation-question-mark","red-question-mark","white-question-mark","white-exclamation-mark","red-exclamation-mark","wavy-dash","currency-exchange","heavy-dollar-sign","medical-symbol","recycling-symbol","fleurdelis","trident-emblem","name-badge","japanese-symbol-for-beginner","hollow-red-circle","check-mark-button","check-box-with-check","check-mark","cross-mark","cross-mark-button","curly-loop","double-curly-loop","part-alternation-mark","eightspoked-asterisk","eightpointed-star","sparkle","copyright","registered","trade-mark","keycap","keycap","keycap-0","keycap-1","keycap-2","keycap-3","keycap-4","keycap-5","keycap-6","keycap-7","keycap-8","keycap-9","keycap-10","input-latin-uppercase","input-latin-lowercase","input-numbers","input-symbols","input-latin-letters","a-button-blood-type","ab-button-blood-type","b-button-blood-type","cl-button","cool-button","free-button","information","id-button","circled-m","new-button","ng-button","o-button-blood-type","ok-button","p-button","sos-button","up-button","vs-button","japanese-here-button","japanese-service-charge-button","japanese-monthly-amount-button","japanese-not-free-of-charge-button","japanese-reserved-button","japanese-bargain-button","japanese-discount-button","japanese-free-of-charge-button","japanese-prohibited-button","japanese-acceptable-button","japanese-application-button","japanese-passing-grade-button","japanese-vacancy-button","japanese-congratulations-button","japanese-secret-button","japanese-open-for-business-button","japanese-no-vacancy-button","red-circle","orange-circle","yellow-circle","green-circle","blue-circle","purple-circle","brown-circle","black-circle","white-circle","red-square","orange-square","yellow-square","green-square","blue-square","purple-square","brown-square","black-large-square","white-large-square","black-medium-square","white-medium-square","black-mediumsmall-square","white-mediumsmall-square","black-small-square","white-small-square","large-orange-diamond","large-blue-diamond","small-orange-diamond","small-blue-diamond","red-triangle-pointed-up","red-triangle-pointed-down","diamond-with-a-dot","radio-button","white-square-button","black-square-button"]},{"id":"flags","name":"Flags","emojis":["chequered-flag","triangular-flag","crossed-flags","black-flag","white-flag","rainbow-flag","transgender-flag","pirate-flag","flag-ascension-island","flag-andorra","flag-united-arab-emirates","flag-afghanistan","flag-antigua--barbuda","flag-anguilla","flag-albania","flag-armenia","flag-angola","flag-antarctica","flag-argentina","flag-american-samoa","flag-austria","flag-australia","flag-aruba","flag-land-islands","flag-azerbaijan","flag-bosnia--herzegovina","flag-barbados","flag-bangladesh","flag-belgium","flag-burkina-faso","flag-bulgaria","flag-bahrain","flag-burundi","flag-benin","flag-st-barthlemy","flag-bermuda","flag-brunei","flag-bolivia","flag-caribbean-netherlands","flag-brazil","flag-bahamas","flag-bhutan","flag-bouvet-island","flag-botswana","flag-belarus","flag-belize","flag-canada","flag-cocos-keeling-islands","flag-congo--kinshasa","flag-central-african-republic","flag-congo--brazzaville","flag-switzerland","flag-cte-divoire","flag-cook-islands","flag-chile","flag-cameroon","flag-china","flag-colombia","flag-clipperton-island","flag-costa-rica","flag-cuba","flag-cape-verde","flag-curaao","flag-christmas-island","flag-cyprus","flag-czechia","flag-germany","flag-diego-garcia","flag-djibouti","flag-denmark","flag-dominica","flag-dominican-republic","flag-algeria","flag-ceuta--melilla","flag-ecuador","flag-estonia","flag-egypt","flag-western-sahara","flag-eritrea","flag-spain","flag-ethiopia","flag-european-union","flag-finland","flag-fiji","flag-falkland-islands","flag-micronesia","flag-faroe-islands","flag-france","flag-gabon","flag-united-kingdom","flag-grenada","flag-georgia","flag-french-guiana","flag-guernsey","flag-ghana","flag-gibraltar","flag-greenland","flag-gambia","flag-guinea","flag-guadeloupe","flag-equatorial-guinea","flag-greece","flag-south-georgia--south-sandwich-islands","flag-guatemala","flag-guam","flag-guineabissau","flag-guyana","flag-hong-kong-sar-china","flag-heard--mcdonald-islands","flag-honduras","flag-croatia","flag-haiti","flag-hungary","flag-canary-islands","flag-indonesia","flag-ireland","flag-israel","flag-isle-of-man","flag-india","flag-british-indian-ocean-territory","flag-iraq","flag-iran","flag-iceland","flag-italy","flag-jersey","flag-jamaica","flag-jordan","flag-japan","flag-kenya","flag-kyrgyzstan","flag-cambodia","flag-kiribati","flag-comoros","flag-st-kitts--nevis","flag-north-korea","flag-south-korea","flag-kuwait","flag-cayman-islands","flag-kazakhstan","flag-laos","flag-lebanon","flag-st-lucia","flag-liechtenstein","flag-sri-lanka","flag-liberia","flag-lesotho","flag-lithuania","flag-luxembourg","flag-latvia","flag-libya","flag-morocco","flag-monaco","flag-moldova","flag-montenegro","flag-st-martin","flag-madagascar","flag-marshall-islands","flag-north-macedonia","flag-mali","flag-myanmar-burma","flag-mongolia","flag-macao-sar-china","flag-northern-mariana-islands","flag-martinique","flag-mauritania","flag-montserrat","flag-malta","flag-mauritius","flag-maldives","flag-malawi","flag-mexico","flag-malaysia","flag-mozambique","flag-namibia","flag-new-caledonia","flag-niger","flag-norfolk-island","flag-nigeria","flag-nicaragua","flag-netherlands","flag-norway","flag-nepal","flag-nauru","flag-niue","flag-new-zealand","flag-oman","flag-panama","flag-peru","flag-french-polynesia","flag-papua-new-guinea","flag-philippines","flag-pakistan","flag-poland","flag-st-pierre--miquelon","flag-pitcairn-islands","flag-puerto-rico","flag-palestinian-territories","flag-portugal","flag-palau","flag-paraguay","flag-qatar","flag-runion","flag-romania","flag-serbia","flag-russia","flag-rwanda","flag-saudi-arabia","flag-solomon-islands","flag-seychelles","flag-sudan","flag-sweden","flag-singapore","flag-st-helena","flag-slovenia","flag-svalbard--jan-mayen","flag-slovakia","flag-sierra-leone","flag-san-marino","flag-senegal","flag-somalia","flag-suriname","flag-south-sudan","flag-so-tom--prncipe","flag-el-salvador","flag-sint-maarten","flag-syria","flag-eswatini","flag-tristan-da-cunha","flag-turks--caicos-islands","flag-chad","flag-french-southern-territories","flag-togo","flag-thailand","flag-tajikistan","flag-tokelau","flag-timorleste","flag-turkmenistan","flag-tunisia","flag-tonga","flag-turkey","flag-trinidad--tobago","flag-tuvalu","flag-taiwan","flag-tanzania","flag-ukraine","flag-uganda","flag-us-outlying-islands","flag-united-nations","flag-united-states","flag-uruguay","flag-uzbekistan","flag-vatican-city","flag-st-vincent--grenadines","flag-venezuela","flag-british-virgin-islands","flag-us-virgin-islands","flag-vietnam","flag-vanuatu","flag-wallis--futuna","flag-samoa","flag-kosovo","flag-yemen","flag-mayotte","flag-south-africa","flag-zambia","flag-zimbabwe","flag-england","flag-scotland","flag-wales"]}],"emojis":{"grinning-face":{"a":"Grinning Face","b":"1F600","j":["face","grin","smile","happy","joy",":D"]},"grinning-face-with-big-eyes":{"a":"Grinning Face with Big Eyes","b":"1F603","j":["face","mouth","open","smile","happy","joy","haha",":D",":)","funny"]},"grinning-face-with-smiling-eyes":{"a":"Grinning Face with Smiling Eyes","b":"1F604","j":["eye","face","mouth","open","smile","happy","joy","funny","haha","laugh","like",":D",":)"]},"beaming-face-with-smiling-eyes":{"a":"Beaming Face with Smiling Eyes","b":"1F601","j":["eye","face","grin","smile","happy","joy","kawaii"]},"grinning-squinting-face":{"a":"Grinning Squinting Face","b":"1F606","j":["face","laugh","mouth","satisfied","smile","happy","joy","lol","haha","glad","XD"]},"grinning-face-with-sweat":{"a":"Grinning Face with Sweat","b":"1F605","j":["cold","face","open","smile","sweat","hot","happy","laugh","relief"]},"rolling-on-the-floor-laughing":{"a":"Rolling on the Floor Laughing","b":"1F923","j":["face","floor","laugh","rofl","rolling","rotfl","laughing","lol","haha"]},"face-with-tears-of-joy":{"a":"Face with Tears of Joy","b":"1F602","j":["face","joy","laugh","tear","cry","tears","weep","happy","happytears","haha"]},"slightly-smiling-face":{"a":"Slightly Smiling Face","b":"1F642","j":["face","smile"]},"upsidedown-face":{"a":"Upside-Down Face","b":"1F643","j":["face","upside-down","upside_down_face","flipped","silly","smile"]},"melting-face":{"a":"⊛ Melting Face","b":"1FAE0","j":["disappear","dissolve","liquid","melt"]},"winking-face":{"a":"Winking Face","b":"1F609","j":["face","wink","happy","mischievous","secret",";)","smile","eye"]},"smiling-face-with-smiling-eyes":{"a":"Smiling Face with Smiling Eyes","b":"1F60A","j":["blush","eye","face","smile","happy","flushed","crush","embarrassed","shy","joy"]},"smiling-face-with-halo":{"a":"Smiling Face with Halo","b":"1F607","j":["angel","face","fantasy","halo","innocent","heaven"]},"smiling-face-with-hearts":{"a":"Smiling Face with Hearts","b":"1F970","j":["adore","crush","hearts","in love","face","love","like","affection","valentines","infatuation"]},"smiling-face-with-hearteyes":{"a":"Smiling Face with Heart-Eyes","b":"1F60D","j":["eye","face","love","smile","smiling face with heart-eyes","smiling_face_with_heart_eyes","like","affection","valentines","infatuation","crush","heart"]},"starstruck":{"a":"Star-Struck","b":"1F929","j":["eyes","face","grinning","star","star-struck","starry-eyed","star_struck","smile","starry"]},"face-blowing-a-kiss":{"a":"Face Blowing a Kiss","b":"1F618","j":["face","kiss","love","like","affection","valentines","infatuation"]},"kissing-face":{"a":"Kissing Face","b":"1F617","j":["face","kiss","love","like","3","valentines","infatuation"]},"smiling-face":{"a":"Smiling Face","b":"263A","j":["face","outlined","relaxed","smile","blush","massage","happiness"]},"kissing-face-with-closed-eyes":{"a":"Kissing Face with Closed Eyes","b":"1F61A","j":["closed","eye","face","kiss","love","like","affection","valentines","infatuation"]},"kissing-face-with-smiling-eyes":{"a":"Kissing Face with Smiling Eyes","b":"1F619","j":["eye","face","kiss","smile","affection","valentines","infatuation"]},"smiling-face-with-tear":{"a":"Smiling Face with Tear","b":"1F972","j":["grateful","proud","relieved","smiling","tear","touched","sad","cry","pretend"]},"face-savoring-food":{"a":"Face Savoring Food","b":"1F60B","j":["delicious","face","savouring","smile","yum","happy","joy","tongue","silly","yummy","nom"]},"face-with-tongue":{"a":"Face with Tongue","b":"1F61B","j":["face","tongue","prank","childish","playful","mischievous","smile"]},"winking-face-with-tongue":{"a":"Winking Face with Tongue","b":"1F61C","j":["eye","face","joke","tongue","wink","prank","childish","playful","mischievous","smile"]},"zany-face":{"a":"Zany Face","b":"1F92A","j":["eye","goofy","large","small","face","crazy"]},"squinting-face-with-tongue":{"a":"Squinting Face with Tongue","b":"1F61D","j":["eye","face","horrible","taste","tongue","prank","playful","mischievous","smile"]},"moneymouth-face":{"a":"Money-Mouth Face","b":"1F911","j":["face","money","money-mouth face","mouth","money_mouth_face","rich","dollar"]},"smiling-face-with-open-hands":{"a":"Smiling Face with Open Hands","b":"1F917","j":["face","hug","hugging","open hands","smiling face","hugging_face","smile"]},"face-with-hand-over-mouth":{"a":"Face with Hand over Mouth","b":"1F92D","j":["whoops","shock","sudden realization","surprise","face"]},"face-with-open-eyes-and-hand-over-mouth":{"a":"⊛ Face with Open Eyes and Hand over Mouth","b":"1FAE2","j":["amazement","awe","disbelief","embarrass","scared","surprise"]},"face-with-peeking-eye":{"a":"⊛ Face with Peeking Eye","b":"1FAE3","j":["captivated","peep","stare"]},"shushing-face":{"a":"Shushing Face","b":"1F92B","j":["quiet","shush","face","shhh"]},"thinking-face":{"a":"Thinking Face","b":"1F914","j":["face","thinking","hmmm","think","consider"]},"saluting-face":{"a":"⊛ Saluting Face","b":"1FAE1","j":["ok","salute","sunny","troops","yes"]},"zippermouth-face":{"a":"Zipper-Mouth Face","b":"1F910","j":["face","mouth","zipper","zipper-mouth face","zipper_mouth_face","sealed","secret"]},"face-with-raised-eyebrow":{"a":"Face with Raised Eyebrow","b":"1F928","j":["distrust","skeptic","disapproval","disbelief","mild surprise","scepticism","face","surprise"]},"neutral-face":{"a":"Neutral Face","b":"1F610","j":["deadpan","face","meh","neutral","indifference",":|"]},"expressionless-face":{"a":"Expressionless Face","b":"1F611","j":["expressionless","face","inexpressive","meh","unexpressive","indifferent","-_-","deadpan"]},"face-without-mouth":{"a":"Face Without Mouth","b":"1F636","j":["face","mouth","quiet","silent","hellokitty"]},"dotted-line-face":{"a":"⊛ Dotted Line Face","b":"1FAE5","j":["depressed","disappear","hide","introvert","invisible"]},"face-in-clouds":{"a":"Face in Clouds","b":"1F636-200D-1F32B-FE0F","j":["absentminded","face in the fog","head in clouds","shower","steam","dream"]},"smirking-face":{"a":"Smirking Face","b":"1F60F","j":["face","smirk","smile","mean","prank","smug","sarcasm"]},"unamused-face":{"a":"Unamused Face","b":"1F612","j":["face","unamused","unhappy","indifference","bored","straight face","serious","sarcasm","unimpressed","skeptical","dubious","side_eye"]},"face-with-rolling-eyes":{"a":"Face with Rolling Eyes","b":"1F644","j":["eyeroll","eyes","face","rolling","frustrated"]},"grimacing-face":{"a":"Grimacing Face","b":"1F62C","j":["face","grimace","teeth"]},"face-exhaling":{"a":"Face Exhaling","b":"1F62E-200D-1F4A8","j":["exhale","gasp","groan","relief","whisper","whistle","relieve","tired","sigh"]},"lying-face":{"a":"Lying Face","b":"1F925","j":["face","lie","pinocchio"]},"relieved-face":{"a":"Relieved Face","b":"1F60C","j":["face","relieved","relaxed","phew","massage","happiness"]},"pensive-face":{"a":"Pensive Face","b":"1F614","j":["dejected","face","pensive","sad","depressed","upset"]},"sleepy-face":{"a":"Sleepy Face","b":"1F62A","j":["face","sleep","tired","rest","nap"]},"drooling-face":{"a":"Drooling Face","b":"1F924","j":["drooling","face"]},"sleeping-face":{"a":"Sleeping Face","b":"1F634","j":["face","sleep","zzz","tired","sleepy","night"]},"face-with-medical-mask":{"a":"Face with Medical Mask","b":"1F637","j":["cold","doctor","face","mask","sick","ill","disease"]},"face-with-thermometer":{"a":"Face with Thermometer","b":"1F912","j":["face","ill","sick","thermometer","temperature","cold","fever"]},"face-with-headbandage":{"a":"Face with Head-Bandage","b":"1F915","j":["bandage","face","face with head-bandage","hurt","injury","face_with_head_bandage","injured","clumsy"]},"nauseated-face":{"a":"Nauseated Face","b":"1F922","j":["face","nauseated","vomit","gross","green","sick","throw up","ill"]},"face-vomiting":{"a":"Face Vomiting","b":"1F92E","j":["puke","sick","vomit","face"]},"sneezing-face":{"a":"Sneezing Face","b":"1F927","j":["face","gesundheit","sneeze","sick","allergy"]},"hot-face":{"a":"Hot Face","b":"1F975","j":["feverish","heat stroke","hot","red-faced","sweating","face","heat","red"]},"cold-face":{"a":"Cold Face","b":"1F976","j":["blue-faced","cold","freezing","frostbite","icicles","face","blue","frozen"]},"woozy-face":{"a":"Woozy Face","b":"1F974","j":["dizzy","intoxicated","tipsy","uneven eyes","wavy mouth","face","wavy"]},"face-with-crossedout-eyes":{"a":"Face with Crossed-out Eyes","b":"1F635","j":["crossed-out eyes","dead","face","face with crossed-out eyes","knocked out","dizzy_face","spent","unconscious","xox","dizzy"]},"face-with-spiral-eyes":{"a":"Face with Spiral Eyes","b":"1F635-200D-1F4AB","j":["dizzy","hypnotized","spiral","trouble","whoa","sick","ill","confused","nauseous","nausea"]},"exploding-head":{"a":"Exploding Head","b":"1F92F","j":["mind blown","shocked","face","mind","blown"]},"cowboy-hat-face":{"a":"Cowboy Hat Face","b":"1F920","j":["cowboy","cowgirl","face","hat"]},"partying-face":{"a":"Partying Face","b":"1F973","j":["celebration","hat","horn","party","face","woohoo"]},"disguised-face":{"a":"Disguised Face","b":"1F978","j":["disguise","face","glasses","incognito","nose","pretent","brows","moustache"]},"smiling-face-with-sunglasses":{"a":"Smiling Face with Sunglasses","b":"1F60E","j":["bright","cool","face","sun","sunglasses","smile","summer","beach","sunglass"]},"nerd-face":{"a":"Nerd Face","b":"1F913","j":["face","geek","nerd","nerdy","dork"]},"face-with-monocle":{"a":"Face with Monocle","b":"1F9D0","j":["stuffy","wealthy","face"]},"confused-face":{"a":"Confused Face","b":"1F615","j":["confused","face","meh","indifference","huh","weird","hmmm",":/"]},"face-with-diagonal-mouth":{"a":"⊛ Face with Diagonal Mouth","b":"1FAE4","j":["disappointed","meh","skeptical","unsure"]},"worried-face":{"a":"Worried Face","b":"1F61F","j":["face","worried","concern","nervous",":("]},"slightly-frowning-face":{"a":"Slightly Frowning Face","b":"1F641","j":["face","frown","frowning","disappointed","sad","upset"]},"frowning-face":{"a":"Frowning Face","b":"2639","j":["face","frown","sad","upset"]},"face-with-open-mouth":{"a":"Face with Open Mouth","b":"1F62E","j":["face","mouth","open","sympathy","surprise","impressed","wow","whoa",":O"]},"hushed-face":{"a":"Hushed Face","b":"1F62F","j":["face","hushed","stunned","surprised","woo","shh"]},"astonished-face":{"a":"Astonished Face","b":"1F632","j":["astonished","face","shocked","totally","xox","surprised","poisoned"]},"flushed-face":{"a":"Flushed Face","b":"1F633","j":["dazed","face","flushed","blush","shy","flattered"]},"pleading-face":{"a":"Pleading Face","b":"1F97A","j":["begging","mercy","puppy eyes","face"]},"face-holding-back-tears":{"a":"⊛ Face Holding Back Tears","b":"1F979","j":["angry","cry","proud","resist","sad"]},"frowning-face-with-open-mouth":{"a":"Frowning Face with Open Mouth","b":"1F626","j":["face","frown","mouth","open","aw","what"]},"anguished-face":{"a":"Anguished Face","b":"1F627","j":["anguished","face","stunned","nervous"]},"fearful-face":{"a":"Fearful Face","b":"1F628","j":["face","fear","fearful","scared","terrified","nervous","oops","huh"]},"anxious-face-with-sweat":{"a":"Anxious Face with Sweat","b":"1F630","j":["blue","cold","face","rushed","sweat","nervous"]},"sad-but-relieved-face":{"a":"Sad but Relieved Face","b":"1F625","j":["disappointed","face","relieved","whew","phew","sweat","nervous"]},"crying-face":{"a":"Crying Face","b":"1F622","j":["cry","face","sad","tear","tears","depressed","upset",":'("]},"loudly-crying-face":{"a":"Loudly Crying Face","b":"1F62D","j":["cry","face","sad","sob","tear","tears","upset","depressed"]},"face-screaming-in-fear":{"a":"Face Screaming in Fear","b":"1F631","j":["face","fear","munch","scared","scream","omg"]},"confounded-face":{"a":"Confounded Face","b":"1F616","j":["confounded","face","confused","sick","unwell","oops",":S"]},"persevering-face":{"a":"Persevering Face","b":"1F623","j":["face","persevere","sick","no","upset","oops"]},"disappointed-face":{"a":"Disappointed Face","b":"1F61E","j":["disappointed","face","sad","upset","depressed",":("]},"downcast-face-with-sweat":{"a":"Downcast Face with Sweat","b":"1F613","j":["cold","face","sweat","hot","sad","tired","exercise"]},"weary-face":{"a":"Weary Face","b":"1F629","j":["face","tired","weary","sleepy","sad","frustrated","upset"]},"tired-face":{"a":"Tired Face","b":"1F62B","j":["face","tired","sick","whine","upset","frustrated"]},"yawning-face":{"a":"Yawning Face","b":"1F971","j":["bored","tired","yawn","sleepy"]},"face-with-steam-from-nose":{"a":"Face with Steam From Nose","b":"1F624","j":["face","triumph","won","gas","phew","proud","pride"]},"pouting-face":{"a":"Pouting Face","b":"1F621","j":["angry","face","mad","pouting","rage","red","hate","despise"]},"angry-face":{"a":"Angry Face","b":"1F620","j":["anger","angry","face","mad","annoyed","frustrated"]},"face-with-symbols-on-mouth":{"a":"Face with Symbols on Mouth","b":"1F92C","j":["swearing","cursing","face","cussing","profanity","expletive"]},"smiling-face-with-horns":{"a":"Smiling Face with Horns","b":"1F608","j":["face","fairy tale","fantasy","horns","smile","devil"]},"angry-face-with-horns":{"a":"Angry Face with Horns","b":"1F47F","j":["demon","devil","face","fantasy","imp","angry","horns"]},"skull":{"a":"Skull","b":"1F480","j":["death","face","fairy tale","monster","dead","skeleton","creepy"]},"skull-and-crossbones":{"a":"Skull and Crossbones","b":"2620","j":["crossbones","death","face","monster","skull","poison","danger","deadly","scary","pirate","evil"]},"pile-of-poo":{"a":"Pile of Poo","b":"1F4A9","j":["dung","face","monster","poo","poop","hankey","shitface","fail","turd","shit"]},"clown-face":{"a":"Clown Face","b":"1F921","j":["clown","face"]},"ogre":{"a":"Ogre","b":"1F479","j":["creature","face","fairy tale","fantasy","monster","troll","red","mask","halloween","scary","creepy","devil","demon","japanese"]},"goblin":{"a":"Goblin","b":"1F47A","j":["creature","face","fairy tale","fantasy","monster","red","evil","mask","scary","creepy","japanese"]},"ghost":{"a":"Ghost","b":"1F47B","j":["creature","face","fairy tale","fantasy","monster","halloween","spooky","scary"]},"alien":{"a":"Alien","b":"1F47D","j":["creature","extraterrestrial","face","fantasy","ufo","UFO","paul","weird","outer_space"]},"alien-monster":{"a":"Alien Monster","b":"1F47E","j":["alien","creature","extraterrestrial","face","monster","ufo","game","arcade","play"]},"robot":{"a":"Robot","b":"1F916","j":["face","monster","computer","machine","bot"]},"grinning-cat":{"a":"Grinning Cat","b":"1F63A","j":["cat","face","grinning","mouth","open","smile","animal","cats","happy"]},"grinning-cat-with-smiling-eyes":{"a":"Grinning Cat with Smiling Eyes","b":"1F638","j":["cat","eye","face","grin","smile","animal","cats"]},"cat-with-tears-of-joy":{"a":"Cat with Tears of Joy","b":"1F639","j":["cat","face","joy","tear","animal","cats","haha","happy","tears"]},"smiling-cat-with-hearteyes":{"a":"Smiling Cat with Heart-Eyes","b":"1F63B","j":["cat","eye","face","heart","love","smile","smiling cat with heart-eyes","smiling_cat_with_heart_eyes","animal","like","affection","cats","valentines"]},"cat-with-wry-smile":{"a":"Cat with Wry Smile","b":"1F63C","j":["cat","face","ironic","smile","wry","animal","cats","smirk"]},"kissing-cat":{"a":"Kissing Cat","b":"1F63D","j":["cat","eye","face","kiss","animal","cats"]},"weary-cat":{"a":"Weary Cat","b":"1F640","j":["cat","face","oh","surprised","weary","animal","cats","munch","scared","scream"]},"crying-cat":{"a":"Crying Cat","b":"1F63F","j":["cat","cry","face","sad","tear","animal","tears","weep","cats","upset"]},"pouting-cat":{"a":"Pouting Cat","b":"1F63E","j":["cat","face","pouting","animal","cats"]},"seenoevil-monkey":{"a":"See-No-Evil Monkey","b":"1F648","j":["evil","face","forbidden","monkey","see","see-no-evil monkey","see_no_evil_monkey","animal","nature","haha"]},"hearnoevil-monkey":{"a":"Hear-No-Evil Monkey","b":"1F649","j":["evil","face","forbidden","hear","hear-no-evil monkey","monkey","hear_no_evil_monkey","animal","nature"]},"speaknoevil-monkey":{"a":"Speak-No-Evil Monkey","b":"1F64A","j":["evil","face","forbidden","monkey","speak","speak-no-evil monkey","speak_no_evil_monkey","animal","nature","omg"]},"kiss-mark":{"a":"Kiss Mark","b":"1F48B","j":["kiss","lips","face","love","like","affection","valentines"]},"love-letter":{"a":"Love Letter","b":"1F48C","j":["heart","letter","love","mail","email","like","affection","envelope","valentines"]},"heart-with-arrow":{"a":"Heart with Arrow","b":"1F498","j":["arrow","cupid","love","like","heart","affection","valentines"]},"heart-with-ribbon":{"a":"Heart with Ribbon","b":"1F49D","j":["ribbon","valentine","love","valentines"]},"sparkling-heart":{"a":"Sparkling Heart","b":"1F496","j":["excited","sparkle","love","like","affection","valentines"]},"growing-heart":{"a":"Growing Heart","b":"1F497","j":["excited","growing","nervous","pulse","like","love","affection","valentines","pink"]},"beating-heart":{"a":"Beating Heart","b":"1F493","j":["beating","heartbeat","pulsating","love","like","affection","valentines","pink","heart"]},"revolving-hearts":{"a":"Revolving Hearts","b":"1F49E","j":["revolving","love","like","affection","valentines"]},"two-hearts":{"a":"Two Hearts","b":"1F495","j":["love","like","affection","valentines","heart"]},"heart-decoration":{"a":"Heart Decoration","b":"1F49F","j":["heart","purple-square","love","like"]},"heart-exclamation":{"a":"Heart Exclamation","b":"2763","j":["exclamation","mark","punctuation","decoration","love"]},"broken-heart":{"a":"Broken Heart","b":"1F494","j":["break","broken","sad","sorry","heart","heartbreak"]},"heart-on-fire":{"a":"Heart on Fire","b":"2764-FE0F-200D-1F525","j":["burn","heart","love","lust","sacred heart","passionate","enthusiastic"]},"mending-heart":{"a":"Mending Heart","b":"2764-FE0F-200D-1FA79","j":["healthier","improving","mending","recovering","recuperating","well","broken heart","bandage","wounded"]},"red-heart":{"a":"Red Heart","b":"2764","j":["heart","love","like","valentines"]},"orange-heart":{"a":"Orange Heart","b":"1F9E1","j":["orange","love","like","affection","valentines"]},"yellow-heart":{"a":"Yellow Heart","b":"1F49B","j":["yellow","love","like","affection","valentines"]},"green-heart":{"a":"Green Heart","b":"1F49A","j":["green","love","like","affection","valentines"]},"blue-heart":{"a":"Blue Heart","b":"1F499","j":["blue","love","like","affection","valentines"]},"purple-heart":{"a":"Purple Heart","b":"1F49C","j":["purple","love","like","affection","valentines"]},"brown-heart":{"a":"Brown Heart","b":"1F90E","j":["brown","heart","coffee"]},"black-heart":{"a":"Black Heart","b":"1F5A4","j":["black","evil","wicked"]},"white-heart":{"a":"White Heart","b":"1F90D","j":["heart","white","pure"]},"hundred-points":{"a":"Hundred Points","b":"1F4AF","j":["100","full","hundred","score","perfect","numbers","century","exam","quiz","test","pass"]},"anger-symbol":{"a":"Anger Symbol","b":"1F4A2","j":["angry","comic","mad"]},"collision":{"a":"Collision","b":"1F4A5","j":["boom","comic","bomb","explode","explosion","blown"]},"dizzy":{"a":"Dizzy","b":"1F4AB","j":["comic","star","sparkle","shoot","magic"]},"sweat-droplets":{"a":"Sweat Droplets","b":"1F4A6","j":["comic","splashing","sweat","water","drip","oops"]},"dashing-away":{"a":"Dashing Away","b":"1F4A8","j":["comic","dash","running","wind","air","fast","shoo","fart","smoke","puff"]},"hole":{"a":"Hole","b":"1F573","j":["embarrassing"]},"bomb":{"a":"Bomb","b":"1F4A3","j":["comic","boom","explode","explosion","terrorism"]},"speech-balloon":{"a":"Speech Balloon","b":"1F4AC","j":["balloon","bubble","comic","dialog","speech","words","message","talk","chatting"]},"eye-in-speech-bubble":{"a":"Eye in Speech Bubble","b":"1F441-FE0F-200D-1F5E8-FE0F","j":["eye","speech bubble","witness","info"]},"left-speech-bubble":{"a":"Left Speech Bubble","b":"1F5E8","j":["dialog","speech","words","message","talk","chatting"]},"right-anger-bubble":{"a":"Right Anger Bubble","b":"1F5EF","j":["angry","balloon","bubble","mad","caption","speech","thinking"]},"thought-balloon":{"a":"Thought Balloon","b":"1F4AD","j":["balloon","bubble","comic","thought","cloud","speech","thinking","dream"]},"zzz":{"a":"Zzz","b":"1F4A4","j":["comic","sleep","sleepy","tired","dream"]},"waving-hand":{"a":"Waving Hand","b":"1F44B","j":["hand","wave","waving","hands","gesture","goodbye","solong","farewell","hello","hi","palm"]},"raised-back-of-hand":{"a":"Raised Back of Hand","b":"1F91A","j":["backhand","raised","fingers"]},"hand-with-fingers-splayed":{"a":"Hand with Fingers Splayed","b":"1F590","j":["finger","hand","splayed","fingers","palm"]},"raised-hand":{"a":"Raised Hand","b":"270B","j":["hand","high 5","high five","fingers","stop","highfive","palm","ban"]},"vulcan-salute":{"a":"Vulcan Salute","b":"1F596","j":["finger","hand","spock","vulcan","fingers","star trek"]},"rightwards-hand":{"a":"⊛ Rightwards Hand","b":"1FAF1","j":["hand","right","rightward"]},"leftwards-hand":{"a":"⊛ Leftwards Hand","b":"1FAF2","j":["hand","left","leftward"]},"palm-down-hand":{"a":"⊛ Palm Down Hand","b":"1FAF3","j":["dismiss","drop","shoo"]},"palm-up-hand":{"a":"⊛ Palm Up Hand","b":"1FAF4","j":["beckon","catch","come","offer"]},"ok-hand":{"a":"Ok Hand","b":"1F44C","j":["hand","OK","fingers","limbs","perfect","ok","okay"]},"pinched-fingers":{"a":"Pinched Fingers","b":"1F90C","j":["fingers","hand gesture","interrogation","pinched","sarcastic","size","tiny","small"]},"pinching-hand":{"a":"Pinching Hand","b":"1F90F","j":["small amount","tiny","small","size"]},"victory-hand":{"a":"Victory Hand","b":"270C","j":["hand","v","victory","fingers","ohyeah","peace","two"]},"crossed-fingers":{"a":"Crossed Fingers","b":"1F91E","j":["cross","finger","hand","luck","good","lucky"]},"hand-with-index-finger-and-thumb-crossed":{"a":"⊛ Hand with Index Finger and Thumb Crossed","b":"1FAF0","j":["expensive","heart","love","money","snap"]},"loveyou-gesture":{"a":"Love-You Gesture","b":"1F91F","j":["hand","ILY","love-you gesture","love_you_gesture","fingers","gesture"]},"sign-of-the-horns":{"a":"Sign of the Horns","b":"1F918","j":["finger","hand","horns","rock-on","fingers","evil_eye","sign_of_horns","rock_on"]},"call-me-hand":{"a":"Call Me Hand","b":"1F919","j":["call","hand","hands","gesture","shaka"]},"backhand-index-pointing-left":{"a":"Backhand Index Pointing Left","b":"1F448","j":["backhand","finger","hand","index","point","direction","fingers","left"]},"backhand-index-pointing-right":{"a":"Backhand Index Pointing Right","b":"1F449","j":["backhand","finger","hand","index","point","fingers","direction","right"]},"backhand-index-pointing-up":{"a":"Backhand Index Pointing Up","b":"1F446","j":["backhand","finger","hand","point","up","fingers","direction"]},"middle-finger":{"a":"Middle Finger","b":"1F595","j":["finger","hand","fingers","rude","middle","flipping"]},"backhand-index-pointing-down":{"a":"Backhand Index Pointing Down","b":"1F447","j":["backhand","down","finger","hand","point","fingers","direction"]},"index-pointing-up":{"a":"Index Pointing Up","b":"261D","j":["finger","hand","index","point","up","fingers","direction"]},"index-pointing-at-the-viewer":{"a":"⊛ Index Pointing at the Viewer","b":"1FAF5","j":["point","you"]},"thumbs-up":{"a":"Thumbs Up","b":"1F44D","j":["+1","hand","thumb","up","thumbsup","yes","awesome","good","agree","accept","cool","like"]},"thumbs-down":{"a":"Thumbs Down","b":"1F44E","j":["-1","down","hand","thumb","thumbsdown","no","dislike"]},"raised-fist":{"a":"Raised Fist","b":"270A","j":["clenched","fist","hand","punch","fingers","grasp"]},"oncoming-fist":{"a":"Oncoming Fist","b":"1F44A","j":["clenched","fist","hand","punch","angry","violence","hit","attack"]},"leftfacing-fist":{"a":"Left-Facing Fist","b":"1F91B","j":["fist","left-facing fist","leftwards","left_facing_fist","hand","fistbump"]},"rightfacing-fist":{"a":"Right-Facing Fist","b":"1F91C","j":["fist","right-facing fist","rightwards","right_facing_fist","hand","fistbump"]},"clapping-hands":{"a":"Clapping Hands","b":"1F44F","j":["clap","hand","hands","praise","applause","congrats","yay"]},"raising-hands":{"a":"Raising Hands","b":"1F64C","j":["celebration","gesture","hand","hooray","raised","yea","hands"]},"heart-hands":{"a":"⊛ Heart Hands","b":"1FAF6","j":["love"]},"open-hands":{"a":"Open Hands","b":"1F450","j":["hand","open","fingers","butterfly","hands"]},"palms-up-together":{"a":"Palms Up Together","b":"1F932","j":["prayer","cupped hands","hands","gesture","cupped"]},"handshake":{"a":"Handshake","b":"1F91D","j":["agreement","hand","meeting","shake"]},"folded-hands":{"a":"Folded Hands","b":"1F64F","j":["ask","hand","high 5","high five","please","pray","thanks","hope","wish","namaste","highfive"]},"writing-hand":{"a":"Writing Hand","b":"270D","j":["hand","write","lower_left_ballpoint_pen","stationery","compose"]},"nail-polish":{"a":"Nail Polish","b":"1F485","j":["care","cosmetics","manicure","nail","polish","beauty","finger","fashion"]},"selfie":{"a":"Selfie","b":"1F933","j":["camera","phone"]},"flexed-biceps":{"a":"Flexed Biceps","b":"1F4AA","j":["biceps","comic","flex","muscle","arm","hand","summer","strong"]},"mechanical-arm":{"a":"Mechanical Arm","b":"1F9BE","j":["accessibility","prosthetic"]},"mechanical-leg":{"a":"Mechanical Leg","b":"1F9BF","j":["accessibility","prosthetic"]},"leg":{"a":"Leg","b":"1F9B5","j":["kick","limb"]},"foot":{"a":"Foot","b":"1F9B6","j":["kick","stomp"]},"ear":{"a":"Ear","b":"1F442","j":["body","face","hear","sound","listen"]},"ear-with-hearing-aid":{"a":"Ear with Hearing Aid","b":"1F9BB","j":["accessibility","hard of hearing"]},"nose":{"a":"Nose","b":"1F443","j":["body","smell","sniff"]},"brain":{"a":"Brain","b":"1F9E0","j":["intelligent","smart"]},"anatomical-heart":{"a":"Anatomical Heart","b":"1FAC0","j":["anatomical","cardiology","heart","organ","pulse","health","heartbeat"]},"lungs":{"a":"Lungs","b":"1FAC1","j":["breath","exhalation","inhalation","organ","respiration","breathe"]},"tooth":{"a":"Tooth","b":"1F9B7","j":["dentist","teeth"]},"bone":{"a":"Bone","b":"1F9B4","j":["skeleton"]},"eyes":{"a":"Eyes","b":"1F440","j":["eye","face","look","watch","stalk","peek","see"]},"eye":{"a":"Eye","b":"1F441","j":["body","face","look","see","watch","stare"]},"tongue":{"a":"Tongue","b":"1F445","j":["body","mouth","playful"]},"mouth":{"a":"Mouth","b":"1F444","j":["lips","kiss"]},"biting-lip":{"a":"⊛ Biting Lip","b":"1FAE6","j":["anxious","fear","flirting","nervous","uncomfortable","worried"]},"baby":{"a":"Baby","b":"1F476","j":["young","child","boy","girl","toddler"]},"child":{"a":"Child","b":"1F9D2","j":["gender-neutral","unspecified gender","young"]},"boy":{"a":"Boy","b":"1F466","j":["young","man","male","guy","teenager"]},"girl":{"a":"Girl","b":"1F467","j":["Virgo","young","zodiac","female","woman","teenager"]},"person":{"a":"Person","b":"1F9D1","j":["adult","gender-neutral","unspecified gender"]},"person-blond-hair":{"a":"Person: Blond Hair","b":"1F471","j":["blond","blond-haired person","hair","person: blond hair","hairstyle"]},"man":{"a":"Man","b":"1F468","j":["adult","mustache","father","dad","guy","classy","sir","moustache"]},"person-beard":{"a":"Person: Beard","b":"1F9D4","j":["beard","person","person: beard","bewhiskered","man_beard"]},"man-beard":{"a":"Man: Beard","b":"1F9D4-200D-2642-FE0F","j":["beard","man","man: beard","facial hair"]},"woman-beard":{"a":"Woman: Beard","b":"1F9D4-200D-2640-FE0F","j":["beard","woman","woman: beard","facial hair"]},"man-red-hair":{"a":"Man: Red Hair","b":"1F468-200D-1F9B0","j":["adult","man","red hair","hairstyle"]},"man-curly-hair":{"a":"Man: Curly Hair","b":"1F468-200D-1F9B1","j":["adult","curly hair","man","hairstyle"]},"man-white-hair":{"a":"Man: White Hair","b":"1F468-200D-1F9B3","j":["adult","man","white hair","old","elder"]},"man-bald":{"a":"Man: Bald","b":"1F468-200D-1F9B2","j":["adult","bald","man","hairless"]},"woman":{"a":"Woman","b":"1F469","j":["adult","female","girls","lady"]},"woman-red-hair":{"a":"Woman: Red Hair","b":"1F469-200D-1F9B0","j":["adult","red hair","woman","hairstyle"]},"person-red-hair":{"a":"Person: Red Hair","b":"1F9D1-200D-1F9B0","j":["adult","gender-neutral","person","red hair","unspecified gender","hairstyle"]},"woman-curly-hair":{"a":"Woman: Curly Hair","b":"1F469-200D-1F9B1","j":["adult","curly hair","woman","hairstyle"]},"person-curly-hair":{"a":"Person: Curly Hair","b":"1F9D1-200D-1F9B1","j":["adult","curly hair","gender-neutral","person","unspecified gender","hairstyle"]},"woman-white-hair":{"a":"Woman: White Hair","b":"1F469-200D-1F9B3","j":["adult","white hair","woman","old","elder"]},"person-white-hair":{"a":"Person: White Hair","b":"1F9D1-200D-1F9B3","j":["adult","gender-neutral","person","unspecified gender","white hair","elder","old"]},"woman-bald":{"a":"Woman: Bald","b":"1F469-200D-1F9B2","j":["adult","bald","woman","hairless"]},"person-bald":{"a":"Person: Bald","b":"1F9D1-200D-1F9B2","j":["adult","bald","gender-neutral","person","unspecified gender","hairless"]},"woman-blond-hair":{"a":"Woman: Blond Hair","b":"1F471-200D-2640-FE0F","j":["blond-haired woman","blonde","hair","woman","woman: blond hair","female","girl","person"]},"man-blond-hair":{"a":"Man: Blond Hair","b":"1F471-200D-2642-FE0F","j":["blond","blond-haired man","hair","man","man: blond hair","male","boy","blonde","guy","person"]},"older-person":{"a":"Older Person","b":"1F9D3","j":["adult","gender-neutral","old","unspecified gender","human","elder","senior"]},"old-man":{"a":"Old Man","b":"1F474","j":["adult","man","old","human","male","men","elder","senior"]},"old-woman":{"a":"Old Woman","b":"1F475","j":["adult","old","woman","human","female","women","lady","elder","senior"]},"person-frowning":{"a":"Person Frowning","b":"1F64D","j":["frown","gesture","worried"]},"man-frowning":{"a":"Man Frowning","b":"1F64D-200D-2642-FE0F","j":["frowning","gesture","man","male","boy","sad","depressed","discouraged","unhappy"]},"woman-frowning":{"a":"Woman Frowning","b":"1F64D-200D-2640-FE0F","j":["frowning","gesture","woman","female","girl","sad","depressed","discouraged","unhappy"]},"person-pouting":{"a":"Person Pouting","b":"1F64E","j":["gesture","pouting","upset"]},"man-pouting":{"a":"Man Pouting","b":"1F64E-200D-2642-FE0F","j":["gesture","man","pouting","male","boy"]},"woman-pouting":{"a":"Woman Pouting","b":"1F64E-200D-2640-FE0F","j":["gesture","pouting","woman","female","girl"]},"person-gesturing-no":{"a":"Person Gesturing No","b":"1F645","j":["forbidden","gesture","hand","person gesturing NO","prohibited","decline"]},"man-gesturing-no":{"a":"Man Gesturing No","b":"1F645-200D-2642-FE0F","j":["forbidden","gesture","hand","man","man gesturing NO","prohibited","male","boy","nope"]},"woman-gesturing-no":{"a":"Woman Gesturing No","b":"1F645-200D-2640-FE0F","j":["forbidden","gesture","hand","prohibited","woman","woman gesturing NO","female","girl","nope"]},"person-gesturing-ok":{"a":"Person Gesturing Ok","b":"1F646","j":["gesture","hand","OK","person gesturing OK","agree"]},"man-gesturing-ok":{"a":"Man Gesturing Ok","b":"1F646-200D-2642-FE0F","j":["gesture","hand","man","man gesturing OK","OK","men","boy","male","blue","human"]},"woman-gesturing-ok":{"a":"Woman Gesturing Ok","b":"1F646-200D-2640-FE0F","j":["gesture","hand","OK","woman","woman gesturing OK","women","girl","female","pink","human"]},"person-tipping-hand":{"a":"Person Tipping Hand","b":"1F481","j":["hand","help","information","sassy","tipping"]},"man-tipping-hand":{"a":"Man Tipping Hand","b":"1F481-200D-2642-FE0F","j":["man","sassy","tipping hand","male","boy","human","information"]},"woman-tipping-hand":{"a":"Woman Tipping Hand","b":"1F481-200D-2640-FE0F","j":["sassy","tipping hand","woman","female","girl","human","information"]},"person-raising-hand":{"a":"Person Raising Hand","b":"1F64B","j":["gesture","hand","happy","raised","question"]},"man-raising-hand":{"a":"Man Raising Hand","b":"1F64B-200D-2642-FE0F","j":["gesture","man","raising hand","male","boy"]},"woman-raising-hand":{"a":"Woman Raising Hand","b":"1F64B-200D-2640-FE0F","j":["gesture","raising hand","woman","female","girl"]},"deaf-person":{"a":"Deaf Person","b":"1F9CF","j":["accessibility","deaf","ear","hear"]},"deaf-man":{"a":"Deaf Man","b":"1F9CF-200D-2642-FE0F","j":["deaf","man","accessibility"]},"deaf-woman":{"a":"Deaf Woman","b":"1F9CF-200D-2640-FE0F","j":["deaf","woman","accessibility"]},"person-bowing":{"a":"Person Bowing","b":"1F647","j":["apology","bow","gesture","sorry","respectiful"]},"man-bowing":{"a":"Man Bowing","b":"1F647-200D-2642-FE0F","j":["apology","bowing","favor","gesture","man","sorry","male","boy"]},"woman-bowing":{"a":"Woman Bowing","b":"1F647-200D-2640-FE0F","j":["apology","bowing","favor","gesture","sorry","woman","female","girl"]},"person-facepalming":{"a":"Person Facepalming","b":"1F926","j":["disbelief","exasperation","face","palm","disappointed"]},"man-facepalming":{"a":"Man Facepalming","b":"1F926-200D-2642-FE0F","j":["disbelief","exasperation","facepalm","man","male","boy"]},"woman-facepalming":{"a":"Woman Facepalming","b":"1F926-200D-2640-FE0F","j":["disbelief","exasperation","facepalm","woman","female","girl"]},"person-shrugging":{"a":"Person Shrugging","b":"1F937","j":["doubt","ignorance","indifference","shrug","regardless"]},"man-shrugging":{"a":"Man Shrugging","b":"1F937-200D-2642-FE0F","j":["doubt","ignorance","indifference","man","shrug","male","boy","confused","indifferent"]},"woman-shrugging":{"a":"Woman Shrugging","b":"1F937-200D-2640-FE0F","j":["doubt","ignorance","indifference","shrug","woman","female","girl","confused","indifferent"]},"health-worker":{"a":"Health Worker","b":"1F9D1-200D-2695-FE0F","j":["doctor","healthcare","nurse","therapist","hospital"]},"man-health-worker":{"a":"Man Health Worker","b":"1F468-200D-2695-FE0F","j":["doctor","healthcare","man","nurse","therapist","human"]},"woman-health-worker":{"a":"Woman Health Worker","b":"1F469-200D-2695-FE0F","j":["doctor","healthcare","nurse","therapist","woman","human"]},"student":{"a":"Student","b":"1F9D1-200D-1F393","j":["graduate","learn"]},"man-student":{"a":"Man Student","b":"1F468-200D-1F393","j":["graduate","man","student","human"]},"woman-student":{"a":"Woman Student","b":"1F469-200D-1F393","j":["graduate","student","woman","human"]},"teacher":{"a":"Teacher","b":"1F9D1-200D-1F3EB","j":["instructor","professor"]},"man-teacher":{"a":"Man Teacher","b":"1F468-200D-1F3EB","j":["instructor","man","professor","teacher","human"]},"woman-teacher":{"a":"Woman Teacher","b":"1F469-200D-1F3EB","j":["instructor","professor","teacher","woman","human"]},"judge":{"a":"Judge","b":"1F9D1-200D-2696-FE0F","j":["justice","scales","law"]},"man-judge":{"a":"Man Judge","b":"1F468-200D-2696-FE0F","j":["judge","justice","man","scales","court","human"]},"woman-judge":{"a":"Woman Judge","b":"1F469-200D-2696-FE0F","j":["judge","justice","scales","woman","court","human"]},"farmer":{"a":"Farmer","b":"1F9D1-200D-1F33E","j":["gardener","rancher","crops"]},"man-farmer":{"a":"Man Farmer","b":"1F468-200D-1F33E","j":["farmer","gardener","man","rancher","human"]},"woman-farmer":{"a":"Woman Farmer","b":"1F469-200D-1F33E","j":["farmer","gardener","rancher","woman","human"]},"cook":{"a":"Cook","b":"1F9D1-200D-1F373","j":["chef","food","kitchen","culinary"]},"man-cook":{"a":"Man Cook","b":"1F468-200D-1F373","j":["chef","cook","man","human"]},"woman-cook":{"a":"Woman Cook","b":"1F469-200D-1F373","j":["chef","cook","woman","human"]},"mechanic":{"a":"Mechanic","b":"1F9D1-200D-1F527","j":["electrician","plumber","tradesperson","worker","technician"]},"man-mechanic":{"a":"Man Mechanic","b":"1F468-200D-1F527","j":["electrician","man","mechanic","plumber","tradesperson","human","wrench"]},"woman-mechanic":{"a":"Woman Mechanic","b":"1F469-200D-1F527","j":["electrician","mechanic","plumber","tradesperson","woman","human","wrench"]},"factory-worker":{"a":"Factory Worker","b":"1F9D1-200D-1F3ED","j":["assembly","factory","industrial","worker","labor"]},"man-factory-worker":{"a":"Man Factory Worker","b":"1F468-200D-1F3ED","j":["assembly","factory","industrial","man","worker","human"]},"woman-factory-worker":{"a":"Woman Factory Worker","b":"1F469-200D-1F3ED","j":["assembly","factory","industrial","woman","worker","human"]},"office-worker":{"a":"Office Worker","b":"1F9D1-200D-1F4BC","j":["architect","business","manager","white-collar"]},"man-office-worker":{"a":"Man Office Worker","b":"1F468-200D-1F4BC","j":["architect","business","man","manager","white-collar","human"]},"woman-office-worker":{"a":"Woman Office Worker","b":"1F469-200D-1F4BC","j":["architect","business","manager","white-collar","woman","human"]},"scientist":{"a":"Scientist","b":"1F9D1-200D-1F52C","j":["biologist","chemist","engineer","physicist","chemistry"]},"man-scientist":{"a":"Man Scientist","b":"1F468-200D-1F52C","j":["biologist","chemist","engineer","man","physicist","scientist","human"]},"woman-scientist":{"a":"Woman Scientist","b":"1F469-200D-1F52C","j":["biologist","chemist","engineer","physicist","scientist","woman","human"]},"technologist":{"a":"Technologist","b":"1F9D1-200D-1F4BB","j":["coder","developer","inventor","software","computer"]},"man-technologist":{"a":"Man Technologist","b":"1F468-200D-1F4BB","j":["coder","developer","inventor","man","software","technologist","engineer","programmer","human","laptop","computer"]},"woman-technologist":{"a":"Woman Technologist","b":"1F469-200D-1F4BB","j":["coder","developer","inventor","software","technologist","woman","engineer","programmer","human","laptop","computer"]},"singer":{"a":"Singer","b":"1F9D1-200D-1F3A4","j":["actor","entertainer","rock","star","song","artist","performer"]},"man-singer":{"a":"Man Singer","b":"1F468-200D-1F3A4","j":["actor","entertainer","man","rock","singer","star","rockstar","human"]},"woman-singer":{"a":"Woman Singer","b":"1F469-200D-1F3A4","j":["actor","entertainer","rock","singer","star","woman","rockstar","human"]},"artist":{"a":"Artist","b":"1F9D1-200D-1F3A8","j":["palette","painting","draw","creativity"]},"man-artist":{"a":"Man Artist","b":"1F468-200D-1F3A8","j":["artist","man","palette","painter","human"]},"woman-artist":{"a":"Woman Artist","b":"1F469-200D-1F3A8","j":["artist","palette","woman","painter","human"]},"pilot":{"a":"Pilot","b":"1F9D1-200D-2708-FE0F","j":["plane","fly","airplane"]},"man-pilot":{"a":"Man Pilot","b":"1F468-200D-2708-FE0F","j":["man","pilot","plane","aviator","human"]},"woman-pilot":{"a":"Woman Pilot","b":"1F469-200D-2708-FE0F","j":["pilot","plane","woman","aviator","human"]},"astronaut":{"a":"Astronaut","b":"1F9D1-200D-1F680","j":["rocket","outerspace"]},"man-astronaut":{"a":"Man Astronaut","b":"1F468-200D-1F680","j":["astronaut","man","rocket","space","human"]},"woman-astronaut":{"a":"Woman Astronaut","b":"1F469-200D-1F680","j":["astronaut","rocket","woman","space","human"]},"firefighter":{"a":"Firefighter","b":"1F9D1-200D-1F692","j":["firetruck","fire"]},"man-firefighter":{"a":"Man Firefighter","b":"1F468-200D-1F692","j":["firefighter","firetruck","man","fireman","human"]},"woman-firefighter":{"a":"Woman Firefighter","b":"1F469-200D-1F692","j":["firefighter","firetruck","woman","fireman","human"]},"police-officer":{"a":"Police Officer","b":"1F46E","j":["cop","officer","police"]},"man-police-officer":{"a":"Man Police Officer","b":"1F46E-200D-2642-FE0F","j":["cop","man","officer","police","law","legal","enforcement","arrest","911"]},"woman-police-officer":{"a":"Woman Police Officer","b":"1F46E-200D-2640-FE0F","j":["cop","officer","police","woman","law","legal","enforcement","arrest","911","female"]},"detective":{"a":"Detective","b":"1F575","j":["sleuth","spy","human"]},"man-detective":{"a":"Man Detective","b":"1F575-FE0F-200D-2642-FE0F","j":["detective","man","sleuth","spy","crime"]},"woman-detective":{"a":"Woman Detective","b":"1F575-FE0F-200D-2640-FE0F","j":["detective","sleuth","spy","woman","human","female"]},"guard":{"a":"Guard","b":"1F482","j":["protect"]},"man-guard":{"a":"Man Guard","b":"1F482-200D-2642-FE0F","j":["guard","man","uk","gb","british","male","guy","royal"]},"woman-guard":{"a":"Woman Guard","b":"1F482-200D-2640-FE0F","j":["guard","woman","uk","gb","british","female","royal"]},"ninja":{"a":"Ninja","b":"1F977","j":["fighter","hidden","stealth","ninjutsu","skills","japanese"]},"construction-worker":{"a":"Construction Worker","b":"1F477","j":["construction","hat","worker","labor","build"]},"man-construction-worker":{"a":"Man Construction Worker","b":"1F477-200D-2642-FE0F","j":["construction","man","worker","male","human","wip","guy","build","labor"]},"woman-construction-worker":{"a":"Woman Construction Worker","b":"1F477-200D-2640-FE0F","j":["construction","woman","worker","female","human","wip","build","labor"]},"person-with-crown":{"a":"⊛ Person with Crown","b":"1FAC5","j":["monarch","noble","regal","royalty"]},"prince":{"a":"Prince","b":"1F934","j":["boy","man","male","crown","royal","king"]},"princess":{"a":"Princess","b":"1F478","j":["fairy tale","fantasy","girl","woman","female","blond","crown","royal","queen"]},"person-wearing-turban":{"a":"Person Wearing Turban","b":"1F473","j":["turban","headdress"]},"man-wearing-turban":{"a":"Man Wearing Turban","b":"1F473-200D-2642-FE0F","j":["man","turban","male","indian","hinduism","arabs"]},"woman-wearing-turban":{"a":"Woman Wearing Turban","b":"1F473-200D-2640-FE0F","j":["turban","woman","female","indian","hinduism","arabs"]},"person-with-skullcap":{"a":"Person with Skullcap","b":"1F472","j":["cap","gua pi mao","hat","person","skullcap","man_with_skullcap","male","boy","chinese"]},"woman-with-headscarf":{"a":"Woman with Headscarf","b":"1F9D5","j":["headscarf","hijab","mantilla","tichel","bandana","head kerchief","female"]},"person-in-tuxedo":{"a":"Person in Tuxedo","b":"1F935","j":["groom","person","tuxedo","man_in_tuxedo","couple","marriage","wedding"]},"man-in-tuxedo":{"a":"Man in Tuxedo","b":"1F935-200D-2642-FE0F","j":["man","tuxedo","formal","fashion"]},"woman-in-tuxedo":{"a":"Woman in Tuxedo","b":"1F935-200D-2640-FE0F","j":["tuxedo","woman","formal","fashion"]},"person-with-veil":{"a":"Person with Veil","b":"1F470","j":["bride","person","veil","wedding","bride_with_veil","couple","marriage","woman"]},"man-with-veil":{"a":"Man with Veil","b":"1F470-200D-2642-FE0F","j":["man","veil","wedding","marriage"]},"woman-with-veil":{"a":"Woman with Veil","b":"1F470-200D-2640-FE0F","j":["veil","woman","wedding","marriage"]},"pregnant-woman":{"a":"Pregnant Woman","b":"1F930","j":["pregnant","woman","baby"]},"pregnant-man":{"a":"⊛ Pregnant Man","b":"1FAC3","j":["belly","bloated","full","pregnant"]},"pregnant-person":{"a":"⊛ Pregnant Person","b":"1FAC4","j":["belly","bloated","full","pregnant"]},"breastfeeding":{"a":"Breast-Feeding","b":"1F931","j":["baby","breast","breast-feeding","nursing","breast_feeding"]},"woman-feeding-baby":{"a":"Woman Feeding Baby","b":"1F469-200D-1F37C","j":["baby","feeding","nursing","woman","birth","food"]},"man-feeding-baby":{"a":"Man Feeding Baby","b":"1F468-200D-1F37C","j":["baby","feeding","man","nursing","birth","food"]},"person-feeding-baby":{"a":"Person Feeding Baby","b":"1F9D1-200D-1F37C","j":["baby","feeding","nursing","person","birth","food"]},"baby-angel":{"a":"Baby Angel","b":"1F47C","j":["angel","baby","face","fairy tale","fantasy","heaven","wings","halo"]},"santa-claus":{"a":"Santa Claus","b":"1F385","j":["celebration","Christmas","claus","father","santa","festival","man","male","xmas","father christmas"]},"mrs-claus":{"a":"Mrs. Claus","b":"1F936","j":["celebration","Christmas","claus","mother","Mrs.","woman","female","xmas","mother christmas"]},"mx-claus":{"a":"Mx Claus","b":"1F9D1-200D-1F384","j":["Claus, christmas","christmas"]},"superhero":{"a":"Superhero","b":"1F9B8","j":["good","hero","heroine","superpower","marvel"]},"man-superhero":{"a":"Man Superhero","b":"1F9B8-200D-2642-FE0F","j":["good","hero","man","superpower","male","superpowers"]},"woman-superhero":{"a":"Woman Superhero","b":"1F9B8-200D-2640-FE0F","j":["good","hero","heroine","superpower","woman","female","superpowers"]},"supervillain":{"a":"Supervillain","b":"1F9B9","j":["criminal","evil","superpower","villain","marvel"]},"man-supervillain":{"a":"Man Supervillain","b":"1F9B9-200D-2642-FE0F","j":["criminal","evil","man","superpower","villain","male","bad","hero","superpowers"]},"woman-supervillain":{"a":"Woman Supervillain","b":"1F9B9-200D-2640-FE0F","j":["criminal","evil","superpower","villain","woman","female","bad","heroine","superpowers"]},"mage":{"a":"Mage","b":"1F9D9","j":["sorcerer","sorceress","witch","wizard","magic"]},"man-mage":{"a":"Man Mage","b":"1F9D9-200D-2642-FE0F","j":["sorcerer","wizard","man","male","mage"]},"woman-mage":{"a":"Woman Mage","b":"1F9D9-200D-2640-FE0F","j":["sorceress","witch","woman","female","mage"]},"fairy":{"a":"Fairy","b":"1F9DA","j":["Oberon","Puck","Titania","wings","magical"]},"man-fairy":{"a":"Man Fairy","b":"1F9DA-200D-2642-FE0F","j":["Oberon","Puck","man","male"]},"woman-fairy":{"a":"Woman Fairy","b":"1F9DA-200D-2640-FE0F","j":["Titania","woman","female"]},"vampire":{"a":"Vampire","b":"1F9DB","j":["Dracula","undead","blood","twilight"]},"man-vampire":{"a":"Man Vampire","b":"1F9DB-200D-2642-FE0F","j":["Dracula","undead","man","male","dracula"]},"woman-vampire":{"a":"Woman Vampire","b":"1F9DB-200D-2640-FE0F","j":["undead","woman","female"]},"merperson":{"a":"Merperson","b":"1F9DC","j":["mermaid","merman","merwoman","sea"]},"merman":{"a":"Merman","b":"1F9DC-200D-2642-FE0F","j":["Triton","man","male","triton"]},"mermaid":{"a":"Mermaid","b":"1F9DC-200D-2640-FE0F","j":["merwoman","woman","female","ariel"]},"elf":{"a":"Elf","b":"1F9DD","j":["magical","LOTR style"]},"man-elf":{"a":"Man Elf","b":"1F9DD-200D-2642-FE0F","j":["magical","man","male"]},"woman-elf":{"a":"Woman Elf","b":"1F9DD-200D-2640-FE0F","j":["magical","woman","female"]},"genie":{"a":"Genie","b":"1F9DE","j":["djinn","(non-human color)","magical","wishes"]},"man-genie":{"a":"Man Genie","b":"1F9DE-200D-2642-FE0F","j":["djinn","man","male"]},"woman-genie":{"a":"Woman Genie","b":"1F9DE-200D-2640-FE0F","j":["djinn","woman","female"]},"zombie":{"a":"Zombie","b":"1F9DF","j":["undead","walking dead","(non-human color)","dead"]},"man-zombie":{"a":"Man Zombie","b":"1F9DF-200D-2642-FE0F","j":["undead","walking dead","man","male","dracula"]},"woman-zombie":{"a":"Woman Zombie","b":"1F9DF-200D-2640-FE0F","j":["undead","walking dead","woman","female"]},"troll":{"a":"⊛ Troll","b":"1F9CC","j":["fairy tale","fantasy","monster"]},"person-getting-massage":{"a":"Person Getting Massage","b":"1F486","j":["face","massage","salon","relax"]},"man-getting-massage":{"a":"Man Getting Massage","b":"1F486-200D-2642-FE0F","j":["face","man","massage","male","boy","head"]},"woman-getting-massage":{"a":"Woman Getting Massage","b":"1F486-200D-2640-FE0F","j":["face","massage","woman","female","girl","head"]},"person-getting-haircut":{"a":"Person Getting Haircut","b":"1F487","j":["barber","beauty","haircut","parlor","hairstyle"]},"man-getting-haircut":{"a":"Man Getting Haircut","b":"1F487-200D-2642-FE0F","j":["haircut","man","male","boy"]},"woman-getting-haircut":{"a":"Woman Getting Haircut","b":"1F487-200D-2640-FE0F","j":["haircut","woman","female","girl"]},"person-walking":{"a":"Person Walking","b":"1F6B6","j":["hike","walk","walking","move"]},"man-walking":{"a":"Man Walking","b":"1F6B6-200D-2642-FE0F","j":["hike","man","walk","human","feet","steps"]},"woman-walking":{"a":"Woman Walking","b":"1F6B6-200D-2640-FE0F","j":["hike","walk","woman","human","feet","steps","female"]},"person-standing":{"a":"Person Standing","b":"1F9CD","j":["stand","standing","still"]},"man-standing":{"a":"Man Standing","b":"1F9CD-200D-2642-FE0F","j":["man","standing","still"]},"woman-standing":{"a":"Woman Standing","b":"1F9CD-200D-2640-FE0F","j":["standing","woman","still"]},"person-kneeling":{"a":"Person Kneeling","b":"1F9CE","j":["kneel","kneeling","pray","respectful"]},"man-kneeling":{"a":"Man Kneeling","b":"1F9CE-200D-2642-FE0F","j":["kneeling","man","pray","respectful"]},"woman-kneeling":{"a":"Woman Kneeling","b":"1F9CE-200D-2640-FE0F","j":["kneeling","woman","respectful","pray"]},"person-with-white-cane":{"a":"Person with White Cane","b":"1F9D1-200D-1F9AF","j":["accessibility","blind","person_with_probing_cane"]},"man-with-white-cane":{"a":"Man with White Cane","b":"1F468-200D-1F9AF","j":["accessibility","blind","man","man_with_probing_cane"]},"woman-with-white-cane":{"a":"Woman with White Cane","b":"1F469-200D-1F9AF","j":["accessibility","blind","woman","woman_with_probing_cane"]},"person-in-motorized-wheelchair":{"a":"Person in Motorized Wheelchair","b":"1F9D1-200D-1F9BC","j":["accessibility","wheelchair","disability"]},"man-in-motorized-wheelchair":{"a":"Man in Motorized Wheelchair","b":"1F468-200D-1F9BC","j":["accessibility","man","wheelchair","disability"]},"woman-in-motorized-wheelchair":{"a":"Woman in Motorized Wheelchair","b":"1F469-200D-1F9BC","j":["accessibility","wheelchair","woman","disability"]},"person-in-manual-wheelchair":{"a":"Person in Manual Wheelchair","b":"1F9D1-200D-1F9BD","j":["accessibility","wheelchair","disability"]},"man-in-manual-wheelchair":{"a":"Man in Manual Wheelchair","b":"1F468-200D-1F9BD","j":["accessibility","man","wheelchair","disability"]},"woman-in-manual-wheelchair":{"a":"Woman in Manual Wheelchair","b":"1F469-200D-1F9BD","j":["accessibility","wheelchair","woman","disability"]},"person-running":{"a":"Person Running","b":"1F3C3","j":["marathon","running","move"]},"man-running":{"a":"Man Running","b":"1F3C3-200D-2642-FE0F","j":["man","marathon","racing","running","walking","exercise","race"]},"woman-running":{"a":"Woman Running","b":"1F3C3-200D-2640-FE0F","j":["marathon","racing","running","woman","walking","exercise","race","female"]},"woman-dancing":{"a":"Woman Dancing","b":"1F483","j":["dance","dancing","woman","female","girl","fun"]},"man-dancing":{"a":"Man Dancing","b":"1F57A","j":["dance","dancing","man","male","boy","fun","dancer"]},"person-in-suit-levitating":{"a":"Person in Suit Levitating","b":"1F574","j":["business","person","suit","man_in_suit_levitating","levitate","hover","jump"]},"people-with-bunny-ears":{"a":"People with Bunny Ears","b":"1F46F","j":["bunny ear","dancer","partying","perform","costume"]},"men-with-bunny-ears":{"a":"Men with Bunny Ears","b":"1F46F-200D-2642-FE0F","j":["bunny ear","dancer","men","partying","male","bunny","boys"]},"women-with-bunny-ears":{"a":"Women with Bunny Ears","b":"1F46F-200D-2640-FE0F","j":["bunny ear","dancer","partying","women","female","bunny","girls"]},"person-in-steamy-room":{"a":"Person in Steamy Room","b":"1F9D6","j":["sauna","steam room","hamam","steambath","relax","spa"]},"man-in-steamy-room":{"a":"Man in Steamy Room","b":"1F9D6-200D-2642-FE0F","j":["sauna","steam room","male","man","spa","steamroom"]},"woman-in-steamy-room":{"a":"Woman in Steamy Room","b":"1F9D6-200D-2640-FE0F","j":["sauna","steam room","female","woman","spa","steamroom"]},"person-climbing":{"a":"Person Climbing","b":"1F9D7","j":["climber","sport"]},"man-climbing":{"a":"Man Climbing","b":"1F9D7-200D-2642-FE0F","j":["climber","sports","hobby","man","male","rock"]},"woman-climbing":{"a":"Woman Climbing","b":"1F9D7-200D-2640-FE0F","j":["climber","sports","hobby","woman","female","rock"]},"person-fencing":{"a":"Person Fencing","b":"1F93A","j":["fencer","fencing","sword","sports"]},"horse-racing":{"a":"Horse Racing","b":"1F3C7","j":["horse","jockey","racehorse","racing","animal","betting","competition","gambling","luck"]},"skier":{"a":"Skier","b":"26F7","j":["ski","snow","sports","winter"]},"snowboarder":{"a":"Snowboarder","b":"1F3C2","j":["ski","snow","snowboard","sports","winter"]},"person-golfing":{"a":"Person Golfing","b":"1F3CC","j":["ball","golf","sports","business"]},"man-golfing":{"a":"Man Golfing","b":"1F3CC-FE0F-200D-2642-FE0F","j":["golf","man","sport"]},"woman-golfing":{"a":"Woman Golfing","b":"1F3CC-FE0F-200D-2640-FE0F","j":["golf","woman","sports","business","female"]},"person-surfing":{"a":"Person Surfing","b":"1F3C4","j":["surfing","sport","sea"]},"man-surfing":{"a":"Man Surfing","b":"1F3C4-200D-2642-FE0F","j":["man","surfing","sports","ocean","sea","summer","beach"]},"woman-surfing":{"a":"Woman Surfing","b":"1F3C4-200D-2640-FE0F","j":["surfing","woman","sports","ocean","sea","summer","beach","female"]},"person-rowing-boat":{"a":"Person Rowing Boat","b":"1F6A3","j":["boat","rowboat","sport","move"]},"man-rowing-boat":{"a":"Man Rowing Boat","b":"1F6A3-200D-2642-FE0F","j":["boat","man","rowboat","sports","hobby","water","ship"]},"woman-rowing-boat":{"a":"Woman Rowing Boat","b":"1F6A3-200D-2640-FE0F","j":["boat","rowboat","woman","sports","hobby","water","ship","female"]},"person-swimming":{"a":"Person Swimming","b":"1F3CA","j":["swim","sport","pool"]},"man-swimming":{"a":"Man Swimming","b":"1F3CA-200D-2642-FE0F","j":["man","swim","sports","exercise","human","athlete","water","summer"]},"woman-swimming":{"a":"Woman Swimming","b":"1F3CA-200D-2640-FE0F","j":["swim","woman","sports","exercise","human","athlete","water","summer","female"]},"person-bouncing-ball":{"a":"Person Bouncing Ball","b":"26F9","j":["ball","sports","human"]},"man-bouncing-ball":{"a":"Man Bouncing Ball","b":"26F9-FE0F-200D-2642-FE0F","j":["ball","man","sport"]},"woman-bouncing-ball":{"a":"Woman Bouncing Ball","b":"26F9-FE0F-200D-2640-FE0F","j":["ball","woman","sports","human","female"]},"person-lifting-weights":{"a":"Person Lifting Weights","b":"1F3CB","j":["lifter","weight","sports","training","exercise"]},"man-lifting-weights":{"a":"Man Lifting Weights","b":"1F3CB-FE0F-200D-2642-FE0F","j":["man","weight lifter","sport"]},"woman-lifting-weights":{"a":"Woman Lifting Weights","b":"1F3CB-FE0F-200D-2640-FE0F","j":["weight lifter","woman","sports","training","exercise","female"]},"person-biking":{"a":"Person Biking","b":"1F6B4","j":["bicycle","biking","cyclist","sport","move"]},"man-biking":{"a":"Man Biking","b":"1F6B4-200D-2642-FE0F","j":["bicycle","biking","cyclist","man","sports","bike","exercise","hipster"]},"woman-biking":{"a":"Woman Biking","b":"1F6B4-200D-2640-FE0F","j":["bicycle","biking","cyclist","woman","sports","bike","exercise","hipster","female"]},"person-mountain-biking":{"a":"Person Mountain Biking","b":"1F6B5","j":["bicycle","bicyclist","bike","cyclist","mountain","sport","move"]},"man-mountain-biking":{"a":"Man Mountain Biking","b":"1F6B5-200D-2642-FE0F","j":["bicycle","bike","cyclist","man","mountain","transportation","sports","human","race"]},"woman-mountain-biking":{"a":"Woman Mountain Biking","b":"1F6B5-200D-2640-FE0F","j":["bicycle","bike","biking","cyclist","mountain","woman","transportation","sports","human","race","female"]},"person-cartwheeling":{"a":"Person Cartwheeling","b":"1F938","j":["cartwheel","gymnastics","sport","gymnastic"]},"man-cartwheeling":{"a":"Man Cartwheeling","b":"1F938-200D-2642-FE0F","j":["cartwheel","gymnastics","man"]},"woman-cartwheeling":{"a":"Woman Cartwheeling","b":"1F938-200D-2640-FE0F","j":["cartwheel","gymnastics","woman"]},"people-wrestling":{"a":"People Wrestling","b":"1F93C","j":["wrestle","wrestler","sport"]},"men-wrestling":{"a":"Men Wrestling","b":"1F93C-200D-2642-FE0F","j":["men","wrestle","sports","wrestlers"]},"women-wrestling":{"a":"Women Wrestling","b":"1F93C-200D-2640-FE0F","j":["women","wrestle","sports","wrestlers"]},"person-playing-water-polo":{"a":"Person Playing Water Polo","b":"1F93D","j":["polo","water","sport"]},"man-playing-water-polo":{"a":"Man Playing Water Polo","b":"1F93D-200D-2642-FE0F","j":["man","water polo","sports","pool"]},"woman-playing-water-polo":{"a":"Woman Playing Water Polo","b":"1F93D-200D-2640-FE0F","j":["water polo","woman","sports","pool"]},"person-playing-handball":{"a":"Person Playing Handball","b":"1F93E","j":["ball","handball","sport"]},"man-playing-handball":{"a":"Man Playing Handball","b":"1F93E-200D-2642-FE0F","j":["handball","man","sports"]},"woman-playing-handball":{"a":"Woman Playing Handball","b":"1F93E-200D-2640-FE0F","j":["handball","woman","sports"]},"person-juggling":{"a":"Person Juggling","b":"1F939","j":["balance","juggle","multitask","skill","performance"]},"man-juggling":{"a":"Man Juggling","b":"1F939-200D-2642-FE0F","j":["juggling","man","multitask","juggle","balance","skill"]},"woman-juggling":{"a":"Woman Juggling","b":"1F939-200D-2640-FE0F","j":["juggling","multitask","woman","juggle","balance","skill"]},"person-in-lotus-position":{"a":"Person in Lotus Position","b":"1F9D8","j":["meditation","yoga","serenity","meditate"]},"man-in-lotus-position":{"a":"Man in Lotus Position","b":"1F9D8-200D-2642-FE0F","j":["meditation","yoga","man","male","serenity","zen","mindfulness"]},"woman-in-lotus-position":{"a":"Woman in Lotus Position","b":"1F9D8-200D-2640-FE0F","j":["meditation","yoga","woman","female","serenity","zen","mindfulness"]},"person-taking-bath":{"a":"Person Taking Bath","b":"1F6C0","j":["bath","bathtub","clean","shower","bathroom"]},"person-in-bed":{"a":"Person in Bed","b":"1F6CC","j":["hotel","sleep","bed","rest"]},"people-holding-hands":{"a":"People Holding Hands","b":"1F9D1-200D-1F91D-200D-1F9D1","j":["couple","hand","hold","holding hands","person","friendship"]},"women-holding-hands":{"a":"Women Holding Hands","b":"1F46D","j":["couple","hand","holding hands","women","pair","friendship","love","like","female","people","human"]},"woman-and-man-holding-hands":{"a":"Woman and Man Holding Hands","b":"1F46B","j":["couple","hand","hold","holding hands","man","woman","pair","people","human","love","date","dating","like","affection","valentines","marriage"]},"men-holding-hands":{"a":"Men Holding Hands","b":"1F46C","j":["couple","Gemini","holding hands","man","men","twins","zodiac","pair","love","like","bromance","friendship","people","human"]},"kiss":{"a":"Kiss","b":"1F48F","j":["couple","pair","valentines","love","like","dating","marriage"]},"kiss-woman-man":{"a":"Kiss: Woman, Man","b":"1F469-200D-2764-FE0F-200D-1F48B-200D-1F468","j":["couple","kiss","man","woman","love"]},"kiss-man-man":{"a":"Kiss: Man, Man","b":"1F468-200D-2764-FE0F-200D-1F48B-200D-1F468","j":["couple","kiss","man","pair","valentines","love","like","dating","marriage"]},"kiss-woman-woman":{"a":"Kiss: Woman, Woman","b":"1F469-200D-2764-FE0F-200D-1F48B-200D-1F469","j":["couple","kiss","woman","pair","valentines","love","like","dating","marriage"]},"couple-with-heart":{"a":"Couple with Heart","b":"1F491","j":["couple","love","pair","like","affection","human","dating","valentines","marriage"]},"couple-with-heart-woman-man":{"a":"Couple with Heart: Woman, Man","b":"1F469-200D-2764-FE0F-200D-1F468","j":["couple","couple with heart","love","man","woman"]},"couple-with-heart-man-man":{"a":"Couple with Heart: Man, Man","b":"1F468-200D-2764-FE0F-200D-1F468","j":["couple","couple with heart","love","man","pair","like","affection","human","dating","valentines","marriage"]},"couple-with-heart-woman-woman":{"a":"Couple with Heart: Woman, Woman","b":"1F469-200D-2764-FE0F-200D-1F469","j":["couple","couple with heart","love","woman","pair","like","affection","human","dating","valentines","marriage"]},"family":{"a":"Family","b":"1F46A","j":["home","parents","child","mom","dad","father","mother","people","human"]},"family-man-woman-boy":{"a":"Family: Man, Woman, Boy","b":"1F468-200D-1F469-200D-1F466","j":["boy","family","man","woman","love"]},"family-man-woman-girl":{"a":"Family: Man, Woman, Girl","b":"1F468-200D-1F469-200D-1F467","j":["family","girl","man","woman","home","parents","people","human","child"]},"family-man-woman-girl-boy":{"a":"Family: Man, Woman, Girl, Boy","b":"1F468-200D-1F469-200D-1F467-200D-1F466","j":["boy","family","girl","man","woman","home","parents","people","human","children"]},"family-man-woman-boy-boy":{"a":"Family: Man, Woman, Boy, Boy","b":"1F468-200D-1F469-200D-1F466-200D-1F466","j":["boy","family","man","woman","home","parents","people","human","children"]},"family-man-woman-girl-girl":{"a":"Family: Man, Woman, Girl, Girl","b":"1F468-200D-1F469-200D-1F467-200D-1F467","j":["family","girl","man","woman","home","parents","people","human","children"]},"family-man-man-boy":{"a":"Family: Man, Man, Boy","b":"1F468-200D-1F468-200D-1F466","j":["boy","family","man","home","parents","people","human","children"]},"family-man-man-girl":{"a":"Family: Man, Man, Girl","b":"1F468-200D-1F468-200D-1F467","j":["family","girl","man","home","parents","people","human","children"]},"family-man-man-girl-boy":{"a":"Family: Man, Man, Girl, Boy","b":"1F468-200D-1F468-200D-1F467-200D-1F466","j":["boy","family","girl","man","home","parents","people","human","children"]},"family-man-man-boy-boy":{"a":"Family: Man, Man, Boy, Boy","b":"1F468-200D-1F468-200D-1F466-200D-1F466","j":["boy","family","man","home","parents","people","human","children"]},"family-man-man-girl-girl":{"a":"Family: Man, Man, Girl, Girl","b":"1F468-200D-1F468-200D-1F467-200D-1F467","j":["family","girl","man","home","parents","people","human","children"]},"family-woman-woman-boy":{"a":"Family: Woman, Woman, Boy","b":"1F469-200D-1F469-200D-1F466","j":["boy","family","woman","home","parents","people","human","children"]},"family-woman-woman-girl":{"a":"Family: Woman, Woman, Girl","b":"1F469-200D-1F469-200D-1F467","j":["family","girl","woman","home","parents","people","human","children"]},"family-woman-woman-girl-boy":{"a":"Family: Woman, Woman, Girl, Boy","b":"1F469-200D-1F469-200D-1F467-200D-1F466","j":["boy","family","girl","woman","home","parents","people","human","children"]},"family-woman-woman-boy-boy":{"a":"Family: Woman, Woman, Boy, Boy","b":"1F469-200D-1F469-200D-1F466-200D-1F466","j":["boy","family","woman","home","parents","people","human","children"]},"family-woman-woman-girl-girl":{"a":"Family: Woman, Woman, Girl, Girl","b":"1F469-200D-1F469-200D-1F467-200D-1F467","j":["family","girl","woman","home","parents","people","human","children"]},"family-man-boy":{"a":"Family: Man, Boy","b":"1F468-200D-1F466","j":["boy","family","man","home","parent","people","human","child"]},"family-man-boy-boy":{"a":"Family: Man, Boy, Boy","b":"1F468-200D-1F466-200D-1F466","j":["boy","family","man","home","parent","people","human","children"]},"family-man-girl":{"a":"Family: Man, Girl","b":"1F468-200D-1F467","j":["family","girl","man","home","parent","people","human","child"]},"family-man-girl-boy":{"a":"Family: Man, Girl, Boy","b":"1F468-200D-1F467-200D-1F466","j":["boy","family","girl","man","home","parent","people","human","children"]},"family-man-girl-girl":{"a":"Family: Man, Girl, Girl","b":"1F468-200D-1F467-200D-1F467","j":["family","girl","man","home","parent","people","human","children"]},"family-woman-boy":{"a":"Family: Woman, Boy","b":"1F469-200D-1F466","j":["boy","family","woman","home","parent","people","human","child"]},"family-woman-boy-boy":{"a":"Family: Woman, Boy, Boy","b":"1F469-200D-1F466-200D-1F466","j":["boy","family","woman","home","parent","people","human","children"]},"family-woman-girl":{"a":"Family: Woman, Girl","b":"1F469-200D-1F467","j":["family","girl","woman","home","parent","people","human","child"]},"family-woman-girl-boy":{"a":"Family: Woman, Girl, Boy","b":"1F469-200D-1F467-200D-1F466","j":["boy","family","girl","woman","home","parent","people","human","children"]},"family-woman-girl-girl":{"a":"Family: Woman, Girl, Girl","b":"1F469-200D-1F467-200D-1F467","j":["family","girl","woman","home","parent","people","human","children"]},"speaking-head":{"a":"Speaking Head","b":"1F5E3","j":["face","head","silhouette","speak","speaking","user","person","human","sing","say","talk"]},"bust-in-silhouette":{"a":"Bust in Silhouette","b":"1F464","j":["bust","silhouette","user","person","human"]},"busts-in-silhouette":{"a":"Busts in Silhouette","b":"1F465","j":["bust","silhouette","user","person","human","group","team"]},"people-hugging":{"a":"People Hugging","b":"1FAC2","j":["goodbye","hello","hug","thanks","care"]},"footprints":{"a":"Footprints","b":"1F463","j":["clothing","footprint","print","feet","tracking","walking","beach"]},"red-hair":{"a":"Red Hair","b":"1F9B0","j":["ginger","red hair","redhead"]},"curly-hair":{"a":"Curly Hair","b":"1F9B1","j":["afro","curly","curly hair","ringlets"]},"white-hair":{"a":"White Hair","b":"1F9B3","j":["gray","hair","old","white"]},"bald":{"a":"Bald","b":"1F9B2","j":["bald","chemotherapy","hairless","no hair","shaven"]},"monkey-face":{"a":"Monkey Face","b":"1F435","j":["face","monkey","animal","nature","circus"]},"monkey":{"a":"Monkey","b":"1F412","j":["animal","nature","banana","circus"]},"gorilla":{"a":"Gorilla","b":"1F98D","j":["animal","nature","circus"]},"orangutan":{"a":"Orangutan","b":"1F9A7","j":["ape","animal"]},"dog-face":{"a":"Dog Face","b":"1F436","j":["dog","face","pet","animal","friend","nature","woof","puppy","faithful"]},"dog":{"a":"Dog","b":"1F415","j":["pet","animal","nature","friend","doge","faithful"]},"guide-dog":{"a":"Guide Dog","b":"1F9AE","j":["accessibility","blind","guide","animal"]},"service-dog":{"a":"Service Dog","b":"1F415-200D-1F9BA","j":["accessibility","assistance","dog","service","blind","animal"]},"poodle":{"a":"Poodle","b":"1F429","j":["dog","animal","101","nature","pet"]},"wolf":{"a":"Wolf","b":"1F43A","j":["face","animal","nature","wild"]},"fox":{"a":"Fox","b":"1F98A","j":["face","animal","nature"]},"raccoon":{"a":"Raccoon","b":"1F99D","j":["curious","sly","animal","nature"]},"cat-face":{"a":"Cat Face","b":"1F431","j":["cat","face","pet","animal","meow","nature","kitten"]},"cat":{"a":"Cat","b":"1F408","j":["pet","animal","meow","cats"]},"black-cat":{"a":"Black Cat","b":"1F408-200D-2B1B","j":["black","cat","unlucky","superstition","luck"]},"lion":{"a":"Lion","b":"1F981","j":["face","Leo","zodiac","animal","nature"]},"tiger-face":{"a":"Tiger Face","b":"1F42F","j":["face","tiger","animal","cat","danger","wild","nature","roar"]},"tiger":{"a":"Tiger","b":"1F405","j":["animal","nature","roar"]},"leopard":{"a":"Leopard","b":"1F406","j":["animal","nature"]},"horse-face":{"a":"Horse Face","b":"1F434","j":["face","horse","animal","brown","nature"]},"horse":{"a":"Horse","b":"1F40E","j":["equestrian","racehorse","racing","animal","gamble","luck"]},"unicorn":{"a":"Unicorn","b":"1F984","j":["face","animal","nature","mystical"]},"zebra":{"a":"Zebra","b":"1F993","j":["stripe","animal","nature","stripes","safari"]},"deer":{"a":"Deer","b":"1F98C","j":["animal","nature","horns","venison"]},"bison":{"a":"Bison","b":"1F9AC","j":["buffalo","herd","wisent","ox"]},"cow-face":{"a":"Cow Face","b":"1F42E","j":["cow","face","beef","ox","animal","nature","moo","milk"]},"ox":{"a":"Ox","b":"1F402","j":["bull","Taurus","zodiac","animal","cow","beef"]},"water-buffalo":{"a":"Water Buffalo","b":"1F403","j":["buffalo","water","animal","nature","ox","cow"]},"cow":{"a":"Cow","b":"1F404","j":["beef","ox","animal","nature","moo","milk"]},"pig-face":{"a":"Pig Face","b":"1F437","j":["face","pig","animal","oink","nature"]},"pig":{"a":"Pig","b":"1F416","j":["sow","animal","nature"]},"boar":{"a":"Boar","b":"1F417","j":["pig","animal","nature"]},"pig-nose":{"a":"Pig Nose","b":"1F43D","j":["face","nose","pig","animal","oink"]},"ram":{"a":"Ram","b":"1F40F","j":["Aries","male","sheep","zodiac","animal","nature"]},"ewe":{"a":"Ewe","b":"1F411","j":["female","sheep","animal","nature","wool","shipit"]},"goat":{"a":"Goat","b":"1F410","j":["Capricorn","zodiac","animal","nature"]},"camel":{"a":"Camel","b":"1F42A","j":["dromedary","hump","animal","hot","desert"]},"twohump-camel":{"a":"Two-Hump Camel","b":"1F42B","j":["bactrian","camel","hump","two-hump camel","two_hump_camel","animal","nature","hot","desert"]},"llama":{"a":"Llama","b":"1F999","j":["alpaca","guanaco","vicuña","wool","animal","nature"]},"giraffe":{"a":"Giraffe","b":"1F992","j":["spots","animal","nature","safari"]},"elephant":{"a":"Elephant","b":"1F418","j":["animal","nature","nose","th","circus"]},"mammoth":{"a":"Mammoth","b":"1F9A3","j":["extinction","large","tusk","woolly","elephant","tusks"]},"rhinoceros":{"a":"Rhinoceros","b":"1F98F","j":["animal","nature","horn"]},"hippopotamus":{"a":"Hippopotamus","b":"1F99B","j":["hippo","animal","nature"]},"mouse-face":{"a":"Mouse Face","b":"1F42D","j":["face","mouse","animal","nature","cheese_wedge","rodent"]},"mouse":{"a":"Mouse","b":"1F401","j":["animal","nature","rodent"]},"rat":{"a":"Rat","b":"1F400","j":["animal","mouse","rodent"]},"hamster":{"a":"Hamster","b":"1F439","j":["face","pet","animal","nature"]},"rabbit-face":{"a":"Rabbit Face","b":"1F430","j":["bunny","face","pet","rabbit","animal","nature","spring","magic"]},"rabbit":{"a":"Rabbit","b":"1F407","j":["bunny","pet","animal","nature","magic","spring"]},"chipmunk":{"a":"Chipmunk","b":"1F43F","j":["squirrel","animal","nature","rodent"]},"beaver":{"a":"Beaver","b":"1F9AB","j":["dam","animal","rodent"]},"hedgehog":{"a":"Hedgehog","b":"1F994","j":["spiny","animal","nature"]},"bat":{"a":"Bat","b":"1F987","j":["vampire","animal","nature","blind"]},"bear":{"a":"Bear","b":"1F43B","j":["face","animal","nature","wild"]},"polar-bear":{"a":"Polar Bear","b":"1F43B-200D-2744-FE0F","j":["arctic","bear","white","animal"]},"koala":{"a":"Koala","b":"1F428","j":["face","marsupial","animal","nature"]},"panda":{"a":"Panda","b":"1F43C","j":["face","animal","nature"]},"sloth":{"a":"Sloth","b":"1F9A5","j":["lazy","slow","animal"]},"otter":{"a":"Otter","b":"1F9A6","j":["fishing","playful","animal"]},"skunk":{"a":"Skunk","b":"1F9A8","j":["stink","animal"]},"kangaroo":{"a":"Kangaroo","b":"1F998","j":["Australia","joey","jump","marsupial","animal","nature","australia","hop"]},"badger":{"a":"Badger","b":"1F9A1","j":["honey badger","pester","animal","nature","honey"]},"paw-prints":{"a":"Paw Prints","b":"1F43E","j":["feet","paw","print","animal","tracking","footprints","dog","cat","pet"]},"turkey":{"a":"Turkey","b":"1F983","j":["bird","animal"]},"chicken":{"a":"Chicken","b":"1F414","j":["bird","animal","cluck","nature"]},"rooster":{"a":"Rooster","b":"1F413","j":["bird","animal","nature","chicken"]},"hatching-chick":{"a":"Hatching Chick","b":"1F423","j":["baby","bird","chick","hatching","animal","chicken","egg","born"]},"baby-chick":{"a":"Baby Chick","b":"1F424","j":["baby","bird","chick","animal","chicken"]},"frontfacing-baby-chick":{"a":"Front-Facing Baby Chick","b":"1F425","j":["baby","bird","chick","front-facing baby chick","front_facing_baby_chick","animal","chicken"]},"bird":{"a":"Bird","b":"1F426","j":["animal","nature","fly","tweet","spring"]},"penguin":{"a":"Penguin","b":"1F427","j":["bird","animal","nature"]},"dove":{"a":"Dove","b":"1F54A","j":["bird","fly","peace","animal"]},"eagle":{"a":"Eagle","b":"1F985","j":["bird","animal","nature"]},"duck":{"a":"Duck","b":"1F986","j":["bird","animal","nature","mallard"]},"swan":{"a":"Swan","b":"1F9A2","j":["bird","cygnet","ugly duckling","animal","nature"]},"owl":{"a":"Owl","b":"1F989","j":["bird","wise","animal","nature","hoot"]},"dodo":{"a":"Dodo","b":"1F9A4","j":["extinction","large","Mauritius","animal","bird"]},"feather":{"a":"Feather","b":"1FAB6","j":["bird","flight","light","plumage","fly"]},"flamingo":{"a":"Flamingo","b":"1F9A9","j":["flamboyant","tropical","animal"]},"peacock":{"a":"Peacock","b":"1F99A","j":["bird","ostentatious","peahen","proud","animal","nature"]},"parrot":{"a":"Parrot","b":"1F99C","j":["bird","pirate","talk","animal","nature"]},"frog":{"a":"Frog","b":"1F438","j":["face","animal","nature","croak","toad"]},"crocodile":{"a":"Crocodile","b":"1F40A","j":["animal","nature","reptile","lizard","alligator"]},"turtle":{"a":"Turtle","b":"1F422","j":["terrapin","tortoise","animal","slow","nature"]},"lizard":{"a":"Lizard","b":"1F98E","j":["reptile","animal","nature"]},"snake":{"a":"Snake","b":"1F40D","j":["bearer","Ophiuchus","serpent","zodiac","animal","evil","nature","hiss","python"]},"dragon-face":{"a":"Dragon Face","b":"1F432","j":["dragon","face","fairy tale","animal","myth","nature","chinese","green"]},"dragon":{"a":"Dragon","b":"1F409","j":["fairy tale","animal","myth","nature","chinese","green"]},"sauropod":{"a":"Sauropod","b":"1F995","j":["brachiosaurus","brontosaurus","diplodocus","animal","nature","dinosaur","extinct"]},"trex":{"a":"T-Rex","b":"1F996","j":["Tyrannosaurus Rex","t_rex","animal","nature","dinosaur","tyrannosaurus","extinct"]},"spouting-whale":{"a":"Spouting Whale","b":"1F433","j":["face","spouting","whale","animal","nature","sea","ocean"]},"whale":{"a":"Whale","b":"1F40B","j":["animal","nature","sea","ocean"]},"dolphin":{"a":"Dolphin","b":"1F42C","j":["flipper","animal","nature","fish","sea","ocean","fins","beach"]},"seal":{"a":"Seal","b":"1F9AD","j":["sea lion","animal","creature","sea"]},"fish":{"a":"Fish","b":"1F41F","j":["Pisces","zodiac","animal","food","nature"]},"tropical-fish":{"a":"Tropical Fish","b":"1F420","j":["fish","tropical","animal","swim","ocean","beach","nemo"]},"blowfish":{"a":"Blowfish","b":"1F421","j":["fish","animal","nature","food","sea","ocean"]},"shark":{"a":"Shark","b":"1F988","j":["fish","animal","nature","sea","ocean","jaws","fins","beach"]},"octopus":{"a":"Octopus","b":"1F419","j":["animal","creature","ocean","sea","nature","beach"]},"spiral-shell":{"a":"Spiral Shell","b":"1F41A","j":["shell","spiral","nature","sea","beach"]},"coral":{"a":"⊛ Coral","b":"1FAB8","j":["ocean","reef"]},"snail":{"a":"Snail","b":"1F40C","j":["slow","animal","shell"]},"butterfly":{"a":"Butterfly","b":"1F98B","j":["insect","pretty","animal","nature","caterpillar"]},"bug":{"a":"Bug","b":"1F41B","j":["insect","animal","nature","worm"]},"ant":{"a":"Ant","b":"1F41C","j":["insect","animal","nature","bug"]},"honeybee":{"a":"Honeybee","b":"1F41D","j":["bee","insect","animal","nature","bug","spring","honey"]},"beetle":{"a":"Beetle","b":"1FAB2","j":["bug","insect"]},"lady-beetle":{"a":"Lady Beetle","b":"1F41E","j":["beetle","insect","ladybird","ladybug","animal","nature"]},"cricket":{"a":"Cricket","b":"1F997","j":["grasshopper","Orthoptera","animal","chirp"]},"cockroach":{"a":"Cockroach","b":"1FAB3","j":["insect","pest","roach","pests"]},"spider":{"a":"Spider","b":"1F577","j":["insect","animal","arachnid"]},"spider-web":{"a":"Spider Web","b":"1F578","j":["spider","web","animal","insect","arachnid","silk"]},"scorpion":{"a":"Scorpion","b":"1F982","j":["scorpio","Scorpio","zodiac","animal","arachnid"]},"mosquito":{"a":"Mosquito","b":"1F99F","j":["disease","fever","malaria","pest","virus","animal","nature","insect"]},"fly":{"a":"Fly","b":"1FAB0","j":["disease","maggot","pest","rotting","insect"]},"worm":{"a":"Worm","b":"1FAB1","j":["annelid","earthworm","parasite","animal"]},"microbe":{"a":"Microbe","b":"1F9A0","j":["amoeba","bacteria","virus","germs"]},"bouquet":{"a":"Bouquet","b":"1F490","j":["flower","flowers","nature","spring"]},"cherry-blossom":{"a":"Cherry Blossom","b":"1F338","j":["blossom","cherry","flower","nature","plant","spring"]},"white-flower":{"a":"White Flower","b":"1F4AE","j":["flower","japanese","spring"]},"lotus":{"a":"⊛ Lotus","b":"1FAB7","j":["Buddhism","flower","Hinduism","India","purity","Vietnam"]},"rosette":{"a":"Rosette","b":"1F3F5","j":["plant","flower","decoration","military"]},"rose":{"a":"Rose","b":"1F339","j":["flower","flowers","valentines","love","spring"]},"wilted-flower":{"a":"Wilted Flower","b":"1F940","j":["flower","wilted","plant","nature"]},"hibiscus":{"a":"Hibiscus","b":"1F33A","j":["flower","plant","vegetable","flowers","beach"]},"sunflower":{"a":"Sunflower","b":"1F33B","j":["flower","sun","nature","plant","fall"]},"blossom":{"a":"Blossom","b":"1F33C","j":["flower","nature","flowers","yellow"]},"tulip":{"a":"Tulip","b":"1F337","j":["flower","flowers","plant","nature","summer","spring"]},"seedling":{"a":"Seedling","b":"1F331","j":["young","plant","nature","grass","lawn","spring"]},"potted-plant":{"a":"Potted Plant","b":"1FAB4","j":["boring","grow","house","nurturing","plant","useless","greenery"]},"evergreen-tree":{"a":"Evergreen Tree","b":"1F332","j":["tree","plant","nature"]},"deciduous-tree":{"a":"Deciduous Tree","b":"1F333","j":["deciduous","shedding","tree","plant","nature"]},"palm-tree":{"a":"Palm Tree","b":"1F334","j":["palm","tree","plant","vegetable","nature","summer","beach","mojito","tropical"]},"cactus":{"a":"Cactus","b":"1F335","j":["plant","vegetable","nature"]},"sheaf-of-rice":{"a":"Sheaf of Rice","b":"1F33E","j":["ear","grain","rice","nature","plant"]},"herb":{"a":"Herb","b":"1F33F","j":["leaf","vegetable","plant","medicine","weed","grass","lawn"]},"shamrock":{"a":"Shamrock","b":"2618","j":["plant","vegetable","nature","irish","clover"]},"four-leaf-clover":{"a":"Four Leaf Clover","b":"1F340","j":["4","clover","four","four-leaf clover","leaf","vegetable","plant","nature","lucky","irish"]},"maple-leaf":{"a":"Maple Leaf","b":"1F341","j":["falling","leaf","maple","nature","plant","vegetable","ca","fall"]},"fallen-leaf":{"a":"Fallen Leaf","b":"1F342","j":["falling","leaf","nature","plant","vegetable","leaves"]},"leaf-fluttering-in-wind":{"a":"Leaf Fluttering in Wind","b":"1F343","j":["blow","flutter","leaf","wind","nature","plant","tree","vegetable","grass","lawn","spring"]},"empty-nest":{"a":"⊛ Empty Nest","b":"1FAB9","j":["nesting"]},"nest-with-eggs":{"a":"⊛ Nest with Eggs","b":"1FABA","j":["nesting"]},"grapes":{"a":"Grapes","b":"1F347","j":["fruit","grape","food","wine"]},"melon":{"a":"Melon","b":"1F348","j":["fruit","nature","food"]},"watermelon":{"a":"Watermelon","b":"1F349","j":["fruit","food","picnic","summer"]},"tangerine":{"a":"Tangerine","b":"1F34A","j":["fruit","orange","food","nature"]},"lemon":{"a":"Lemon","b":"1F34B","j":["citrus","fruit","nature"]},"banana":{"a":"Banana","b":"1F34C","j":["fruit","food","monkey"]},"pineapple":{"a":"Pineapple","b":"1F34D","j":["fruit","nature","food"]},"mango":{"a":"Mango","b":"1F96D","j":["fruit","tropical","food"]},"red-apple":{"a":"Red Apple","b":"1F34E","j":["apple","fruit","red","mac","school"]},"green-apple":{"a":"Green Apple","b":"1F34F","j":["apple","fruit","green","nature"]},"pear":{"a":"Pear","b":"1F350","j":["fruit","nature","food"]},"peach":{"a":"Peach","b":"1F351","j":["fruit","nature","food"]},"cherries":{"a":"Cherries","b":"1F352","j":["berries","cherry","fruit","red","food"]},"strawberry":{"a":"Strawberry","b":"1F353","j":["berry","fruit","food","nature"]},"blueberries":{"a":"Blueberries","b":"1FAD0","j":["berry","bilberry","blue","blueberry","fruit"]},"kiwi-fruit":{"a":"Kiwi Fruit","b":"1F95D","j":["food","fruit","kiwi"]},"tomato":{"a":"Tomato","b":"1F345","j":["fruit","vegetable","nature","food"]},"olive":{"a":"Olive","b":"1FAD2","j":["food","fruit"]},"coconut":{"a":"Coconut","b":"1F965","j":["palm","piña colada","fruit","nature","food"]},"avocado":{"a":"Avocado","b":"1F951","j":["food","fruit"]},"eggplant":{"a":"Eggplant","b":"1F346","j":["aubergine","vegetable","nature","food"]},"potato":{"a":"Potato","b":"1F954","j":["food","vegetable","tuber","vegatable","starch"]},"carrot":{"a":"Carrot","b":"1F955","j":["food","vegetable","orange"]},"ear-of-corn":{"a":"Ear of Corn","b":"1F33D","j":["corn","ear","maize","maze","food","vegetable","plant"]},"hot-pepper":{"a":"Hot Pepper","b":"1F336","j":["hot","pepper","food","spicy","chilli","chili"]},"bell-pepper":{"a":"Bell Pepper","b":"1FAD1","j":["capsicum","pepper","vegetable","fruit","plant"]},"cucumber":{"a":"Cucumber","b":"1F952","j":["food","pickle","vegetable","fruit"]},"leafy-green":{"a":"Leafy Green","b":"1F96C","j":["bok choy","cabbage","kale","lettuce","food","vegetable","plant"]},"broccoli":{"a":"Broccoli","b":"1F966","j":["wild cabbage","fruit","food","vegetable"]},"garlic":{"a":"Garlic","b":"1F9C4","j":["flavoring","food","spice","cook"]},"onion":{"a":"Onion","b":"1F9C5","j":["flavoring","cook","food","spice"]},"mushroom":{"a":"Mushroom","b":"1F344","j":["toadstool","plant","vegetable"]},"peanuts":{"a":"Peanuts","b":"1F95C","j":["food","nut","peanut","vegetable"]},"beans":{"a":"⊛ Beans","b":"1FAD8","j":["food","kidney","legume"]},"chestnut":{"a":"Chestnut","b":"1F330","j":["plant","food","squirrel"]},"bread":{"a":"Bread","b":"1F35E","j":["loaf","food","wheat","breakfast","toast"]},"croissant":{"a":"Croissant","b":"1F950","j":["bread","breakfast","food","french","roll"]},"baguette-bread":{"a":"Baguette Bread","b":"1F956","j":["baguette","bread","food","french"]},"flatbread":{"a":"Flatbread","b":"1FAD3","j":["arepa","lavash","naan","pita","flour","food"]},"pretzel":{"a":"Pretzel","b":"1F968","j":["twisted","convoluted","food","bread"]},"bagel":{"a":"Bagel","b":"1F96F","j":["bakery","breakfast","schmear","food","bread"]},"pancakes":{"a":"Pancakes","b":"1F95E","j":["breakfast","crêpe","food","hotcake","pancake","flapjacks","hotcakes"]},"waffle":{"a":"Waffle","b":"1F9C7","j":["breakfast","indecisive","iron","food"]},"cheese-wedge":{"a":"Cheese Wedge","b":"1F9C0","j":["cheese","food","chadder"]},"meat-on-bone":{"a":"Meat on Bone","b":"1F356","j":["bone","meat","good","food","drumstick"]},"poultry-leg":{"a":"Poultry Leg","b":"1F357","j":["bone","chicken","drumstick","leg","poultry","food","meat","bird","turkey"]},"cut-of-meat":{"a":"Cut of Meat","b":"1F969","j":["chop","lambchop","porkchop","steak","food","cow","meat","cut"]},"bacon":{"a":"Bacon","b":"1F953","j":["breakfast","food","meat","pork","pig"]},"hamburger":{"a":"Hamburger","b":"1F354","j":["burger","meat","fast food","beef","cheeseburger","mcdonalds","burger king"]},"french-fries":{"a":"French Fries","b":"1F35F","j":["french","fries","chips","snack","fast food"]},"pizza":{"a":"Pizza","b":"1F355","j":["cheese","slice","food","party"]},"hot-dog":{"a":"Hot Dog","b":"1F32D","j":["frankfurter","hotdog","sausage","food"]},"sandwich":{"a":"Sandwich","b":"1F96A","j":["bread","food","lunch"]},"taco":{"a":"Taco","b":"1F32E","j":["mexican","food"]},"burrito":{"a":"Burrito","b":"1F32F","j":["mexican","wrap","food"]},"tamale":{"a":"Tamale","b":"1FAD4","j":["mexican","wrapped","food","masa"]},"stuffed-flatbread":{"a":"Stuffed Flatbread","b":"1F959","j":["falafel","flatbread","food","gyro","kebab","stuffed"]},"falafel":{"a":"Falafel","b":"1F9C6","j":["chickpea","meatball","food"]},"egg":{"a":"Egg","b":"1F95A","j":["breakfast","food","chicken"]},"cooking":{"a":"Cooking","b":"1F373","j":["breakfast","egg","frying","pan","food","kitchen"]},"shallow-pan-of-food":{"a":"Shallow Pan of Food","b":"1F958","j":["casserole","food","paella","pan","shallow","cooking"]},"pot-of-food":{"a":"Pot of Food","b":"1F372","j":["pot","stew","food","meat","soup"]},"fondue":{"a":"Fondue","b":"1FAD5","j":["cheese","chocolate","melted","pot","Swiss","food"]},"bowl-with-spoon":{"a":"Bowl with Spoon","b":"1F963","j":["breakfast","cereal","congee","oatmeal","porridge","food"]},"green-salad":{"a":"Green Salad","b":"1F957","j":["food","green","salad","healthy","lettuce"]},"popcorn":{"a":"Popcorn","b":"1F37F","j":["food","movie theater","films","snack"]},"butter":{"a":"Butter","b":"1F9C8","j":["dairy","food","cook"]},"salt":{"a":"Salt","b":"1F9C2","j":["condiment","shaker"]},"canned-food":{"a":"Canned Food","b":"1F96B","j":["can","food","soup"]},"bento-box":{"a":"Bento Box","b":"1F371","j":["bento","box","food","japanese"]},"rice-cracker":{"a":"Rice Cracker","b":"1F358","j":["cracker","rice","food","japanese"]},"rice-ball":{"a":"Rice Ball","b":"1F359","j":["ball","Japanese","rice","food","japanese"]},"cooked-rice":{"a":"Cooked Rice","b":"1F35A","j":["cooked","rice","food","china","asian"]},"curry-rice":{"a":"Curry Rice","b":"1F35B","j":["curry","rice","food","spicy","hot","indian"]},"steaming-bowl":{"a":"Steaming Bowl","b":"1F35C","j":["bowl","noodle","ramen","steaming","food","japanese","chopsticks"]},"spaghetti":{"a":"Spaghetti","b":"1F35D","j":["pasta","food","italian","noodle"]},"roasted-sweet-potato":{"a":"Roasted Sweet Potato","b":"1F360","j":["potato","roasted","sweet","food","nature"]},"oden":{"a":"Oden","b":"1F362","j":["kebab","seafood","skewer","stick","food","japanese"]},"sushi":{"a":"Sushi","b":"1F363","j":["food","fish","japanese","rice"]},"fried-shrimp":{"a":"Fried Shrimp","b":"1F364","j":["fried","prawn","shrimp","tempura","food","animal","appetizer","summer"]},"fish-cake-with-swirl":{"a":"Fish Cake with Swirl","b":"1F365","j":["cake","fish","pastry","swirl","food","japan","sea","beach","narutomaki","pink","kamaboko","surimi","ramen"]},"moon-cake":{"a":"Moon Cake","b":"1F96E","j":["autumn","festival","yuèbǐng","food"]},"dango":{"a":"Dango","b":"1F361","j":["dessert","Japanese","skewer","stick","sweet","food","japanese","barbecue","meat"]},"dumpling":{"a":"Dumpling","b":"1F95F","j":["empanada","gyōza","jiaozi","pierogi","potsticker","food"]},"fortune-cookie":{"a":"Fortune Cookie","b":"1F960","j":["prophecy","food"]},"takeout-box":{"a":"Takeout Box","b":"1F961","j":["oyster pail","food","leftovers"]},"crab":{"a":"Crab","b":"1F980","j":["Cancer","zodiac","animal","crustacean"]},"lobster":{"a":"Lobster","b":"1F99E","j":["bisque","claws","seafood","animal","nature"]},"shrimp":{"a":"Shrimp","b":"1F990","j":["food","shellfish","small","animal","ocean","nature","seafood"]},"squid":{"a":"Squid","b":"1F991","j":["food","molusc","animal","nature","ocean","sea"]},"oyster":{"a":"Oyster","b":"1F9AA","j":["diving","pearl","food"]},"soft-ice-cream":{"a":"Soft Ice Cream","b":"1F366","j":["cream","dessert","ice","icecream","soft","sweet","food","hot","summer"]},"shaved-ice":{"a":"Shaved Ice","b":"1F367","j":["dessert","ice","shaved","sweet","hot","summer"]},"ice-cream":{"a":"Ice Cream","b":"1F368","j":["cream","dessert","ice","sweet","food","hot"]},"doughnut":{"a":"Doughnut","b":"1F369","j":["breakfast","dessert","donut","sweet","food","snack"]},"cookie":{"a":"Cookie","b":"1F36A","j":["dessert","sweet","food","snack","oreo","chocolate"]},"birthday-cake":{"a":"Birthday Cake","b":"1F382","j":["birthday","cake","celebration","dessert","pastry","sweet","food"]},"shortcake":{"a":"Shortcake","b":"1F370","j":["cake","dessert","pastry","slice","sweet","food"]},"cupcake":{"a":"Cupcake","b":"1F9C1","j":["bakery","sweet","food","dessert"]},"pie":{"a":"Pie","b":"1F967","j":["filling","pastry","fruit","meat","food","dessert"]},"chocolate-bar":{"a":"Chocolate Bar","b":"1F36B","j":["bar","chocolate","dessert","sweet","food","snack"]},"candy":{"a":"Candy","b":"1F36C","j":["dessert","sweet","snack","lolly"]},"lollipop":{"a":"Lollipop","b":"1F36D","j":["candy","dessert","sweet","food","snack"]},"custard":{"a":"Custard","b":"1F36E","j":["dessert","pudding","sweet","food"]},"honey-pot":{"a":"Honey Pot","b":"1F36F","j":["honey","honeypot","pot","sweet","bees","kitchen"]},"baby-bottle":{"a":"Baby Bottle","b":"1F37C","j":["baby","bottle","drink","milk","food","container"]},"glass-of-milk":{"a":"Glass of Milk","b":"1F95B","j":["drink","glass","milk","beverage","cow"]},"hot-beverage":{"a":"Hot Beverage","b":"2615","j":["beverage","coffee","drink","hot","steaming","tea","caffeine","latte","espresso"]},"teapot":{"a":"Teapot","b":"1FAD6","j":["drink","pot","tea","hot"]},"teacup-without-handle":{"a":"Teacup Without Handle","b":"1F375","j":["beverage","cup","drink","tea","teacup","bowl","breakfast","green","british"]},"sake":{"a":"Sake","b":"1F376","j":["bar","beverage","bottle","cup","drink","wine","drunk","japanese","alcohol","booze"]},"bottle-with-popping-cork":{"a":"Bottle with Popping Cork","b":"1F37E","j":["bar","bottle","cork","drink","popping","wine","celebration"]},"wine-glass":{"a":"Wine Glass","b":"1F377","j":["bar","beverage","drink","glass","wine","drunk","alcohol","booze"]},"cocktail-glass":{"a":"Cocktail Glass","b":"1F378","j":["bar","cocktail","drink","glass","drunk","alcohol","beverage","booze","mojito"]},"tropical-drink":{"a":"Tropical Drink","b":"1F379","j":["bar","drink","tropical","beverage","cocktail","summer","beach","alcohol","booze","mojito"]},"beer-mug":{"a":"Beer Mug","b":"1F37A","j":["bar","beer","drink","mug","relax","beverage","drunk","party","pub","summer","alcohol","booze"]},"clinking-beer-mugs":{"a":"Clinking Beer Mugs","b":"1F37B","j":["bar","beer","clink","drink","mug","relax","beverage","drunk","party","pub","summer","alcohol","booze"]},"clinking-glasses":{"a":"Clinking Glasses","b":"1F942","j":["celebrate","clink","drink","glass","beverage","party","alcohol","cheers","wine","champagne","toast"]},"tumbler-glass":{"a":"Tumbler Glass","b":"1F943","j":["glass","liquor","shot","tumbler","whisky","drink","beverage","drunk","alcohol","booze","bourbon","scotch"]},"pouring-liquid":{"a":"⊛ Pouring Liquid","b":"1FAD7","j":["drink","empty","glass","spill"]},"cup-with-straw":{"a":"Cup with Straw","b":"1F964","j":["juice","soda","malt","soft drink","water","drink"]},"bubble-tea":{"a":"Bubble Tea","b":"1F9CB","j":["bubble","milk","pearl","tea","taiwan","boba","milk tea","straw"]},"beverage-box":{"a":"Beverage Box","b":"1F9C3","j":["beverage","box","juice","straw","sweet","drink"]},"mate":{"a":"Mate","b":"1F9C9","j":["drink","tea","beverage"]},"ice":{"a":"Ice","b":"1F9CA","j":["cold","ice cube","iceberg","water"]},"chopsticks":{"a":"Chopsticks","b":"1F962","j":["hashi","jeotgarak","kuaizi","food"]},"fork-and-knife-with-plate":{"a":"Fork and Knife with Plate","b":"1F37D","j":["cooking","fork","knife","plate","food","eat","meal","lunch","dinner","restaurant"]},"fork-and-knife":{"a":"Fork and Knife","b":"1F374","j":["cooking","cutlery","fork","knife","kitchen"]},"spoon":{"a":"Spoon","b":"1F944","j":["tableware","cutlery","kitchen"]},"kitchen-knife":{"a":"Kitchen Knife","b":"1F52A","j":["cooking","hocho","knife","tool","weapon","blade","cutlery","kitchen"]},"jar":{"a":"⊛ Jar","b":"1FAD9","j":["condiment","container","empty","sauce","store"]},"amphora":{"a":"Amphora","b":"1F3FA","j":["Aquarius","cooking","drink","jug","zodiac","vase","jar"]},"globe-showing-europeafrica":{"a":"Globe Showing Europe-Africa","b":"1F30D","j":["Africa","earth","Europe","globe","globe showing Europe-Africa","world","globe_showing_europe_africa","international"]},"globe-showing-americas":{"a":"Globe Showing Americas","b":"1F30E","j":["Americas","earth","globe","globe showing Americas","world","USA","international"]},"globe-showing-asiaaustralia":{"a":"Globe Showing Asia-Australia","b":"1F30F","j":["Asia","Australia","earth","globe","globe showing Asia-Australia","world","globe_showing_asia_australia","east","international"]},"globe-with-meridians":{"a":"Globe with Meridians","b":"1F310","j":["earth","globe","meridians","world","international","internet","interweb","i18n"]},"world-map":{"a":"World Map","b":"1F5FA","j":["map","world","location","direction"]},"map-of-japan":{"a":"Map of Japan","b":"1F5FE","j":["Japan","map","map of Japan","nation","country","japanese","asia"]},"compass":{"a":"Compass","b":"1F9ED","j":["magnetic","navigation","orienteering"]},"snowcapped-mountain":{"a":"Snow-Capped Mountain","b":"1F3D4","j":["cold","mountain","snow","snow-capped mountain","snow_capped_mountain","photo","nature","environment","winter"]},"mountain":{"a":"Mountain","b":"26F0","j":["photo","nature","environment"]},"volcano":{"a":"Volcano","b":"1F30B","j":["eruption","mountain","photo","nature","disaster"]},"mount-fuji":{"a":"Mount Fuji","b":"1F5FB","j":["fuji","mountain","photo","nature","japanese"]},"camping":{"a":"Camping","b":"1F3D5","j":["photo","outdoors","tent"]},"beach-with-umbrella":{"a":"Beach with Umbrella","b":"1F3D6","j":["beach","umbrella","weather","summer","sunny","sand","mojito"]},"desert":{"a":"Desert","b":"1F3DC","j":["photo","warm","saharah"]},"desert-island":{"a":"Desert Island","b":"1F3DD","j":["desert","island","photo","tropical","mojito"]},"national-park":{"a":"National Park","b":"1F3DE","j":["park","photo","environment","nature"]},"stadium":{"a":"Stadium","b":"1F3DF","j":["photo","place","sports","concert","venue"]},"classical-building":{"a":"Classical Building","b":"1F3DB","j":["classical","art","culture","history"]},"building-construction":{"a":"Building Construction","b":"1F3D7","j":["construction","wip","working","progress"]},"brick":{"a":"Brick","b":"1F9F1","j":["bricks","clay","mortar","wall"]},"rock":{"a":"Rock","b":"1FAA8","j":["boulder","heavy","solid","stone"]},"wood":{"a":"Wood","b":"1FAB5","j":["log","lumber","timber","nature","trunk"]},"hut":{"a":"Hut","b":"1F6D6","j":["house","roundhouse","yurt","structure"]},"houses":{"a":"Houses","b":"1F3D8","j":["buildings","photo"]},"derelict-house":{"a":"Derelict House","b":"1F3DA","j":["derelict","house","abandon","evict","broken","building"]},"house":{"a":"House","b":"1F3E0","j":["home","building"]},"house-with-garden":{"a":"House with Garden","b":"1F3E1","j":["garden","home","house","plant","nature"]},"office-building":{"a":"Office Building","b":"1F3E2","j":["building","bureau","work"]},"japanese-post-office":{"a":"Japanese Post Office","b":"1F3E3","j":["Japanese","Japanese post office","post","building","envelope","communication"]},"post-office":{"a":"Post Office","b":"1F3E4","j":["European","post","building","email"]},"hospital":{"a":"Hospital","b":"1F3E5","j":["doctor","medicine","building","health","surgery"]},"bank":{"a":"Bank","b":"1F3E6","j":["building","money","sales","cash","business","enterprise"]},"hotel":{"a":"Hotel","b":"1F3E8","j":["building","accomodation","checkin"]},"love-hotel":{"a":"Love Hotel","b":"1F3E9","j":["hotel","love","like","affection","dating"]},"convenience-store":{"a":"Convenience Store","b":"1F3EA","j":["convenience","store","building","shopping","groceries"]},"school":{"a":"School","b":"1F3EB","j":["building","student","education","learn","teach"]},"department-store":{"a":"Department Store","b":"1F3EC","j":["department","store","building","shopping","mall"]},"factory":{"a":"Factory","b":"1F3ED","j":["building","industry","pollution","smoke"]},"japanese-castle":{"a":"Japanese Castle","b":"1F3EF","j":["castle","Japanese","photo","building"]},"castle":{"a":"Castle","b":"1F3F0","j":["European","building","royalty","history"]},"wedding":{"a":"Wedding","b":"1F492","j":["chapel","romance","love","like","affection","couple","marriage","bride","groom"]},"tokyo-tower":{"a":"Tokyo Tower","b":"1F5FC","j":["Tokyo","tower","photo","japanese"]},"statue-of-liberty":{"a":"Statue of Liberty","b":"1F5FD","j":["liberty","statue","american","newyork"]},"church":{"a":"Church","b":"26EA","j":["Christian","cross","religion","building","christ"]},"mosque":{"a":"Mosque","b":"1F54C","j":["islam","Muslim","religion","worship","minaret"]},"hindu-temple":{"a":"Hindu Temple","b":"1F6D5","j":["hindu","temple","religion"]},"synagogue":{"a":"Synagogue","b":"1F54D","j":["Jew","Jewish","religion","temple","judaism","worship","jewish"]},"shinto-shrine":{"a":"Shinto Shrine","b":"26E9","j":["religion","shinto","shrine","temple","japan","kyoto"]},"kaaba":{"a":"Kaaba","b":"1F54B","j":["islam","Muslim","religion","mecca","mosque"]},"fountain":{"a":"Fountain","b":"26F2","j":["photo","summer","water","fresh"]},"tent":{"a":"Tent","b":"26FA","j":["camping","photo","outdoors"]},"foggy":{"a":"Foggy","b":"1F301","j":["fog","photo","mountain"]},"night-with-stars":{"a":"Night with Stars","b":"1F303","j":["night","star","evening","city","downtown"]},"cityscape":{"a":"Cityscape","b":"1F3D9","j":["city","photo","night life","urban"]},"sunrise-over-mountains":{"a":"Sunrise over Mountains","b":"1F304","j":["morning","mountain","sun","sunrise","view","vacation","photo"]},"sunrise":{"a":"Sunrise","b":"1F305","j":["morning","sun","view","vacation","photo"]},"cityscape-at-dusk":{"a":"Cityscape at Dusk","b":"1F306","j":["city","dusk","evening","landscape","sunset","photo","sky","buildings"]},"sunset":{"a":"Sunset","b":"1F307","j":["dusk","sun","photo","good morning","dawn"]},"bridge-at-night":{"a":"Bridge at Night","b":"1F309","j":["bridge","night","photo","sanfrancisco"]},"hot-springs":{"a":"Hot Springs","b":"2668","j":["hot","hotsprings","springs","steaming","bath","warm","relax"]},"carousel-horse":{"a":"Carousel Horse","b":"1F3A0","j":["carousel","horse","photo","carnival"]},"playground-slide":{"a":"⊛ Playground Slide","b":"1F6DD","j":["amusement park","play"]},"ferris-wheel":{"a":"Ferris Wheel","b":"1F3A1","j":["amusement park","ferris","wheel","photo","carnival","londoneye"]},"roller-coaster":{"a":"Roller Coaster","b":"1F3A2","j":["amusement park","coaster","roller","carnival","playground","photo","fun"]},"barber-pole":{"a":"Barber Pole","b":"1F488","j":["barber","haircut","pole","hair","salon","style"]},"circus-tent":{"a":"Circus Tent","b":"1F3AA","j":["circus","tent","festival","carnival","party"]},"locomotive":{"a":"Locomotive","b":"1F682","j":["engine","railway","steam","train","transportation","vehicle"]},"railway-car":{"a":"Railway Car","b":"1F683","j":["car","electric","railway","train","tram","trolleybus","transportation","vehicle"]},"highspeed-train":{"a":"High-Speed Train","b":"1F684","j":["high-speed train","railway","shinkansen","speed","train","high_speed_train","transportation","vehicle"]},"bullet-train":{"a":"Bullet Train","b":"1F685","j":["bullet","railway","shinkansen","speed","train","transportation","vehicle","fast","public","travel"]},"train":{"a":"Train","b":"1F686","j":["railway","transportation","vehicle"]},"metro":{"a":"Metro","b":"1F687","j":["subway","transportation","blue-square","mrt","underground","tube"]},"light-rail":{"a":"Light Rail","b":"1F688","j":["railway","transportation","vehicle"]},"station":{"a":"Station","b":"1F689","j":["railway","train","transportation","vehicle","public"]},"tram":{"a":"Tram","b":"1F68A","j":["trolleybus","transportation","vehicle"]},"monorail":{"a":"Monorail","b":"1F69D","j":["vehicle","transportation"]},"mountain-railway":{"a":"Mountain Railway","b":"1F69E","j":["car","mountain","railway","transportation","vehicle"]},"tram-car":{"a":"Tram Car","b":"1F68B","j":["car","tram","trolleybus","transportation","vehicle","carriage","public","travel"]},"bus":{"a":"Bus","b":"1F68C","j":["vehicle","car","transportation"]},"oncoming-bus":{"a":"Oncoming Bus","b":"1F68D","j":["bus","oncoming","vehicle","transportation"]},"trolleybus":{"a":"Trolleybus","b":"1F68E","j":["bus","tram","trolley","bart","transportation","vehicle"]},"minibus":{"a":"Minibus","b":"1F690","j":["bus","vehicle","car","transportation"]},"ambulance":{"a":"Ambulance","b":"1F691","j":["vehicle","health","911","hospital"]},"fire-engine":{"a":"Fire Engine","b":"1F692","j":["engine","fire","truck","transportation","cars","vehicle"]},"police-car":{"a":"Police Car","b":"1F693","j":["car","patrol","police","vehicle","cars","transportation","law","legal","enforcement"]},"oncoming-police-car":{"a":"Oncoming Police Car","b":"1F694","j":["car","oncoming","police","vehicle","law","legal","enforcement","911"]},"taxi":{"a":"Taxi","b":"1F695","j":["vehicle","uber","cars","transportation"]},"oncoming-taxi":{"a":"Oncoming Taxi","b":"1F696","j":["oncoming","taxi","vehicle","cars","uber"]},"automobile":{"a":"Automobile","b":"1F697","j":["car","red","transportation","vehicle"]},"oncoming-automobile":{"a":"Oncoming Automobile","b":"1F698","j":["automobile","car","oncoming","vehicle","transportation"]},"sport-utility-vehicle":{"a":"Sport Utility Vehicle","b":"1F699","j":["recreational","sport utility","transportation","vehicle"]},"pickup-truck":{"a":"Pickup Truck","b":"1F6FB","j":["pick-up","pickup","truck","car","transportation"]},"delivery-truck":{"a":"Delivery Truck","b":"1F69A","j":["delivery","truck","cars","transportation"]},"articulated-lorry":{"a":"Articulated Lorry","b":"1F69B","j":["lorry","semi","truck","vehicle","cars","transportation","express"]},"tractor":{"a":"Tractor","b":"1F69C","j":["vehicle","car","farming","agriculture"]},"racing-car":{"a":"Racing Car","b":"1F3CE","j":["car","racing","sports","race","fast","formula","f1"]},"motorcycle":{"a":"Motorcycle","b":"1F3CD","j":["racing","race","sports","fast"]},"motor-scooter":{"a":"Motor Scooter","b":"1F6F5","j":["motor","scooter","vehicle","vespa","sasha"]},"manual-wheelchair":{"a":"Manual Wheelchair","b":"1F9BD","j":["accessibility"]},"motorized-wheelchair":{"a":"Motorized Wheelchair","b":"1F9BC","j":["accessibility"]},"auto-rickshaw":{"a":"Auto Rickshaw","b":"1F6FA","j":["tuk tuk","move","transportation"]},"bicycle":{"a":"Bicycle","b":"1F6B2","j":["bike","sports","exercise","hipster"]},"kick-scooter":{"a":"Kick Scooter","b":"1F6F4","j":["kick","scooter","vehicle","razor"]},"skateboard":{"a":"Skateboard","b":"1F6F9","j":["board"]},"roller-skate":{"a":"Roller Skate","b":"1F6FC","j":["roller","skate","footwear","sports"]},"bus-stop":{"a":"Bus Stop","b":"1F68F","j":["bus","stop","transportation","wait"]},"motorway":{"a":"Motorway","b":"1F6E3","j":["highway","road","cupertino","interstate"]},"railway-track":{"a":"Railway Track","b":"1F6E4","j":["railway","train","transportation"]},"oil-drum":{"a":"Oil Drum","b":"1F6E2","j":["drum","oil","barrell"]},"fuel-pump":{"a":"Fuel Pump","b":"26FD","j":["diesel","fuel","fuelpump","gas","pump","station","gas station","petroleum"]},"wheel":{"a":"⊛ Wheel","b":"1F6DE","j":["circle","tire","turn"]},"police-car-light":{"a":"Police Car Light","b":"1F6A8","j":["beacon","car","light","police","revolving","ambulance","911","emergency","alert","error","pinged","law","legal"]},"horizontal-traffic-light":{"a":"Horizontal Traffic Light","b":"1F6A5","j":["light","signal","traffic","transportation"]},"vertical-traffic-light":{"a":"Vertical Traffic Light","b":"1F6A6","j":["light","signal","traffic","transportation","driving"]},"stop-sign":{"a":"Stop Sign","b":"1F6D1","j":["octagonal","sign","stop"]},"construction":{"a":"Construction","b":"1F6A7","j":["barrier","wip","progress","caution","warning"]},"anchor":{"a":"Anchor","b":"2693","j":["ship","tool","ferry","sea","boat"]},"ring-buoy":{"a":"⊛ Ring Buoy","b":"1F6DF","j":["float","life preserver","life saver","rescue","safety"]},"sailboat":{"a":"Sailboat","b":"26F5","j":["boat","resort","sea","yacht","ship","summer","transportation","water","sailing"]},"canoe":{"a":"Canoe","b":"1F6F6","j":["boat","paddle","water","ship"]},"speedboat":{"a":"Speedboat","b":"1F6A4","j":["boat","ship","transportation","vehicle","summer"]},"passenger-ship":{"a":"Passenger Ship","b":"1F6F3","j":["passenger","ship","yacht","cruise","ferry"]},"ferry":{"a":"Ferry","b":"26F4","j":["boat","passenger","ship","yacht"]},"motor-boat":{"a":"Motor Boat","b":"1F6E5","j":["boat","motorboat","ship"]},"ship":{"a":"Ship","b":"1F6A2","j":["boat","passenger","transportation","titanic","deploy"]},"airplane":{"a":"Airplane","b":"2708","j":["aeroplane","vehicle","transportation","flight","fly"]},"small-airplane":{"a":"Small Airplane","b":"1F6E9","j":["aeroplane","airplane","flight","transportation","fly","vehicle"]},"airplane-departure":{"a":"Airplane Departure","b":"1F6EB","j":["aeroplane","airplane","check-in","departure","departures","airport","flight","landing"]},"airplane-arrival":{"a":"Airplane Arrival","b":"1F6EC","j":["aeroplane","airplane","arrivals","arriving","landing","airport","flight","boarding"]},"parachute":{"a":"Parachute","b":"1FA82","j":["hang-glide","parasail","skydive","fly","glide"]},"seat":{"a":"Seat","b":"1F4BA","j":["chair","sit","airplane","transport","bus","flight","fly"]},"helicopter":{"a":"Helicopter","b":"1F681","j":["vehicle","transportation","fly"]},"suspension-railway":{"a":"Suspension Railway","b":"1F69F","j":["railway","suspension","vehicle","transportation"]},"mountain-cableway":{"a":"Mountain Cableway","b":"1F6A0","j":["cable","gondola","mountain","transportation","vehicle","ski"]},"aerial-tramway":{"a":"Aerial Tramway","b":"1F6A1","j":["aerial","cable","car","gondola","tramway","transportation","vehicle","ski"]},"satellite":{"a":"Satellite","b":"1F6F0","j":["space","communication","gps","orbit","spaceflight","NASA","ISS"]},"rocket":{"a":"Rocket","b":"1F680","j":["space","launch","ship","staffmode","NASA","outer space","outer_space","fly"]},"flying-saucer":{"a":"Flying Saucer","b":"1F6F8","j":["UFO","transportation","vehicle","ufo"]},"bellhop-bell":{"a":"Bellhop Bell","b":"1F6CE","j":["bell","bellhop","hotel","service"]},"luggage":{"a":"Luggage","b":"1F9F3","j":["packing","travel"]},"hourglass-done":{"a":"Hourglass Done","b":"231B","j":["sand","timer","time","clock","oldschool","limit","exam","quiz","test"]},"hourglass-not-done":{"a":"Hourglass Not Done","b":"23F3","j":["hourglass","sand","timer","oldschool","time","countdown"]},"watch":{"a":"Watch","b":"231A","j":["clock","time","accessories"]},"alarm-clock":{"a":"Alarm Clock","b":"23F0","j":["alarm","clock","time","wake"]},"stopwatch":{"a":"Stopwatch","b":"23F1","j":["clock","time","deadline"]},"timer-clock":{"a":"Timer Clock","b":"23F2","j":["clock","timer","alarm"]},"mantelpiece-clock":{"a":"Mantelpiece Clock","b":"1F570","j":["clock","time"]},"twelve-oclock":{"a":"Twelve O’Clock","b":"1F55B","j":["00","12","12:00","clock","o’clock","twelve","twelve_o_clock","time","noon","midnight","midday","late","early","schedule"]},"twelvethirty":{"a":"Twelve-Thirty","b":"1F567","j":["12","12:30","clock","thirty","twelve","twelve-thirty","twelve_thirty","time","late","early","schedule"]},"one-oclock":{"a":"One O’Clock","b":"1F550","j":["00","1","1:00","clock","o’clock","one","one_o_clock","time","late","early","schedule"]},"onethirty":{"a":"One-Thirty","b":"1F55C","j":["1","1:30","clock","one","one-thirty","thirty","one_thirty","time","late","early","schedule"]},"two-oclock":{"a":"Two O’Clock","b":"1F551","j":["00","2","2:00","clock","o’clock","two","two_o_clock","time","late","early","schedule"]},"twothirty":{"a":"Two-Thirty","b":"1F55D","j":["2","2:30","clock","thirty","two","two-thirty","two_thirty","time","late","early","schedule"]},"three-oclock":{"a":"Three O’Clock","b":"1F552","j":["00","3","3:00","clock","o’clock","three","three_o_clock","time","late","early","schedule"]},"threethirty":{"a":"Three-Thirty","b":"1F55E","j":["3","3:30","clock","thirty","three","three-thirty","three_thirty","time","late","early","schedule"]},"four-oclock":{"a":"Four O’Clock","b":"1F553","j":["00","4","4:00","clock","four","o’clock","four_o_clock","time","late","early","schedule"]},"fourthirty":{"a":"Four-Thirty","b":"1F55F","j":["4","4:30","clock","four","four-thirty","thirty","four_thirty","time","late","early","schedule"]},"five-oclock":{"a":"Five O’Clock","b":"1F554","j":["00","5","5:00","clock","five","o’clock","five_o_clock","time","late","early","schedule"]},"fivethirty":{"a":"Five-Thirty","b":"1F560","j":["5","5:30","clock","five","five-thirty","thirty","five_thirty","time","late","early","schedule"]},"six-oclock":{"a":"Six O’Clock","b":"1F555","j":["00","6","6:00","clock","o’clock","six","six_o_clock","time","late","early","schedule","dawn","dusk"]},"sixthirty":{"a":"Six-Thirty","b":"1F561","j":["6","6:30","clock","six","six-thirty","thirty","six_thirty","time","late","early","schedule"]},"seven-oclock":{"a":"Seven O’Clock","b":"1F556","j":["00","7","7:00","clock","o’clock","seven","seven_o_clock","time","late","early","schedule"]},"seventhirty":{"a":"Seven-Thirty","b":"1F562","j":["7","7:30","clock","seven","seven-thirty","thirty","seven_thirty","time","late","early","schedule"]},"eight-oclock":{"a":"Eight O’Clock","b":"1F557","j":["00","8","8:00","clock","eight","o’clock","eight_o_clock","time","late","early","schedule"]},"eightthirty":{"a":"Eight-Thirty","b":"1F563","j":["8","8:30","clock","eight","eight-thirty","thirty","eight_thirty","time","late","early","schedule"]},"nine-oclock":{"a":"Nine O’Clock","b":"1F558","j":["00","9","9:00","clock","nine","o’clock","nine_o_clock","time","late","early","schedule"]},"ninethirty":{"a":"Nine-Thirty","b":"1F564","j":["9","9:30","clock","nine","nine-thirty","thirty","nine_thirty","time","late","early","schedule"]},"ten-oclock":{"a":"Ten O’Clock","b":"1F559","j":["00","10","10:00","clock","o’clock","ten","ten_o_clock","time","late","early","schedule"]},"tenthirty":{"a":"Ten-Thirty","b":"1F565","j":["10","10:30","clock","ten","ten-thirty","thirty","ten_thirty","time","late","early","schedule"]},"eleven-oclock":{"a":"Eleven O’Clock","b":"1F55A","j":["00","11","11:00","clock","eleven","o’clock","eleven_o_clock","time","late","early","schedule"]},"eleventhirty":{"a":"Eleven-Thirty","b":"1F566","j":["11","11:30","clock","eleven","eleven-thirty","thirty","eleven_thirty","time","late","early","schedule"]},"new-moon":{"a":"New Moon","b":"1F311","j":["dark","moon","nature","twilight","planet","space","night","evening","sleep"]},"waxing-crescent-moon":{"a":"Waxing Crescent Moon","b":"1F312","j":["crescent","moon","waxing","nature","twilight","planet","space","night","evening","sleep"]},"first-quarter-moon":{"a":"First Quarter Moon","b":"1F313","j":["moon","quarter","nature","twilight","planet","space","night","evening","sleep"]},"waxing-gibbous-moon":{"a":"Waxing Gibbous Moon","b":"1F314","j":["gibbous","moon","waxing","nature","night","sky","gray","twilight","planet","space","evening","sleep"]},"full-moon":{"a":"Full Moon","b":"1F315","j":["full","moon","nature","yellow","twilight","planet","space","night","evening","sleep"]},"waning-gibbous-moon":{"a":"Waning Gibbous Moon","b":"1F316","j":["gibbous","moon","waning","nature","twilight","planet","space","night","evening","sleep","waxing_gibbous_moon"]},"last-quarter-moon":{"a":"Last Quarter Moon","b":"1F317","j":["moon","quarter","nature","twilight","planet","space","night","evening","sleep"]},"waning-crescent-moon":{"a":"Waning Crescent Moon","b":"1F318","j":["crescent","moon","waning","nature","twilight","planet","space","night","evening","sleep"]},"crescent-moon":{"a":"Crescent Moon","b":"1F319","j":["crescent","moon","night","sleep","sky","evening","magic"]},"new-moon-face":{"a":"New Moon Face","b":"1F31A","j":["face","moon","nature","twilight","planet","space","night","evening","sleep"]},"first-quarter-moon-face":{"a":"First Quarter Moon Face","b":"1F31B","j":["face","moon","quarter","nature","twilight","planet","space","night","evening","sleep"]},"last-quarter-moon-face":{"a":"Last Quarter Moon Face","b":"1F31C","j":["face","moon","quarter","nature","twilight","planet","space","night","evening","sleep"]},"thermometer":{"a":"Thermometer","b":"1F321","j":["weather","temperature","hot","cold"]},"sun":{"a":"Sun","b":"2600","j":["bright","rays","sunny","weather","nature","brightness","summer","beach","spring"]},"full-moon-face":{"a":"Full Moon Face","b":"1F31D","j":["bright","face","full","moon","nature","twilight","planet","space","night","evening","sleep"]},"sun-with-face":{"a":"Sun with Face","b":"1F31E","j":["bright","face","sun","nature","morning","sky"]},"ringed-planet":{"a":"Ringed Planet","b":"1FA90","j":["saturn","saturnine","outerspace"]},"star":{"a":"Star","b":"2B50","j":["night","yellow"]},"glowing-star":{"a":"Glowing Star","b":"1F31F","j":["glittery","glow","shining","sparkle","star","night","awesome","good","magic"]},"shooting-star":{"a":"Shooting Star","b":"1F320","j":["falling","shooting","star","night","photo"]},"milky-way":{"a":"Milky Way","b":"1F30C","j":["space","photo","stars"]},"cloud":{"a":"Cloud","b":"2601","j":["weather","sky"]},"sun-behind-cloud":{"a":"Sun Behind Cloud","b":"26C5","j":["cloud","sun","weather","nature","cloudy","morning","fall","spring"]},"cloud-with-lightning-and-rain":{"a":"Cloud with Lightning and Rain","b":"26C8","j":["cloud","rain","thunder","weather","lightning"]},"sun-behind-small-cloud":{"a":"Sun Behind Small Cloud","b":"1F324","j":["cloud","sun","weather"]},"sun-behind-large-cloud":{"a":"Sun Behind Large Cloud","b":"1F325","j":["cloud","sun","weather"]},"sun-behind-rain-cloud":{"a":"Sun Behind Rain Cloud","b":"1F326","j":["cloud","rain","sun","weather"]},"cloud-with-rain":{"a":"Cloud with Rain","b":"1F327","j":["cloud","rain","weather"]},"cloud-with-snow":{"a":"Cloud with Snow","b":"1F328","j":["cloud","cold","snow","weather"]},"cloud-with-lightning":{"a":"Cloud with Lightning","b":"1F329","j":["cloud","lightning","weather","thunder"]},"tornado":{"a":"Tornado","b":"1F32A","j":["cloud","whirlwind","weather","cyclone","twister"]},"fog":{"a":"Fog","b":"1F32B","j":["cloud","weather"]},"wind-face":{"a":"Wind Face","b":"1F32C","j":["blow","cloud","face","wind","gust","air"]},"cyclone":{"a":"Cyclone","b":"1F300","j":["dizzy","hurricane","twister","typhoon","weather","swirl","blue","cloud","vortex","spiral","whirlpool","spin","tornado"]},"rainbow":{"a":"Rainbow","b":"1F308","j":["rain","nature","happy","unicorn_face","photo","sky","spring"]},"closed-umbrella":{"a":"Closed Umbrella","b":"1F302","j":["clothing","rain","umbrella","weather","drizzle"]},"umbrella":{"a":"Umbrella","b":"2602","j":["clothing","rain","weather","spring"]},"umbrella-with-rain-drops":{"a":"Umbrella with Rain Drops","b":"2614","j":["clothing","drop","rain","umbrella","rainy","weather","spring"]},"umbrella-on-ground":{"a":"Umbrella on Ground","b":"26F1","j":["rain","sun","umbrella","weather","summer"]},"high-voltage":{"a":"High Voltage","b":"26A1","j":["danger","electric","lightning","voltage","zap","thunder","weather","lightning bolt","fast"]},"snowflake":{"a":"Snowflake","b":"2744","j":["cold","snow","winter","season","weather","christmas","xmas"]},"snowman":{"a":"Snowman","b":"2603","j":["cold","snow","winter","season","weather","christmas","xmas","frozen"]},"snowman-without-snow":{"a":"Snowman Without Snow","b":"26C4","j":["cold","snow","snowman","winter","season","weather","christmas","xmas","frozen","without_snow"]},"comet":{"a":"Comet","b":"2604","j":["space"]},"fire":{"a":"Fire","b":"1F525","j":["flame","tool","hot","cook"]},"droplet":{"a":"Droplet","b":"1F4A7","j":["cold","comic","drop","sweat","water","drip","faucet","spring"]},"water-wave":{"a":"Water Wave","b":"1F30A","j":["ocean","water","wave","sea","nature","tsunami","disaster"]},"jackolantern":{"a":"Jack-O-Lantern","b":"1F383","j":["celebration","halloween","jack","jack-o-lantern","lantern","jack_o_lantern","light","pumpkin","creepy","fall"]},"christmas-tree":{"a":"Christmas Tree","b":"1F384","j":["celebration","Christmas","tree","festival","vacation","december","xmas"]},"fireworks":{"a":"Fireworks","b":"1F386","j":["celebration","photo","festival","carnival","congratulations"]},"sparkler":{"a":"Sparkler","b":"1F387","j":["celebration","fireworks","sparkle","stars","night","shine"]},"firecracker":{"a":"Firecracker","b":"1F9E8","j":["dynamite","explosive","fireworks","boom","explode","explosion"]},"sparkles":{"a":"Sparkles","b":"2728","j":["*","sparkle","star","stars","shine","shiny","cool","awesome","good","magic"]},"balloon":{"a":"Balloon","b":"1F388","j":["celebration","party","birthday","circus"]},"party-popper":{"a":"Party Popper","b":"1F389","j":["celebration","party","popper","tada","congratulations","birthday","magic","circus"]},"confetti-ball":{"a":"Confetti Ball","b":"1F38A","j":["ball","celebration","confetti","festival","party","birthday","circus"]},"tanabata-tree":{"a":"Tanabata Tree","b":"1F38B","j":["banner","celebration","Japanese","tree","plant","nature","branch","summer"]},"pine-decoration":{"a":"Pine Decoration","b":"1F38D","j":["bamboo","celebration","Japanese","pine","plant","nature","vegetable","panda"]},"japanese-dolls":{"a":"Japanese Dolls","b":"1F38E","j":["celebration","doll","festival","Japanese","Japanese dolls","japanese","toy","kimono"]},"carp-streamer":{"a":"Carp Streamer","b":"1F38F","j":["carp","celebration","streamer","fish","japanese","koinobori","banner"]},"wind-chime":{"a":"Wind Chime","b":"1F390","j":["bell","celebration","chime","wind","nature","ding","spring"]},"moon-viewing-ceremony":{"a":"Moon Viewing Ceremony","b":"1F391","j":["celebration","ceremony","moon","photo","japan","asia","tsukimi"]},"red-envelope":{"a":"Red Envelope","b":"1F9E7","j":["gift","good luck","hóngbāo","lai see","money"]},"ribbon":{"a":"Ribbon","b":"1F380","j":["celebration","decoration","pink","girl","bowtie"]},"wrapped-gift":{"a":"Wrapped Gift","b":"1F381","j":["box","celebration","gift","present","wrapped","birthday","christmas","xmas"]},"reminder-ribbon":{"a":"Reminder Ribbon","b":"1F397","j":["celebration","reminder","ribbon","sports","cause","support","awareness"]},"admission-tickets":{"a":"Admission Tickets","b":"1F39F","j":["admission","ticket","sports","concert","entrance"]},"ticket":{"a":"Ticket","b":"1F3AB","j":["admission","event","concert","pass"]},"military-medal":{"a":"Military Medal","b":"1F396","j":["celebration","medal","military","award","winning","army"]},"trophy":{"a":"Trophy","b":"1F3C6","j":["prize","win","award","contest","place","ftw","ceremony"]},"sports-medal":{"a":"Sports Medal","b":"1F3C5","j":["medal","award","winning"]},"1st-place-medal":{"a":"1st Place Medal","b":"1F947","j":["first","gold","medal","award","winning"]},"2nd-place-medal":{"a":"2nd Place Medal","b":"1F948","j":["medal","second","silver","award"]},"3rd-place-medal":{"a":"3rd Place Medal","b":"1F949","j":["bronze","medal","third","award"]},"soccer-ball":{"a":"Soccer Ball","b":"26BD","j":["ball","football","soccer","sports"]},"baseball":{"a":"Baseball","b":"26BE","j":["ball","sports","balls"]},"softball":{"a":"Softball","b":"1F94E","j":["ball","glove","underarm","sports","balls"]},"basketball":{"a":"Basketball","b":"1F3C0","j":["ball","hoop","sports","balls","NBA"]},"volleyball":{"a":"Volleyball","b":"1F3D0","j":["ball","game","sports","balls"]},"american-football":{"a":"American Football","b":"1F3C8","j":["american","ball","football","sports","balls","NFL"]},"rugby-football":{"a":"Rugby Football","b":"1F3C9","j":["ball","football","rugby","sports","team"]},"tennis":{"a":"Tennis","b":"1F3BE","j":["ball","racquet","sports","balls","green"]},"flying-disc":{"a":"Flying Disc","b":"1F94F","j":["ultimate","sports","frisbee"]},"bowling":{"a":"Bowling","b":"1F3B3","j":["ball","game","sports","fun","play"]},"cricket-game":{"a":"Cricket Game","b":"1F3CF","j":["ball","bat","game","sports"]},"field-hockey":{"a":"Field Hockey","b":"1F3D1","j":["ball","field","game","hockey","stick","sports"]},"ice-hockey":{"a":"Ice Hockey","b":"1F3D2","j":["game","hockey","ice","puck","stick","sports"]},"lacrosse":{"a":"Lacrosse","b":"1F94D","j":["ball","goal","stick","sports"]},"ping-pong":{"a":"Ping Pong","b":"1F3D3","j":["ball","bat","game","paddle","table tennis","sports","pingpong"]},"badminton":{"a":"Badminton","b":"1F3F8","j":["birdie","game","racquet","shuttlecock","sports"]},"boxing-glove":{"a":"Boxing Glove","b":"1F94A","j":["boxing","glove","sports","fighting"]},"martial-arts-uniform":{"a":"Martial Arts Uniform","b":"1F94B","j":["judo","karate","martial arts","taekwondo","uniform"]},"goal-net":{"a":"Goal Net","b":"1F945","j":["goal","net","sports"]},"flag-in-hole":{"a":"Flag in Hole","b":"26F3","j":["golf","hole","sports","business","flag","summer"]},"ice-skate":{"a":"Ice Skate","b":"26F8","j":["ice","skate","sports"]},"fishing-pole":{"a":"Fishing Pole","b":"1F3A3","j":["fish","pole","food","hobby","summer"]},"diving-mask":{"a":"Diving Mask","b":"1F93F","j":["diving","scuba","snorkeling","sport","ocean"]},"running-shirt":{"a":"Running Shirt","b":"1F3BD","j":["athletics","running","sash","shirt","play","pageant"]},"skis":{"a":"Skis","b":"1F3BF","j":["ski","snow","sports","winter","cold"]},"sled":{"a":"Sled","b":"1F6F7","j":["sledge","sleigh","luge","toboggan"]},"curling-stone":{"a":"Curling Stone","b":"1F94C","j":["game","rock","sports"]},"bullseye":{"a":"Bullseye","b":"1F3AF","j":["dart","direct hit","game","hit","target","direct_hit","play","bar"]},"yoyo":{"a":"Yo-Yo","b":"1FA80","j":["fluctuate","toy","yo-yo","yo_yo"]},"kite":{"a":"Kite","b":"1FA81","j":["fly","soar","wind"]},"pool-8-ball":{"a":"Pool 8 Ball","b":"1F3B1","j":["8","ball","billiard","eight","game","pool","hobby","luck","magic"]},"crystal-ball":{"a":"Crystal Ball","b":"1F52E","j":["ball","crystal","fairy tale","fantasy","fortune","tool","disco","party","magic","circus","fortune_teller"]},"magic-wand":{"a":"Magic Wand","b":"1FA84","j":["magic","witch","wizard","supernature","power"]},"nazar-amulet":{"a":"Nazar Amulet","b":"1F9FF","j":["bead","charm","evil-eye","nazar","talisman"]},"hamsa":{"a":"⊛ Hamsa","b":"1FAAC","j":["amulet","Fatima","hand","Mary","Miriam","protection"]},"video-game":{"a":"Video Game","b":"1F3AE","j":["controller","game","play","console","PS4"]},"joystick":{"a":"Joystick","b":"1F579","j":["game","video game","play"]},"slot-machine":{"a":"Slot Machine","b":"1F3B0","j":["game","slot","bet","gamble","vegas","fruit machine","luck","casino"]},"game-die":{"a":"Game Die","b":"1F3B2","j":["dice","die","game","random","tabletop","play","luck"]},"puzzle-piece":{"a":"Puzzle Piece","b":"1F9E9","j":["clue","interlocking","jigsaw","piece","puzzle"]},"teddy-bear":{"a":"Teddy Bear","b":"1F9F8","j":["plaything","plush","stuffed","toy"]},"piata":{"a":"Piñata","b":"1FA85","j":["celebration","party","piñata","pinata","mexico","candy"]},"mirror-ball":{"a":"⊛ Mirror Ball","b":"1FAA9","j":["dance","disco","glitter","party"]},"nesting-dolls":{"a":"Nesting Dolls","b":"1FA86","j":["doll","nesting","russia","matryoshka","toy"]},"spade-suit":{"a":"Spade Suit","b":"2660","j":["card","game","poker","cards","suits","magic"]},"heart-suit":{"a":"Heart Suit","b":"2665","j":["card","game","poker","cards","magic","suits"]},"diamond-suit":{"a":"Diamond Suit","b":"2666","j":["card","game","poker","cards","magic","suits"]},"club-suit":{"a":"Club Suit","b":"2663","j":["card","game","poker","cards","magic","suits"]},"chess-pawn":{"a":"Chess Pawn","b":"265F","j":["chess","dupe","expendable"]},"joker":{"a":"Joker","b":"1F0CF","j":["card","game","wildcard","poker","cards","play","magic"]},"mahjong-red-dragon":{"a":"Mahjong Red Dragon","b":"1F004","j":["game","mahjong","red","play","chinese","kanji"]},"flower-playing-cards":{"a":"Flower Playing Cards","b":"1F3B4","j":["card","flower","game","Japanese","playing","sunset","red"]},"performing-arts":{"a":"Performing Arts","b":"1F3AD","j":["art","mask","performing","theater","theatre","acting","drama"]},"framed-picture":{"a":"Framed Picture","b":"1F5BC","j":["art","frame","museum","painting","picture","photography"]},"artist-palette":{"a":"Artist Palette","b":"1F3A8","j":["art","museum","painting","palette","design","paint","draw","colors"]},"thread":{"a":"Thread","b":"1F9F5","j":["needle","sewing","spool","string"]},"sewing-needle":{"a":"Sewing Needle","b":"1FAA1","j":["embroidery","needle","sewing","stitches","sutures","tailoring"]},"yarn":{"a":"Yarn","b":"1F9F6","j":["ball","crochet","knit"]},"knot":{"a":"Knot","b":"1FAA2","j":["rope","tangled","tie","twine","twist","scout"]},"glasses":{"a":"Glasses","b":"1F453","j":["clothing","eye","eyeglasses","eyewear","fashion","accessories","eyesight","nerdy","dork","geek"]},"sunglasses":{"a":"Sunglasses","b":"1F576","j":["dark","eye","eyewear","glasses","face","cool","accessories"]},"goggles":{"a":"Goggles","b":"1F97D","j":["eye protection","swimming","welding","eyes","protection","safety"]},"lab-coat":{"a":"Lab Coat","b":"1F97C","j":["doctor","experiment","scientist","chemist"]},"safety-vest":{"a":"Safety Vest","b":"1F9BA","j":["emergency","safety","vest","protection"]},"necktie":{"a":"Necktie","b":"1F454","j":["clothing","tie","shirt","suitup","formal","fashion","cloth","business"]},"tshirt":{"a":"T-Shirt","b":"1F455","j":["clothing","shirt","t-shirt","t_shirt","fashion","cloth","casual","tee"]},"jeans":{"a":"Jeans","b":"1F456","j":["clothing","pants","trousers","fashion","shopping"]},"scarf":{"a":"Scarf","b":"1F9E3","j":["neck","winter","clothes"]},"gloves":{"a":"Gloves","b":"1F9E4","j":["hand","hands","winter","clothes"]},"coat":{"a":"Coat","b":"1F9E5","j":["jacket"]},"socks":{"a":"Socks","b":"1F9E6","j":["stocking","stockings","clothes"]},"dress":{"a":"Dress","b":"1F457","j":["clothing","clothes","fashion","shopping"]},"kimono":{"a":"Kimono","b":"1F458","j":["clothing","dress","fashion","women","female","japanese"]},"sari":{"a":"Sari","b":"1F97B","j":["clothing","dress"]},"onepiece-swimsuit":{"a":"One-Piece Swimsuit","b":"1FA71","j":["bathing suit","one-piece swimsuit","one_piece_swimsuit","fashion"]},"briefs":{"a":"Briefs","b":"1FA72","j":["bathing suit","one-piece","swimsuit","underwear","clothing"]},"shorts":{"a":"Shorts","b":"1FA73","j":["bathing suit","pants","underwear","clothing"]},"bikini":{"a":"Bikini","b":"1F459","j":["clothing","swim","swimming","female","woman","girl","fashion","beach","summer"]},"womans-clothes":{"a":"Woman’S Clothes","b":"1F45A","j":["clothing","woman","woman’s clothes","woman_s_clothes","fashion","shopping_bags","female"]},"purse":{"a":"Purse","b":"1F45B","j":["clothing","coin","fashion","accessories","money","sales","shopping"]},"handbag":{"a":"Handbag","b":"1F45C","j":["bag","clothing","purse","fashion","accessory","accessories","shopping"]},"clutch-bag":{"a":"Clutch Bag","b":"1F45D","j":["bag","clothing","pouch","accessories","shopping"]},"shopping-bags":{"a":"Shopping Bags","b":"1F6CD","j":["bag","hotel","shopping","mall","buy","purchase"]},"backpack":{"a":"Backpack","b":"1F392","j":["bag","rucksack","satchel","school","student","education"]},"thong-sandal":{"a":"Thong Sandal","b":"1FA74","j":["beach sandals","sandals","thong sandals","thongs","zōri","footwear","summer"]},"mans-shoe":{"a":"Man’S Shoe","b":"1F45E","j":["clothing","man","man’s shoe","shoe","man_s_shoe","fashion","male"]},"running-shoe":{"a":"Running Shoe","b":"1F45F","j":["athletic","clothing","shoe","sneaker","shoes","sports","sneakers"]},"hiking-boot":{"a":"Hiking Boot","b":"1F97E","j":["backpacking","boot","camping","hiking"]},"flat-shoe":{"a":"Flat Shoe","b":"1F97F","j":["ballet flat","slip-on","slipper","ballet"]},"highheeled-shoe":{"a":"High-Heeled Shoe","b":"1F460","j":["clothing","heel","high-heeled shoe","shoe","woman","high_heeled_shoe","fashion","shoes","female","pumps","stiletto"]},"womans-sandal":{"a":"Woman’S Sandal","b":"1F461","j":["clothing","sandal","shoe","woman","woman’s sandal","woman_s_sandal","shoes","fashion","flip flops"]},"ballet-shoes":{"a":"Ballet Shoes","b":"1FA70","j":["ballet","dance"]},"womans-boot":{"a":"Woman’S Boot","b":"1F462","j":["boot","clothing","shoe","woman","woman’s boot","woman_s_boot","shoes","fashion"]},"crown":{"a":"Crown","b":"1F451","j":["clothing","king","queen","kod","leader","royalty","lord"]},"womans-hat":{"a":"Woman’S Hat","b":"1F452","j":["clothing","hat","woman","woman’s hat","woman_s_hat","fashion","accessories","female","lady","spring"]},"top-hat":{"a":"Top Hat","b":"1F3A9","j":["clothing","hat","top","tophat","magic","gentleman","classy","circus"]},"graduation-cap":{"a":"Graduation Cap","b":"1F393","j":["cap","celebration","clothing","graduation","hat","school","college","degree","university","legal","learn","education"]},"billed-cap":{"a":"Billed Cap","b":"1F9E2","j":["baseball cap","cap","baseball"]},"military-helmet":{"a":"Military Helmet","b":"1FA96","j":["army","helmet","military","soldier","warrior","protection"]},"rescue-workers-helmet":{"a":"Rescue Worker’S Helmet","b":"26D1","j":["aid","cross","face","hat","helmet","rescue worker’s helmet","rescue_worker_s_helmet","construction","build"]},"prayer-beads":{"a":"Prayer Beads","b":"1F4FF","j":["beads","clothing","necklace","prayer","religion","dhikr","religious"]},"lipstick":{"a":"Lipstick","b":"1F484","j":["cosmetics","makeup","female","girl","fashion","woman"]},"ring":{"a":"Ring","b":"1F48D","j":["diamond","wedding","propose","marriage","valentines","fashion","jewelry","gem","engagement"]},"gem-stone":{"a":"Gem Stone","b":"1F48E","j":["diamond","gem","jewel","blue","ruby","jewelry"]},"muted-speaker":{"a":"Muted Speaker","b":"1F507","j":["mute","quiet","silent","speaker","sound","volume","silence"]},"speaker-low-volume":{"a":"Speaker Low Volume","b":"1F508","j":["soft","sound","volume","silence","broadcast"]},"speaker-medium-volume":{"a":"Speaker Medium Volume","b":"1F509","j":["medium","volume","speaker","broadcast"]},"speaker-high-volume":{"a":"Speaker High Volume","b":"1F50A","j":["loud","volume","noise","noisy","speaker","broadcast"]},"loudspeaker":{"a":"Loudspeaker","b":"1F4E2","j":["loud","public address","volume","sound"]},"megaphone":{"a":"Megaphone","b":"1F4E3","j":["cheering","sound","speaker","volume"]},"postal-horn":{"a":"Postal Horn","b":"1F4EF","j":["horn","post","postal","instrument","music"]},"bell":{"a":"Bell","b":"1F514","j":["sound","notification","christmas","xmas","chime"]},"bell-with-slash":{"a":"Bell with Slash","b":"1F515","j":["bell","forbidden","mute","quiet","silent","sound","volume"]},"musical-score":{"a":"Musical Score","b":"1F3BC","j":["music","score","treble","clef","compose"]},"musical-note":{"a":"Musical Note","b":"1F3B5","j":["music","note","score","tone","sound"]},"musical-notes":{"a":"Musical Notes","b":"1F3B6","j":["music","note","notes","score"]},"studio-microphone":{"a":"Studio Microphone","b":"1F399","j":["mic","microphone","music","studio","sing","recording","artist","talkshow"]},"level-slider":{"a":"Level Slider","b":"1F39A","j":["level","music","slider","scale"]},"control-knobs":{"a":"Control Knobs","b":"1F39B","j":["control","knobs","music","dial"]},"microphone":{"a":"Microphone","b":"1F3A4","j":["karaoke","mic","sound","music","PA","sing","talkshow"]},"headphone":{"a":"Headphone","b":"1F3A7","j":["earbud","music","score","gadgets"]},"radio":{"a":"Radio","b":"1F4FB","j":["video","communication","music","podcast","program"]},"saxophone":{"a":"Saxophone","b":"1F3B7","j":["instrument","music","sax","jazz","blues"]},"accordion":{"a":"Accordion","b":"1FA97","j":["concertina","squeeze box","music"]},"guitar":{"a":"Guitar","b":"1F3B8","j":["instrument","music"]},"musical-keyboard":{"a":"Musical Keyboard","b":"1F3B9","j":["instrument","keyboard","music","piano","compose"]},"trumpet":{"a":"Trumpet","b":"1F3BA","j":["instrument","music","brass"]},"violin":{"a":"Violin","b":"1F3BB","j":["instrument","music","orchestra","symphony"]},"banjo":{"a":"Banjo","b":"1FA95","j":["music","stringed","instructment"]},"drum":{"a":"Drum","b":"1F941","j":["drumsticks","music","instrument","snare"]},"long-drum":{"a":"Long Drum","b":"1FA98","j":["beat","conga","drum","rhythm","music"]},"mobile-phone":{"a":"Mobile Phone","b":"1F4F1","j":["cell","mobile","phone","telephone","technology","apple","gadgets","dial"]},"mobile-phone-with-arrow":{"a":"Mobile Phone with Arrow","b":"1F4F2","j":["arrow","cell","mobile","phone","receive","iphone","incoming"]},"telephone":{"a":"Telephone","b":"260E","j":["phone","technology","communication","dial"]},"telephone-receiver":{"a":"Telephone Receiver","b":"1F4DE","j":["phone","receiver","telephone","technology","communication","dial"]},"pager":{"a":"Pager","b":"1F4DF","j":["bbcall","oldschool","90s"]},"fax-machine":{"a":"Fax Machine","b":"1F4E0","j":["fax","communication","technology"]},"battery":{"a":"Battery","b":"1F50B","j":["power","energy","sustain"]},"low-battery":{"a":"⊛ Low Battery","b":"1FAAB","j":["electronic","low energy"]},"electric-plug":{"a":"Electric Plug","b":"1F50C","j":["electric","electricity","plug","charger","power"]},"laptop":{"a":"Laptop","b":"1F4BB","j":["computer","pc","personal","technology","screen","display","monitor"]},"desktop-computer":{"a":"Desktop Computer","b":"1F5A5","j":["computer","desktop","technology","computing","screen"]},"printer":{"a":"Printer","b":"1F5A8","j":["computer","paper","ink"]},"keyboard":{"a":"Keyboard","b":"2328","j":["computer","technology","type","input","text"]},"computer-mouse":{"a":"Computer Mouse","b":"1F5B1","j":["computer","click"]},"trackball":{"a":"Trackball","b":"1F5B2","j":["computer","technology","trackpad"]},"computer-disk":{"a":"Computer Disk","b":"1F4BD","j":["computer","disk","minidisk","optical","technology","record","data","90s"]},"floppy-disk":{"a":"Floppy Disk","b":"1F4BE","j":["computer","disk","floppy","oldschool","technology","save","90s","80s"]},"optical-disk":{"a":"Optical Disk","b":"1F4BF","j":["cd","computer","disk","optical","technology","dvd","disc","90s"]},"dvd":{"a":"Dvd","b":"1F4C0","j":["blu-ray","computer","disk","optical","cd","disc"]},"abacus":{"a":"Abacus","b":"1F9EE","j":["calculation"]},"movie-camera":{"a":"Movie Camera","b":"1F3A5","j":["camera","cinema","movie","film","record"]},"film-frames":{"a":"Film Frames","b":"1F39E","j":["cinema","film","frames","movie"]},"film-projector":{"a":"Film Projector","b":"1F4FD","j":["cinema","film","movie","projector","video","tape","record"]},"clapper-board":{"a":"Clapper Board","b":"1F3AC","j":["clapper","movie","film","record"]},"television":{"a":"Television","b":"1F4FA","j":["tv","video","technology","program","oldschool","show"]},"camera":{"a":"Camera","b":"1F4F7","j":["video","gadgets","photography"]},"camera-with-flash":{"a":"Camera with Flash","b":"1F4F8","j":["camera","flash","video","photography","gadgets"]},"video-camera":{"a":"Video Camera","b":"1F4F9","j":["camera","video","film","record"]},"videocassette":{"a":"Videocassette","b":"1F4FC","j":["tape","vhs","video","record","oldschool","90s","80s"]},"magnifying-glass-tilted-left":{"a":"Magnifying Glass Tilted Left","b":"1F50D","j":["glass","magnifying","search","tool","zoom","find","detective"]},"magnifying-glass-tilted-right":{"a":"Magnifying Glass Tilted Right","b":"1F50E","j":["glass","magnifying","search","tool","zoom","find","detective"]},"candle":{"a":"Candle","b":"1F56F","j":["light","fire","wax"]},"light-bulb":{"a":"Light Bulb","b":"1F4A1","j":["bulb","comic","electric","idea","light","electricity"]},"flashlight":{"a":"Flashlight","b":"1F526","j":["electric","light","tool","torch","dark","camping","sight","night"]},"red-paper-lantern":{"a":"Red Paper Lantern","b":"1F3EE","j":["bar","lantern","light","red","paper","halloween","spooky"]},"diya-lamp":{"a":"Diya Lamp","b":"1FA94","j":["diya","lamp","oil","lighting"]},"notebook-with-decorative-cover":{"a":"Notebook with Decorative Cover","b":"1F4D4","j":["book","cover","decorated","notebook","classroom","notes","record","paper","study"]},"closed-book":{"a":"Closed Book","b":"1F4D5","j":["book","closed","read","library","knowledge","textbook","learn"]},"open-book":{"a":"Open Book","b":"1F4D6","j":["book","open","read","library","knowledge","literature","learn","study"]},"green-book":{"a":"Green Book","b":"1F4D7","j":["book","green","read","library","knowledge","study"]},"blue-book":{"a":"Blue Book","b":"1F4D8","j":["blue","book","read","library","knowledge","learn","study"]},"orange-book":{"a":"Orange Book","b":"1F4D9","j":["book","orange","read","library","knowledge","textbook","study"]},"books":{"a":"Books","b":"1F4DA","j":["book","literature","library","study"]},"notebook":{"a":"Notebook","b":"1F4D3","j":["stationery","record","notes","paper","study"]},"ledger":{"a":"Ledger","b":"1F4D2","j":["notebook","notes","paper"]},"page-with-curl":{"a":"Page with Curl","b":"1F4C3","j":["curl","document","page","documents","office","paper"]},"scroll":{"a":"Scroll","b":"1F4DC","j":["paper","documents","ancient","history"]},"page-facing-up":{"a":"Page Facing Up","b":"1F4C4","j":["document","page","documents","office","paper","information"]},"newspaper":{"a":"Newspaper","b":"1F4F0","j":["news","paper","press","headline"]},"rolledup-newspaper":{"a":"Rolled-Up Newspaper","b":"1F5DE","j":["news","newspaper","paper","rolled","rolled-up newspaper","rolled_up_newspaper","press","headline"]},"bookmark-tabs":{"a":"Bookmark Tabs","b":"1F4D1","j":["bookmark","mark","marker","tabs","favorite","save","order","tidy"]},"bookmark":{"a":"Bookmark","b":"1F516","j":["mark","favorite","label","save"]},"label":{"a":"Label","b":"1F3F7","j":["sale","tag"]},"money-bag":{"a":"Money Bag","b":"1F4B0","j":["bag","dollar","money","moneybag","payment","coins","sale"]},"coin":{"a":"Coin","b":"1FA99","j":["gold","metal","money","silver","treasure","currency"]},"yen-banknote":{"a":"Yen Banknote","b":"1F4B4","j":["banknote","bill","currency","money","note","yen","sales","japanese","dollar"]},"dollar-banknote":{"a":"Dollar Banknote","b":"1F4B5","j":["banknote","bill","currency","dollar","money","note","sales"]},"euro-banknote":{"a":"Euro Banknote","b":"1F4B6","j":["banknote","bill","currency","euro","money","note","sales","dollar"]},"pound-banknote":{"a":"Pound Banknote","b":"1F4B7","j":["banknote","bill","currency","money","note","pound","british","sterling","sales","bills","uk","england"]},"money-with-wings":{"a":"Money with Wings","b":"1F4B8","j":["banknote","bill","fly","money","wings","dollar","bills","payment","sale"]},"credit-card":{"a":"Credit Card","b":"1F4B3","j":["card","credit","money","sales","dollar","bill","payment","shopping"]},"receipt":{"a":"Receipt","b":"1F9FE","j":["accounting","bookkeeping","evidence","proof","expenses"]},"chart-increasing-with-yen":{"a":"Chart Increasing with Yen","b":"1F4B9","j":["chart","graph","growth","money","yen","green-square","presentation","stats"]},"envelope":{"a":"Envelope","b":"2709","j":["email","letter","postal","inbox","communication"]},"email":{"a":"E-Mail","b":"1F4E7","j":["e-mail","letter","mail","e_mail","communication","inbox"]},"incoming-envelope":{"a":"Incoming Envelope","b":"1F4E8","j":["e-mail","email","envelope","incoming","letter","receive","inbox"]},"envelope-with-arrow":{"a":"Envelope with Arrow","b":"1F4E9","j":["arrow","e-mail","email","envelope","outgoing","communication"]},"outbox-tray":{"a":"Outbox Tray","b":"1F4E4","j":["box","letter","mail","outbox","sent","tray","inbox","email"]},"inbox-tray":{"a":"Inbox Tray","b":"1F4E5","j":["box","inbox","letter","mail","receive","tray","email","documents"]},"package":{"a":"Package","b":"1F4E6","j":["box","parcel","mail","gift","cardboard","moving"]},"closed-mailbox-with-raised-flag":{"a":"Closed Mailbox with Raised Flag","b":"1F4EB","j":["closed","mail","mailbox","postbox","email","inbox","communication"]},"closed-mailbox-with-lowered-flag":{"a":"Closed Mailbox with Lowered Flag","b":"1F4EA","j":["closed","lowered","mail","mailbox","postbox","email","communication","inbox"]},"open-mailbox-with-raised-flag":{"a":"Open Mailbox with Raised Flag","b":"1F4EC","j":["mail","mailbox","open","postbox","email","inbox","communication"]},"open-mailbox-with-lowered-flag":{"a":"Open Mailbox with Lowered Flag","b":"1F4ED","j":["lowered","mail","mailbox","open","postbox","email","inbox"]},"postbox":{"a":"Postbox","b":"1F4EE","j":["mail","mailbox","email","letter","envelope"]},"ballot-box-with-ballot":{"a":"Ballot Box with Ballot","b":"1F5F3","j":["ballot","box","election","vote"]},"pencil":{"a":"Pencil","b":"270F","j":["stationery","write","paper","writing","school","study"]},"black-nib":{"a":"Black Nib","b":"2712","j":["nib","pen","stationery","writing","write"]},"fountain-pen":{"a":"Fountain Pen","b":"1F58B","j":["fountain","pen","stationery","writing","write"]},"pen":{"a":"Pen","b":"1F58A","j":["ballpoint","stationery","writing","write"]},"paintbrush":{"a":"Paintbrush","b":"1F58C","j":["painting","drawing","creativity","art"]},"crayon":{"a":"Crayon","b":"1F58D","j":["drawing","creativity"]},"memo":{"a":"Memo","b":"1F4DD","j":["pencil","write","documents","stationery","paper","writing","legal","exam","quiz","test","study","compose"]},"briefcase":{"a":"Briefcase","b":"1F4BC","j":["business","documents","work","law","legal","job","career"]},"file-folder":{"a":"File Folder","b":"1F4C1","j":["file","folder","documents","business","office"]},"open-file-folder":{"a":"Open File Folder","b":"1F4C2","j":["file","folder","open","documents","load"]},"card-index-dividers":{"a":"Card Index Dividers","b":"1F5C2","j":["card","dividers","index","organizing","business","stationery"]},"calendar":{"a":"Calendar","b":"1F4C5","j":["date","schedule"]},"tearoff-calendar":{"a":"Tear-off Calendar","b":"1F4C6","j":["calendar","tear-off calendar","tear_off_calendar","schedule","date","planning"]},"spiral-notepad":{"a":"Spiral Notepad","b":"1F5D2","j":["note","pad","spiral","memo","stationery"]},"spiral-calendar":{"a":"Spiral Calendar","b":"1F5D3","j":["calendar","pad","spiral","date","schedule","planning"]},"card-index":{"a":"Card Index","b":"1F4C7","j":["card","index","rolodex","business","stationery"]},"chart-increasing":{"a":"Chart Increasing","b":"1F4C8","j":["chart","graph","growth","trend","upward","presentation","stats","recovery","business","economics","money","sales","good","success"]},"chart-decreasing":{"a":"Chart Decreasing","b":"1F4C9","j":["chart","down","graph","trend","presentation","stats","recession","business","economics","money","sales","bad","failure"]},"bar-chart":{"a":"Bar Chart","b":"1F4CA","j":["bar","chart","graph","presentation","stats"]},"clipboard":{"a":"Clipboard","b":"1F4CB","j":["stationery","documents"]},"pushpin":{"a":"Pushpin","b":"1F4CC","j":["pin","stationery","mark","here"]},"round-pushpin":{"a":"Round Pushpin","b":"1F4CD","j":["pin","pushpin","stationery","location","map","here"]},"paperclip":{"a":"Paperclip","b":"1F4CE","j":["documents","stationery"]},"linked-paperclips":{"a":"Linked Paperclips","b":"1F587","j":["link","paperclip","documents","stationery"]},"straight-ruler":{"a":"Straight Ruler","b":"1F4CF","j":["ruler","straight edge","stationery","calculate","length","math","school","drawing","architect","sketch"]},"triangular-ruler":{"a":"Triangular Ruler","b":"1F4D0","j":["ruler","set","triangle","stationery","math","architect","sketch"]},"scissors":{"a":"Scissors","b":"2702","j":["cutting","tool","stationery","cut"]},"card-file-box":{"a":"Card File Box","b":"1F5C3","j":["box","card","file","business","stationery"]},"file-cabinet":{"a":"File Cabinet","b":"1F5C4","j":["cabinet","file","filing","organizing"]},"wastebasket":{"a":"Wastebasket","b":"1F5D1","j":["bin","trash","rubbish","garbage","toss"]},"locked":{"a":"Locked","b":"1F512","j":["closed","security","password","padlock"]},"unlocked":{"a":"Unlocked","b":"1F513","j":["lock","open","unlock","privacy","security"]},"locked-with-pen":{"a":"Locked with Pen","b":"1F50F","j":["ink","lock","nib","pen","privacy","security","secret"]},"locked-with-key":{"a":"Locked with Key","b":"1F510","j":["closed","key","lock","secure","security","privacy"]},"key":{"a":"Key","b":"1F511","j":["lock","password","door"]},"old-key":{"a":"Old Key","b":"1F5DD","j":["clue","key","lock","old","door","password"]},"hammer":{"a":"Hammer","b":"1F528","j":["tool","tools","build","create"]},"axe":{"a":"Axe","b":"1FA93","j":["chop","hatchet","split","wood","tool","cut"]},"pick":{"a":"Pick","b":"26CF","j":["mining","tool","tools","dig"]},"hammer-and-pick":{"a":"Hammer and Pick","b":"2692","j":["hammer","pick","tool","tools","build","create"]},"hammer-and-wrench":{"a":"Hammer and Wrench","b":"1F6E0","j":["hammer","spanner","tool","wrench","tools","build","create"]},"dagger":{"a":"Dagger","b":"1F5E1","j":["knife","weapon"]},"crossed-swords":{"a":"Crossed Swords","b":"2694","j":["crossed","swords","weapon"]},"water-pistol":{"a":"Water Pistol","b":"1F52B","j":["gun","handgun","pistol","revolver","tool","water","weapon","violence"]},"boomerang":{"a":"Boomerang","b":"1FA83","j":["australia","rebound","repercussion","weapon"]},"bow-and-arrow":{"a":"Bow and Arrow","b":"1F3F9","j":["archer","arrow","bow","Sagittarius","zodiac","sports"]},"shield":{"a":"Shield","b":"1F6E1","j":["weapon","protection","security"]},"carpentry-saw":{"a":"Carpentry Saw","b":"1FA9A","j":["carpenter","lumber","saw","tool","cut","chop"]},"wrench":{"a":"Wrench","b":"1F527","j":["spanner","tool","tools","diy","ikea","fix","maintainer"]},"screwdriver":{"a":"Screwdriver","b":"1FA9B","j":["screw","tool","tools"]},"nut-and-bolt":{"a":"Nut and Bolt","b":"1F529","j":["bolt","nut","tool","handy","tools","fix"]},"gear":{"a":"Gear","b":"2699","j":["cog","cogwheel","tool"]},"clamp":{"a":"Clamp","b":"1F5DC","j":["compress","tool","vice"]},"balance-scale":{"a":"Balance Scale","b":"2696","j":["balance","justice","Libra","scale","zodiac","law","fairness","weight"]},"white-cane":{"a":"White Cane","b":"1F9AF","j":["accessibility","blind","probing_cane"]},"link":{"a":"Link","b":"1F517","j":["rings","url"]},"chains":{"a":"Chains","b":"26D3","j":["chain","lock","arrest"]},"hook":{"a":"Hook","b":"1FA9D","j":["catch","crook","curve","ensnare","selling point","tools"]},"toolbox":{"a":"Toolbox","b":"1F9F0","j":["chest","mechanic","tool","tools","diy","fix","maintainer"]},"magnet":{"a":"Magnet","b":"1F9F2","j":["attraction","horseshoe","magnetic"]},"ladder":{"a":"Ladder","b":"1FA9C","j":["climb","rung","step","tools"]},"alembic":{"a":"Alembic","b":"2697","j":["chemistry","tool","distilling","science","experiment"]},"test-tube":{"a":"Test Tube","b":"1F9EA","j":["chemist","chemistry","experiment","lab","science"]},"petri-dish":{"a":"Petri Dish","b":"1F9EB","j":["bacteria","biologist","biology","culture","lab"]},"dna":{"a":"Dna","b":"1F9EC","j":["biologist","evolution","gene","genetics","life"]},"microscope":{"a":"Microscope","b":"1F52C","j":["science","tool","laboratory","experiment","zoomin","study"]},"telescope":{"a":"Telescope","b":"1F52D","j":["science","tool","stars","space","zoom","astronomy"]},"satellite-antenna":{"a":"Satellite Antenna","b":"1F4E1","j":["antenna","dish","satellite","communication","future","radio","space"]},"syringe":{"a":"Syringe","b":"1F489","j":["medicine","needle","shot","sick","health","hospital","drugs","blood","doctor","nurse"]},"drop-of-blood":{"a":"Drop of Blood","b":"1FA78","j":["bleed","blood donation","injury","medicine","menstruation","period","hurt","harm","wound"]},"pill":{"a":"Pill","b":"1F48A","j":["doctor","medicine","sick","health","pharmacy","drug"]},"adhesive-bandage":{"a":"Adhesive Bandage","b":"1FA79","j":["bandage","heal"]},"crutch":{"a":"⊛ Crutch","b":"1FA7C","j":["cane","disability","hurt","mobility aid","stick"]},"stethoscope":{"a":"Stethoscope","b":"1FA7A","j":["doctor","heart","medicine","health"]},"xray":{"a":"⊛ X-Ray","b":"1FA7B","j":["bones","doctor","medical","skeleton"]},"door":{"a":"Door","b":"1F6AA","j":["house","entry","exit"]},"elevator":{"a":"Elevator","b":"1F6D7","j":["accessibility","hoist","lift"]},"mirror":{"a":"Mirror","b":"1FA9E","j":["reflection","reflector","speculum"]},"window":{"a":"Window","b":"1FA9F","j":["frame","fresh air","opening","transparent","view","scenery"]},"bed":{"a":"Bed","b":"1F6CF","j":["hotel","sleep","rest"]},"couch-and-lamp":{"a":"Couch and Lamp","b":"1F6CB","j":["couch","hotel","lamp","read","chill"]},"chair":{"a":"Chair","b":"1FA91","j":["seat","sit","furniture"]},"toilet":{"a":"Toilet","b":"1F6BD","j":["restroom","wc","washroom","bathroom","potty"]},"plunger":{"a":"Plunger","b":"1FAA0","j":["force cup","plumber","suction","toilet"]},"shower":{"a":"Shower","b":"1F6BF","j":["water","clean","bathroom"]},"bathtub":{"a":"Bathtub","b":"1F6C1","j":["bath","clean","shower","bathroom"]},"mouse-trap":{"a":"Mouse Trap","b":"1FAA4","j":["bait","mousetrap","snare","trap","cheese"]},"razor":{"a":"Razor","b":"1FA92","j":["sharp","shave","cut"]},"lotion-bottle":{"a":"Lotion Bottle","b":"1F9F4","j":["lotion","moisturizer","shampoo","sunscreen"]},"safety-pin":{"a":"Safety Pin","b":"1F9F7","j":["diaper","punk rock"]},"broom":{"a":"Broom","b":"1F9F9","j":["cleaning","sweeping","witch"]},"basket":{"a":"Basket","b":"1F9FA","j":["farming","laundry","picnic"]},"roll-of-paper":{"a":"Roll of Paper","b":"1F9FB","j":["paper towels","toilet paper","roll"]},"bucket":{"a":"Bucket","b":"1FAA3","j":["cask","pail","vat","water","container"]},"soap":{"a":"Soap","b":"1F9FC","j":["bar","bathing","cleaning","lather","soapdish"]},"bubbles":{"a":"⊛ Bubbles","b":"1FAE7","j":["burp","clean","soap","underwater"]},"toothbrush":{"a":"Toothbrush","b":"1FAA5","j":["bathroom","brush","clean","dental","hygiene","teeth"]},"sponge":{"a":"Sponge","b":"1F9FD","j":["absorbing","cleaning","porous"]},"fire-extinguisher":{"a":"Fire Extinguisher","b":"1F9EF","j":["extinguish","fire","quench"]},"shopping-cart":{"a":"Shopping Cart","b":"1F6D2","j":["cart","shopping","trolley"]},"cigarette":{"a":"Cigarette","b":"1F6AC","j":["smoking","kills","tobacco","joint","smoke"]},"coffin":{"a":"Coffin","b":"26B0","j":["death","vampire","dead","die","rip","graveyard","cemetery","casket","funeral","box"]},"headstone":{"a":"Headstone","b":"1FAA6","j":["cemetery","grave","graveyard","tombstone","death","rip"]},"funeral-urn":{"a":"Funeral Urn","b":"26B1","j":["ashes","death","funeral","urn","dead","die","rip"]},"moai":{"a":"Moai","b":"1F5FF","j":["face","moyai","statue","rock","easter island"]},"placard":{"a":"Placard","b":"1FAA7","j":["demonstration","picket","protest","sign","announcement"]},"identification-card":{"a":"⊛ Identification Card","b":"1FAAA","j":["credentials","ID","license","security"]},"atm-sign":{"a":"Atm Sign","b":"1F3E7","j":["atm","ATM sign","automated","bank","teller","money","sales","cash","blue-square","payment"]},"litter-in-bin-sign":{"a":"Litter in Bin Sign","b":"1F6AE","j":["litter","litter bin","blue-square","sign","human","info"]},"potable-water":{"a":"Potable Water","b":"1F6B0","j":["drinking","potable","water","blue-square","liquid","restroom","cleaning","faucet"]},"wheelchair-symbol":{"a":"Wheelchair Symbol","b":"267F","j":["access","blue-square","disabled","accessibility"]},"mens-room":{"a":"Men’S Room","b":"1F6B9","j":["lavatory","man","men’s room","restroom","wc","men_s_room","toilet","blue-square","gender","male"]},"womens-room":{"a":"Women’S Room","b":"1F6BA","j":["lavatory","restroom","wc","woman","women’s room","women_s_room","purple-square","female","toilet","loo","gender"]},"restroom":{"a":"Restroom","b":"1F6BB","j":["lavatory","WC","blue-square","toilet","refresh","wc","gender"]},"baby-symbol":{"a":"Baby Symbol","b":"1F6BC","j":["baby","changing","orange-square","child"]},"water-closet":{"a":"Water Closet","b":"1F6BE","j":["closet","lavatory","restroom","water","wc","toilet","blue-square"]},"passport-control":{"a":"Passport Control","b":"1F6C2","j":["control","passport","custom","blue-square"]},"customs":{"a":"Customs","b":"1F6C3","j":["passport","border","blue-square"]},"baggage-claim":{"a":"Baggage Claim","b":"1F6C4","j":["baggage","claim","blue-square","airport","transport"]},"left-luggage":{"a":"Left Luggage","b":"1F6C5","j":["baggage","locker","luggage","blue-square","travel"]},"warning":{"a":"Warning","b":"26A0","j":["exclamation","wip","alert","error","problem","issue"]},"children-crossing":{"a":"Children Crossing","b":"1F6B8","j":["child","crossing","pedestrian","traffic","school","warning","danger","sign","driving","yellow-diamond"]},"no-entry":{"a":"No Entry","b":"26D4","j":["entry","forbidden","no","not","prohibited","traffic","limit","security","privacy","bad","denied","stop","circle"]},"prohibited":{"a":"Prohibited","b":"1F6AB","j":["entry","forbidden","no","not","forbid","stop","limit","denied","disallow","circle"]},"no-bicycles":{"a":"No Bicycles","b":"1F6B3","j":["bicycle","bike","forbidden","no","prohibited","cyclist","circle"]},"no-smoking":{"a":"No Smoking","b":"1F6AD","j":["forbidden","no","not","prohibited","smoking","cigarette","blue-square","smell","smoke"]},"no-littering":{"a":"No Littering","b":"1F6AF","j":["forbidden","litter","no","not","prohibited","trash","bin","garbage","circle"]},"nonpotable-water":{"a":"Non-Potable Water","b":"1F6B1","j":["non-drinking","non-potable","water","non_potable_water","drink","faucet","tap","circle"]},"no-pedestrians":{"a":"No Pedestrians","b":"1F6B7","j":["forbidden","no","not","pedestrian","prohibited","rules","crossing","walking","circle"]},"no-mobile-phones":{"a":"No Mobile Phones","b":"1F4F5","j":["cell","forbidden","mobile","no","phone","iphone","mute","circle"]},"no-one-under-eighteen":{"a":"No One Under Eighteen","b":"1F51E","j":["18","age restriction","eighteen","prohibited","underage","drink","pub","night","minor","circle"]},"radioactive":{"a":"Radioactive","b":"2622","j":["sign","nuclear","danger"]},"biohazard":{"a":"Biohazard","b":"2623","j":["sign","danger"]},"up-arrow":{"a":"Up Arrow","b":"2B06","j":["arrow","cardinal","direction","north","blue-square","continue","top"]},"upright-arrow":{"a":"Up-Right Arrow","b":"2197","j":["arrow","direction","intercardinal","northeast","up-right arrow","up_right_arrow","blue-square","point","diagonal"]},"right-arrow":{"a":"Right Arrow","b":"27A1","j":["arrow","cardinal","direction","east","blue-square","next"]},"downright-arrow":{"a":"Down-Right Arrow","b":"2198","j":["arrow","direction","down-right arrow","intercardinal","southeast","down_right_arrow","blue-square","diagonal"]},"down-arrow":{"a":"Down Arrow","b":"2B07","j":["arrow","cardinal","direction","down","south","blue-square","bottom"]},"downleft-arrow":{"a":"Down-Left Arrow","b":"2199","j":["arrow","direction","down-left arrow","intercardinal","southwest","down_left_arrow","blue-square","diagonal"]},"left-arrow":{"a":"Left Arrow","b":"2B05","j":["arrow","cardinal","direction","west","blue-square","previous","back"]},"upleft-arrow":{"a":"Up-Left Arrow","b":"2196","j":["arrow","direction","intercardinal","northwest","up-left arrow","up_left_arrow","blue-square","point","diagonal"]},"updown-arrow":{"a":"Up-Down Arrow","b":"2195","j":["arrow","up-down arrow","up_down_arrow","blue-square","direction","way","vertical"]},"leftright-arrow":{"a":"Left-Right Arrow","b":"2194","j":["arrow","left-right arrow","left_right_arrow","shape","direction","horizontal","sideways"]},"right-arrow-curving-left":{"a":"Right Arrow Curving Left","b":"21A9","j":["arrow","back","return","blue-square","undo","enter"]},"left-arrow-curving-right":{"a":"Left Arrow Curving Right","b":"21AA","j":["arrow","blue-square","return","rotate","direction"]},"right-arrow-curving-up":{"a":"Right Arrow Curving Up","b":"2934","j":["arrow","blue-square","direction","top"]},"right-arrow-curving-down":{"a":"Right Arrow Curving Down","b":"2935","j":["arrow","down","blue-square","direction","bottom"]},"clockwise-vertical-arrows":{"a":"Clockwise Vertical Arrows","b":"1F503","j":["arrow","clockwise","reload","sync","cycle","round","repeat"]},"counterclockwise-arrows-button":{"a":"Counterclockwise Arrows Button","b":"1F504","j":["anticlockwise","arrow","counterclockwise","withershins","blue-square","sync","cycle"]},"back-arrow":{"a":"Back Arrow","b":"1F519","j":["arrow","back","BACK arrow","words","return"]},"end-arrow":{"a":"End Arrow","b":"1F51A","j":["arrow","end","END arrow","words"]},"on-arrow":{"a":"On! Arrow","b":"1F51B","j":["arrow","mark","on","ON! arrow","words"]},"soon-arrow":{"a":"Soon Arrow","b":"1F51C","j":["arrow","soon","SOON arrow","words"]},"top-arrow":{"a":"Top Arrow","b":"1F51D","j":["arrow","top","TOP arrow","up","words","blue-square"]},"place-of-worship":{"a":"Place of Worship","b":"1F6D0","j":["religion","worship","church","temple","prayer"]},"atom-symbol":{"a":"Atom Symbol","b":"269B","j":["atheist","atom","science","physics","chemistry"]},"om":{"a":"Om","b":"1F549","j":["Hindu","religion","hinduism","buddhism","sikhism","jainism"]},"star-of-david":{"a":"Star of David","b":"2721","j":["David","Jew","Jewish","religion","star","star of David","judaism"]},"wheel-of-dharma":{"a":"Wheel of Dharma","b":"2638","j":["Buddhist","dharma","religion","wheel","hinduism","buddhism","sikhism","jainism"]},"yin-yang":{"a":"Yin Yang","b":"262F","j":["religion","tao","taoist","yang","yin","balance"]},"latin-cross":{"a":"Latin Cross","b":"271D","j":["Christian","cross","religion","christianity"]},"orthodox-cross":{"a":"Orthodox Cross","b":"2626","j":["Christian","cross","religion","suppedaneum"]},"star-and-crescent":{"a":"Star and Crescent","b":"262A","j":["islam","Muslim","religion"]},"peace-symbol":{"a":"Peace Symbol","b":"262E","j":["peace","hippie"]},"menorah":{"a":"Menorah","b":"1F54E","j":["candelabrum","candlestick","religion","hanukkah","candles","jewish"]},"dotted-sixpointed-star":{"a":"Dotted Six-Pointed Star","b":"1F52F","j":["dotted six-pointed star","fortune","star","dotted_six_pointed_star","purple-square","religion","jewish","hexagram"]},"aries":{"a":"Aries","b":"2648","j":["ram","zodiac","sign","purple-square","astrology"]},"taurus":{"a":"Taurus","b":"2649","j":["bull","ox","zodiac","purple-square","sign","astrology"]},"gemini":{"a":"Gemini","b":"264A","j":["twins","zodiac","sign","purple-square","astrology"]},"cancer":{"a":"Cancer","b":"264B","j":["crab","zodiac","sign","purple-square","astrology"]},"leo":{"a":"Leo","b":"264C","j":["lion","zodiac","sign","purple-square","astrology"]},"virgo":{"a":"Virgo","b":"264D","j":["zodiac","sign","purple-square","astrology"]},"libra":{"a":"Libra","b":"264E","j":["balance","justice","scales","zodiac","sign","purple-square","astrology"]},"scorpio":{"a":"Scorpio","b":"264F","j":["scorpion","scorpius","zodiac","sign","purple-square","astrology"]},"sagittarius":{"a":"Sagittarius","b":"2650","j":["archer","zodiac","sign","purple-square","astrology"]},"capricorn":{"a":"Capricorn","b":"2651","j":["goat","zodiac","sign","purple-square","astrology"]},"aquarius":{"a":"Aquarius","b":"2652","j":["bearer","water","zodiac","sign","purple-square","astrology"]},"pisces":{"a":"Pisces","b":"2653","j":["fish","zodiac","purple-square","sign","astrology"]},"ophiuchus":{"a":"Ophiuchus","b":"26CE","j":["bearer","serpent","snake","zodiac","sign","purple-square","constellation","astrology"]},"shuffle-tracks-button":{"a":"Shuffle Tracks Button","b":"1F500","j":["arrow","crossed","blue-square","shuffle","music","random"]},"repeat-button":{"a":"Repeat Button","b":"1F501","j":["arrow","clockwise","repeat","loop","record"]},"repeat-single-button":{"a":"Repeat Single Button","b":"1F502","j":["arrow","clockwise","once","blue-square","loop"]},"play-button":{"a":"Play Button","b":"25B6","j":["arrow","play","right","triangle","blue-square","direction"]},"fastforward-button":{"a":"Fast-Forward Button","b":"23E9","j":["arrow","double","fast","fast-forward button","forward","fast_forward_button","blue-square","play","speed","continue"]},"next-track-button":{"a":"Next Track Button","b":"23ED","j":["arrow","next scene","next track","triangle","forward","next","blue-square"]},"play-or-pause-button":{"a":"Play or Pause Button","b":"23EF","j":["arrow","pause","play","right","triangle","blue-square"]},"reverse-button":{"a":"Reverse Button","b":"25C0","j":["arrow","left","reverse","triangle","blue-square","direction"]},"fast-reverse-button":{"a":"Fast Reverse Button","b":"23EA","j":["arrow","double","rewind","play","blue-square"]},"last-track-button":{"a":"Last Track Button","b":"23EE","j":["arrow","previous scene","previous track","triangle","backward"]},"upwards-button":{"a":"Upwards Button","b":"1F53C","j":["arrow","button","red","blue-square","triangle","direction","point","forward","top"]},"fast-up-button":{"a":"Fast Up Button","b":"23EB","j":["arrow","double","blue-square","direction","top"]},"downwards-button":{"a":"Downwards Button","b":"1F53D","j":["arrow","button","down","red","blue-square","direction","bottom"]},"fast-down-button":{"a":"Fast Down Button","b":"23EC","j":["arrow","double","down","blue-square","direction","bottom"]},"pause-button":{"a":"Pause Button","b":"23F8","j":["bar","double","pause","vertical","blue-square"]},"stop-button":{"a":"Stop Button","b":"23F9","j":["square","stop","blue-square"]},"record-button":{"a":"Record Button","b":"23FA","j":["circle","record","blue-square"]},"eject-button":{"a":"Eject Button","b":"23CF","j":["eject","blue-square"]},"cinema":{"a":"Cinema","b":"1F3A6","j":["camera","film","movie","blue-square","record","curtain","stage","theater"]},"dim-button":{"a":"Dim Button","b":"1F505","j":["brightness","dim","low","sun","afternoon","warm","summer"]},"bright-button":{"a":"Bright Button","b":"1F506","j":["bright","brightness","sun","light"]},"antenna-bars":{"a":"Antenna Bars","b":"1F4F6","j":["antenna","bar","cell","mobile","phone","blue-square","reception","internet","connection","wifi","bluetooth","bars"]},"vibration-mode":{"a":"Vibration Mode","b":"1F4F3","j":["cell","mobile","mode","phone","telephone","vibration","orange-square"]},"mobile-phone-off":{"a":"Mobile Phone off","b":"1F4F4","j":["cell","mobile","off","phone","telephone","mute","orange-square","silence","quiet"]},"female-sign":{"a":"Female Sign","b":"2640","j":["woman","women","lady","girl"]},"male-sign":{"a":"Male Sign","b":"2642","j":["man","boy","men"]},"transgender-symbol":{"a":"Transgender Symbol","b":"26A7","j":["transgender","lgbtq"]},"multiply":{"a":"Multiply","b":"2716","j":["×","cancel","multiplication","sign","x","multiplication_sign","math","calculation"]},"plus":{"a":"Plus","b":"2795","j":["+","math","sign","plus_sign","calculation","addition","more","increase"]},"minus":{"a":"Minus","b":"2796","j":["-","−","math","sign","minus_sign","calculation","subtract","less"]},"divide":{"a":"Divide","b":"2797","j":["÷","division","math","sign","division_sign","calculation"]},"heavy-equals-sign":{"a":"⊛ Heavy Equals Sign","b":"1F7F0","j":["equality","math"]},"infinity":{"a":"Infinity","b":"267E","j":["forever","unbounded","universal"]},"double-exclamation-mark":{"a":"Double Exclamation Mark","b":"203C","j":["!","!!","bangbang","exclamation","mark","surprise"]},"exclamation-question-mark":{"a":"Exclamation Question Mark","b":"2049","j":["!","!?","?","exclamation","interrobang","mark","punctuation","question","wat","surprise"]},"red-question-mark":{"a":"Red Question Mark","b":"2753","j":["?","mark","punctuation","question","question_mark","doubt","confused"]},"white-question-mark":{"a":"White Question Mark","b":"2754","j":["?","mark","outlined","punctuation","question","doubts","gray","huh","confused"]},"white-exclamation-mark":{"a":"White Exclamation Mark","b":"2755","j":["!","exclamation","mark","outlined","punctuation","surprise","gray","wow","warning"]},"red-exclamation-mark":{"a":"Red Exclamation Mark","b":"2757","j":["!","exclamation","mark","punctuation","exclamation_mark","heavy_exclamation_mark","danger","surprise","wow","warning"]},"wavy-dash":{"a":"Wavy Dash","b":"3030","j":["dash","punctuation","wavy","draw","line","moustache","mustache","squiggle","scribble"]},"currency-exchange":{"a":"Currency Exchange","b":"1F4B1","j":["bank","currency","exchange","money","sales","dollar","travel"]},"heavy-dollar-sign":{"a":"Heavy Dollar Sign","b":"1F4B2","j":["currency","dollar","money","sales","payment","buck"]},"medical-symbol":{"a":"Medical Symbol","b":"2695","j":["aesculapius","medicine","staff","health","hospital"]},"recycling-symbol":{"a":"Recycling Symbol","b":"267B","j":["recycle","arrow","environment","garbage","trash"]},"fleurdelis":{"a":"Fleur-De-Lis","b":"269C","j":["fleur-de-lis","fleur_de_lis","decorative","scout"]},"trident-emblem":{"a":"Trident Emblem","b":"1F531","j":["anchor","emblem","ship","tool","trident","weapon","spear"]},"name-badge":{"a":"Name Badge","b":"1F4DB","j":["badge","name","fire","forbid"]},"japanese-symbol-for-beginner":{"a":"Japanese Symbol for Beginner","b":"1F530","j":["beginner","chevron","Japanese","Japanese symbol for beginner","leaf","badge","shield"]},"hollow-red-circle":{"a":"Hollow Red Circle","b":"2B55","j":["circle","large","o","red","round"]},"check-mark-button":{"a":"Check Mark Button","b":"2705","j":["✓","button","check","mark","green-square","ok","agree","vote","election","answer","tick"]},"check-box-with-check":{"a":"Check Box with Check","b":"2611","j":["✓","box","check","ok","agree","confirm","black-square","vote","election","yes","tick"]},"check-mark":{"a":"Check Mark","b":"2714","j":["✓","check","mark","ok","nike","answer","yes","tick"]},"cross-mark":{"a":"Cross Mark","b":"274C","j":["×","cancel","cross","mark","multiplication","multiply","x","no","delete","remove","red"]},"cross-mark-button":{"a":"Cross Mark Button","b":"274E","j":["×","mark","square","x","green-square","no","deny"]},"curly-loop":{"a":"Curly Loop","b":"27B0","j":["curl","loop","scribble","draw","shape","squiggle"]},"double-curly-loop":{"a":"Double Curly Loop","b":"27BF","j":["curl","double","loop","tape","cassette"]},"part-alternation-mark":{"a":"Part Alternation Mark","b":"303D","j":["mark","part","graph","presentation","stats","business","economics","bad"]},"eightspoked-asterisk":{"a":"Eight-Spoked Asterisk","b":"2733","j":["*","asterisk","eight-spoked asterisk","eight_spoked_asterisk","star","sparkle","green-square"]},"eightpointed-star":{"a":"Eight-Pointed Star","b":"2734","j":["*","eight-pointed star","star","eight_pointed_star","orange-square","shape","polygon"]},"sparkle":{"a":"Sparkle","b":"2747","j":["*","stars","green-square","awesome","good","fireworks"]},"copyright":{"a":"Copyright","b":"00A9","j":["c","ip","license","circle","law","legal"]},"registered":{"a":"Registered","b":"00AE","j":["r","alphabet","circle"]},"trade-mark":{"a":"Trade Mark","b":"2122","j":["mark","tm","trademark","brand","law","legal"]},"keycap":{"a":"Keycap: *","b":"002A-FE0F-20E3","j":["keycap_","star"]},"keycap-0":{"a":"Keycap: 0","b":"0030-FE0F-20E3","j":["keycap","0","numbers","blue-square","null"]},"keycap-1":{"a":"Keycap: 1","b":"0031-FE0F-20E3","j":["keycap","blue-square","numbers","1"]},"keycap-2":{"a":"Keycap: 2","b":"0032-FE0F-20E3","j":["keycap","numbers","2","prime","blue-square"]},"keycap-3":{"a":"Keycap: 3","b":"0033-FE0F-20E3","j":["keycap","3","numbers","prime","blue-square"]},"keycap-4":{"a":"Keycap: 4","b":"0034-FE0F-20E3","j":["keycap","4","numbers","blue-square"]},"keycap-5":{"a":"Keycap: 5","b":"0035-FE0F-20E3","j":["keycap","5","numbers","blue-square","prime"]},"keycap-6":{"a":"Keycap: 6","b":"0036-FE0F-20E3","j":["keycap","6","numbers","blue-square"]},"keycap-7":{"a":"Keycap: 7","b":"0037-FE0F-20E3","j":["keycap","7","numbers","blue-square","prime"]},"keycap-8":{"a":"Keycap: 8","b":"0038-FE0F-20E3","j":["keycap","8","blue-square","numbers"]},"keycap-9":{"a":"Keycap: 9","b":"0039-FE0F-20E3","j":["keycap","blue-square","numbers","9"]},"keycap-10":{"a":"Keycap: 10","b":"1F51F","j":["keycap","numbers","10","blue-square"]},"input-latin-uppercase":{"a":"Input Latin Uppercase","b":"1F520","j":["ABCD","input","latin","letters","uppercase","alphabet","words","blue-square"]},"input-latin-lowercase":{"a":"Input Latin Lowercase","b":"1F521","j":["abcd","input","latin","letters","lowercase","blue-square","alphabet"]},"input-numbers":{"a":"Input Numbers","b":"1F522","j":["1234","input","numbers","blue-square"]},"input-symbols":{"a":"Input Symbols","b":"1F523","j":["〒♪&%","input","blue-square","music","note","ampersand","percent","glyphs","characters"]},"input-latin-letters":{"a":"Input Latin Letters","b":"1F524","j":["abc","alphabet","input","latin","letters","blue-square"]},"a-button-blood-type":{"a":"A Button (Blood Type)","b":"1F170","j":["a","A button (blood type)","blood type","a_button","red-square","alphabet","letter"]},"ab-button-blood-type":{"a":"Ab Button (Blood Type)","b":"1F18E","j":["ab","AB button (blood type)","blood type","ab_button","red-square","alphabet"]},"b-button-blood-type":{"a":"B Button (Blood Type)","b":"1F171","j":["b","B button (blood type)","blood type","b_button","red-square","alphabet","letter"]},"cl-button":{"a":"Cl Button","b":"1F191","j":["cl","CL button","alphabet","words","red-square"]},"cool-button":{"a":"Cool Button","b":"1F192","j":["cool","COOL button","words","blue-square"]},"free-button":{"a":"Free Button","b":"1F193","j":["free","FREE button","blue-square","words"]},"information":{"a":"Information","b":"2139","j":["i","blue-square","alphabet","letter"]},"id-button":{"a":"Id Button","b":"1F194","j":["id","ID button","identity","purple-square","words"]},"circled-m":{"a":"Circled M","b":"24C2","j":["circle","circled M","m","alphabet","blue-circle","letter"]},"new-button":{"a":"New Button","b":"1F195","j":["new","NEW button","blue-square","words","start"]},"ng-button":{"a":"Ng Button","b":"1F196","j":["ng","NG button","blue-square","words","shape","icon"]},"o-button-blood-type":{"a":"O Button (Blood Type)","b":"1F17E","j":["blood type","o","O button (blood type)","o_button","alphabet","red-square","letter"]},"ok-button":{"a":"Ok Button","b":"1F197","j":["OK","OK button","good","agree","yes","blue-square"]},"p-button":{"a":"P Button","b":"1F17F","j":["P button","parking","cars","blue-square","alphabet","letter"]},"sos-button":{"a":"Sos Button","b":"1F198","j":["help","sos","SOS button","red-square","words","emergency","911"]},"up-button":{"a":"Up! Button","b":"1F199","j":["mark","up","UP! button","blue-square","above","high"]},"vs-button":{"a":"Vs Button","b":"1F19A","j":["versus","vs","VS button","words","orange-square"]},"japanese-here-button":{"a":"Japanese “Here” Button","b":"1F201","j":["“here”","Japanese","Japanese “here” button","katakana","ココ","blue-square","here","japanese","destination"]},"japanese-service-charge-button":{"a":"Japanese “Service Charge” Button","b":"1F202","j":["“service charge”","Japanese","Japanese “service charge” button","katakana","サ","japanese","blue-square"]},"japanese-monthly-amount-button":{"a":"Japanese “Monthly Amount” Button","b":"1F237","j":["“monthly amount”","ideograph","Japanese","Japanese “monthly amount” button","月","chinese","month","moon","japanese","orange-square","kanji"]},"japanese-not-free-of-charge-button":{"a":"Japanese “Not Free of Charge” Button","b":"1F236","j":["“not free of charge”","ideograph","Japanese","Japanese “not free of charge” button","有","orange-square","chinese","have","kanji"]},"japanese-reserved-button":{"a":"Japanese “Reserved” Button","b":"1F22F","j":["“reserved”","ideograph","Japanese","Japanese “reserved” button","指","chinese","point","green-square","kanji"]},"japanese-bargain-button":{"a":"Japanese “Bargain” Button","b":"1F250","j":["“bargain”","ideograph","Japanese","Japanese “bargain” button","得","chinese","kanji","obtain","get","circle"]},"japanese-discount-button":{"a":"Japanese “Discount” Button","b":"1F239","j":["“discount”","ideograph","Japanese","Japanese “discount” button","割","cut","divide","chinese","kanji","pink-square"]},"japanese-free-of-charge-button":{"a":"Japanese “Free of Charge” Button","b":"1F21A","j":["“free of charge”","ideograph","Japanese","Japanese “free of charge” button","無","nothing","chinese","kanji","japanese","orange-square"]},"japanese-prohibited-button":{"a":"Japanese “Prohibited” Button","b":"1F232","j":["“prohibited”","ideograph","Japanese","Japanese “prohibited” button","禁","kanji","japanese","chinese","forbidden","limit","restricted","red-square"]},"japanese-acceptable-button":{"a":"Japanese “Acceptable” Button","b":"1F251","j":["“acceptable”","ideograph","Japanese","Japanese “acceptable” button","可","ok","good","chinese","kanji","agree","yes","orange-circle"]},"japanese-application-button":{"a":"Japanese “Application” Button","b":"1F238","j":["“application”","ideograph","Japanese","Japanese “application” button","申","chinese","japanese","kanji","orange-square"]},"japanese-passing-grade-button":{"a":"Japanese “Passing Grade” Button","b":"1F234","j":["“passing grade”","ideograph","Japanese","Japanese “passing grade” button","合","japanese","chinese","join","kanji","red-square"]},"japanese-vacancy-button":{"a":"Japanese “Vacancy” Button","b":"1F233","j":["“vacancy”","ideograph","Japanese","Japanese “vacancy” button","空","kanji","japanese","chinese","empty","sky","blue-square"]},"japanese-congratulations-button":{"a":"Japanese “Congratulations” Button","b":"3297","j":["“congratulations”","ideograph","Japanese","Japanese “congratulations” button","祝","chinese","kanji","japanese","red-circle"]},"japanese-secret-button":{"a":"Japanese “Secret” Button","b":"3299","j":["“secret”","ideograph","Japanese","Japanese “secret” button","秘","privacy","chinese","sshh","kanji","red-circle"]},"japanese-open-for-business-button":{"a":"Japanese “Open for Business” Button","b":"1F23A","j":["“open for business”","ideograph","Japanese","Japanese “open for business” button","営","japanese","opening hours","orange-square"]},"japanese-no-vacancy-button":{"a":"Japanese “No Vacancy” Button","b":"1F235","j":["“no vacancy”","ideograph","Japanese","Japanese “no vacancy” button","満","full","chinese","japanese","red-square","kanji"]},"red-circle":{"a":"Red Circle","b":"1F534","j":["circle","geometric","red","shape","error","danger"]},"orange-circle":{"a":"Orange Circle","b":"1F7E0","j":["circle","orange","round"]},"yellow-circle":{"a":"Yellow Circle","b":"1F7E1","j":["circle","yellow","round"]},"green-circle":{"a":"Green Circle","b":"1F7E2","j":["circle","green","round"]},"blue-circle":{"a":"Blue Circle","b":"1F535","j":["blue","circle","geometric","shape","icon","button"]},"purple-circle":{"a":"Purple Circle","b":"1F7E3","j":["circle","purple","round"]},"brown-circle":{"a":"Brown Circle","b":"1F7E4","j":["brown","circle","round"]},"black-circle":{"a":"Black Circle","b":"26AB","j":["circle","geometric","shape","button","round"]},"white-circle":{"a":"White Circle","b":"26AA","j":["circle","geometric","shape","round"]},"red-square":{"a":"Red Square","b":"1F7E5","j":["red","square"]},"orange-square":{"a":"Orange Square","b":"1F7E7","j":["orange","square"]},"yellow-square":{"a":"Yellow Square","b":"1F7E8","j":["square","yellow"]},"green-square":{"a":"Green Square","b":"1F7E9","j":["green","square"]},"blue-square":{"a":"Blue Square","b":"1F7E6","j":["blue","square"]},"purple-square":{"a":"Purple Square","b":"1F7EA","j":["purple","square"]},"brown-square":{"a":"Brown Square","b":"1F7EB","j":["brown","square"]},"black-large-square":{"a":"Black Large Square","b":"2B1B","j":["geometric","square","shape","icon","button"]},"white-large-square":{"a":"White Large Square","b":"2B1C","j":["geometric","square","shape","icon","stone","button"]},"black-medium-square":{"a":"Black Medium Square","b":"25FC","j":["geometric","square","shape","button","icon"]},"white-medium-square":{"a":"White Medium Square","b":"25FB","j":["geometric","square","shape","stone","icon"]},"black-mediumsmall-square":{"a":"Black Medium-Small Square","b":"25FE","j":["black medium-small square","geometric","square","black_medium_small_square","icon","shape","button"]},"white-mediumsmall-square":{"a":"White Medium-Small Square","b":"25FD","j":["geometric","square","white medium-small square","white_medium_small_square","shape","stone","icon","button"]},"black-small-square":{"a":"Black Small Square","b":"25AA","j":["geometric","square","shape","icon"]},"white-small-square":{"a":"White Small Square","b":"25AB","j":["geometric","square","shape","icon"]},"large-orange-diamond":{"a":"Large Orange Diamond","b":"1F536","j":["diamond","geometric","orange","shape","jewel","gem"]},"large-blue-diamond":{"a":"Large Blue Diamond","b":"1F537","j":["blue","diamond","geometric","shape","jewel","gem"]},"small-orange-diamond":{"a":"Small Orange Diamond","b":"1F538","j":["diamond","geometric","orange","shape","jewel","gem"]},"small-blue-diamond":{"a":"Small Blue Diamond","b":"1F539","j":["blue","diamond","geometric","shape","jewel","gem"]},"red-triangle-pointed-up":{"a":"Red Triangle Pointed Up","b":"1F53A","j":["geometric","red","shape","direction","up","top"]},"red-triangle-pointed-down":{"a":"Red Triangle Pointed Down","b":"1F53B","j":["down","geometric","red","shape","direction","bottom"]},"diamond-with-a-dot":{"a":"Diamond with a Dot","b":"1F4A0","j":["comic","diamond","geometric","inside","jewel","blue","gem","crystal","fancy"]},"radio-button":{"a":"Radio Button","b":"1F518","j":["button","geometric","radio","input","old","music","circle"]},"white-square-button":{"a":"White Square Button","b":"1F533","j":["button","geometric","outlined","square","shape","input"]},"black-square-button":{"a":"Black Square Button","b":"1F532","j":["button","geometric","square","shape","input","frame"]},"chequered-flag":{"a":"Chequered Flag","b":"1F3C1","j":["checkered","chequered","racing","contest","finishline","race","gokart"]},"triangular-flag":{"a":"Triangular Flag","b":"1F6A9","j":["post","mark","milestone","place"]},"crossed-flags":{"a":"Crossed Flags","b":"1F38C","j":["celebration","cross","crossed","Japanese","japanese","nation","country","border"]},"black-flag":{"a":"Black Flag","b":"1F3F4","j":["waving","pirate"]},"white-flag":{"a":"White Flag","b":"1F3F3","j":["waving","losing","loser","lost","surrender","give up","fail"]},"rainbow-flag":{"a":"Rainbow Flag","b":"1F3F3-FE0F-200D-1F308","j":["pride","rainbow","flag","gay","lgbt","glbt","queer","homosexual","lesbian","bisexual","transgender"]},"transgender-flag":{"a":"Transgender Flag","b":"1F3F3-FE0F-200D-26A7-FE0F","j":["flag","light blue","pink","transgender","white","lgbtq"]},"pirate-flag":{"a":"Pirate Flag","b":"1F3F4-200D-2620-FE0F","j":["Jolly Roger","pirate","plunder","treasure","skull","crossbones","flag","banner"]},"flag-ascension-island":{"a":"Flag: Ascension Island","b":"1F1E6-1F1E8","j":["flag"]},"flag-andorra":{"a":"Flag: Andorra","b":"1F1E6-1F1E9","j":["flag","ad","nation","country","banner","andorra"]},"flag-united-arab-emirates":{"a":"Flag: United Arab Emirates","b":"1F1E6-1F1EA","j":["flag","united","arab","emirates","nation","country","banner","united_arab_emirates"]},"flag-afghanistan":{"a":"Flag: Afghanistan","b":"1F1E6-1F1EB","j":["flag","af","nation","country","banner","afghanistan"]},"flag-antigua--barbuda":{"a":"Flag: Antigua & Barbuda","b":"1F1E6-1F1EC","j":["flag","flag_antigua_barbuda","antigua","barbuda","nation","country","banner","antigua_barbuda"]},"flag-anguilla":{"a":"Flag: Anguilla","b":"1F1E6-1F1EE","j":["flag","ai","nation","country","banner","anguilla"]},"flag-albania":{"a":"Flag: Albania","b":"1F1E6-1F1F1","j":["flag","al","nation","country","banner","albania"]},"flag-armenia":{"a":"Flag: Armenia","b":"1F1E6-1F1F2","j":["flag","am","nation","country","banner","armenia"]},"flag-angola":{"a":"Flag: Angola","b":"1F1E6-1F1F4","j":["flag","ao","nation","country","banner","angola"]},"flag-antarctica":{"a":"Flag: Antarctica","b":"1F1E6-1F1F6","j":["flag","aq","nation","country","banner","antarctica"]},"flag-argentina":{"a":"Flag: Argentina","b":"1F1E6-1F1F7","j":["flag","ar","nation","country","banner","argentina"]},"flag-american-samoa":{"a":"Flag: American Samoa","b":"1F1E6-1F1F8","j":["flag","american","ws","nation","country","banner","american_samoa"]},"flag-austria":{"a":"Flag: Austria","b":"1F1E6-1F1F9","j":["flag","at","nation","country","banner","austria"]},"flag-australia":{"a":"Flag: Australia","b":"1F1E6-1F1FA","j":["flag","au","nation","country","banner","australia"]},"flag-aruba":{"a":"Flag: Aruba","b":"1F1E6-1F1FC","j":["flag","aw","nation","country","banner","aruba"]},"flag-land-islands":{"a":"Flag: Åland Islands","b":"1F1E6-1F1FD","j":["flag","flag_aland_islands","Åland","islands","nation","country","banner","aland_islands"]},"flag-azerbaijan":{"a":"Flag: Azerbaijan","b":"1F1E6-1F1FF","j":["flag","az","nation","country","banner","azerbaijan"]},"flag-bosnia--herzegovina":{"a":"Flag: Bosnia & Herzegovina","b":"1F1E7-1F1E6","j":["flag","flag_bosnia_herzegovina","bosnia","herzegovina","nation","country","banner","bosnia_herzegovina"]},"flag-barbados":{"a":"Flag: Barbados","b":"1F1E7-1F1E7","j":["flag","bb","nation","country","banner","barbados"]},"flag-bangladesh":{"a":"Flag: Bangladesh","b":"1F1E7-1F1E9","j":["flag","bd","nation","country","banner","bangladesh"]},"flag-belgium":{"a":"Flag: Belgium","b":"1F1E7-1F1EA","j":["flag","be","nation","country","banner","belgium"]},"flag-burkina-faso":{"a":"Flag: Burkina Faso","b":"1F1E7-1F1EB","j":["flag","burkina","faso","nation","country","banner","burkina_faso"]},"flag-bulgaria":{"a":"Flag: Bulgaria","b":"1F1E7-1F1EC","j":["flag","bg","nation","country","banner","bulgaria"]},"flag-bahrain":{"a":"Flag: Bahrain","b":"1F1E7-1F1ED","j":["flag","bh","nation","country","banner","bahrain"]},"flag-burundi":{"a":"Flag: Burundi","b":"1F1E7-1F1EE","j":["flag","bi","nation","country","banner","burundi"]},"flag-benin":{"a":"Flag: Benin","b":"1F1E7-1F1EF","j":["flag","bj","nation","country","banner","benin"]},"flag-st-barthlemy":{"a":"Flag: St. Barthélemy","b":"1F1E7-1F1F1","j":["flag","flag_st_barthelemy","saint","barthélemy","nation","country","banner","st_barthelemy"]},"flag-bermuda":{"a":"Flag: Bermuda","b":"1F1E7-1F1F2","j":["flag","bm","nation","country","banner","bermuda"]},"flag-brunei":{"a":"Flag: Brunei","b":"1F1E7-1F1F3","j":["flag","bn","darussalam","nation","country","banner","brunei"]},"flag-bolivia":{"a":"Flag: Bolivia","b":"1F1E7-1F1F4","j":["flag","bo","nation","country","banner","bolivia"]},"flag-caribbean-netherlands":{"a":"Flag: Caribbean Netherlands","b":"1F1E7-1F1F6","j":["flag","bonaire","nation","country","banner","caribbean_netherlands"]},"flag-brazil":{"a":"Flag: Brazil","b":"1F1E7-1F1F7","j":["flag","br","nation","country","banner","brazil"]},"flag-bahamas":{"a":"Flag: Bahamas","b":"1F1E7-1F1F8","j":["flag","bs","nation","country","banner","bahamas"]},"flag-bhutan":{"a":"Flag: Bhutan","b":"1F1E7-1F1F9","j":["flag","bt","nation","country","banner","bhutan"]},"flag-bouvet-island":{"a":"Flag: Bouvet Island","b":"1F1E7-1F1FB","j":["flag","norway"]},"flag-botswana":{"a":"Flag: Botswana","b":"1F1E7-1F1FC","j":["flag","bw","nation","country","banner","botswana"]},"flag-belarus":{"a":"Flag: Belarus","b":"1F1E7-1F1FE","j":["flag","by","nation","country","banner","belarus"]},"flag-belize":{"a":"Flag: Belize","b":"1F1E7-1F1FF","j":["flag","bz","nation","country","banner","belize"]},"flag-canada":{"a":"Flag: Canada","b":"1F1E8-1F1E6","j":["flag","ca","nation","country","banner","canada"]},"flag-cocos-keeling-islands":{"a":"Flag: Cocos (Keeling) Islands","b":"1F1E8-1F1E8","j":["flag","flag_cocos_islands","cocos","keeling","islands","nation","country","banner","cocos_islands"]},"flag-congo--kinshasa":{"a":"Flag: Congo - Kinshasa","b":"1F1E8-1F1E9","j":["flag","flag_congo_kinshasa","congo","democratic","republic","nation","country","banner","congo_kinshasa"]},"flag-central-african-republic":{"a":"Flag: Central African Republic","b":"1F1E8-1F1EB","j":["flag","central","african","republic","nation","country","banner","central_african_republic"]},"flag-congo--brazzaville":{"a":"Flag: Congo - Brazzaville","b":"1F1E8-1F1EC","j":["flag","flag_congo_brazzaville","congo","nation","country","banner","congo_brazzaville"]},"flag-switzerland":{"a":"Flag: Switzerland","b":"1F1E8-1F1ED","j":["flag","ch","nation","country","banner","switzerland"]},"flag-cte-divoire":{"a":"Flag: Côte D’Ivoire","b":"1F1E8-1F1EE","j":["flag","flag_cote_d_ivoire","ivory","coast","nation","country","banner","cote_d_ivoire"]},"flag-cook-islands":{"a":"Flag: Cook Islands","b":"1F1E8-1F1F0","j":["flag","cook","islands","nation","country","banner","cook_islands"]},"flag-chile":{"a":"Flag: Chile","b":"1F1E8-1F1F1","j":["flag","nation","country","banner","chile"]},"flag-cameroon":{"a":"Flag: Cameroon","b":"1F1E8-1F1F2","j":["flag","cm","nation","country","banner","cameroon"]},"flag-china":{"a":"Flag: China","b":"1F1E8-1F1F3","j":["flag","china","chinese","prc","country","nation","banner"]},"flag-colombia":{"a":"Flag: Colombia","b":"1F1E8-1F1F4","j":["flag","co","nation","country","banner","colombia"]},"flag-clipperton-island":{"a":"Flag: Clipperton Island","b":"1F1E8-1F1F5","j":["flag"]},"flag-costa-rica":{"a":"Flag: Costa Rica","b":"1F1E8-1F1F7","j":["flag","costa","rica","nation","country","banner","costa_rica"]},"flag-cuba":{"a":"Flag: Cuba","b":"1F1E8-1F1FA","j":["flag","cu","nation","country","banner","cuba"]},"flag-cape-verde":{"a":"Flag: Cape Verde","b":"1F1E8-1F1FB","j":["flag","cabo","verde","nation","country","banner","cape_verde"]},"flag-curaao":{"a":"Flag: Curaçao","b":"1F1E8-1F1FC","j":["flag","flag_curacao","curaçao","nation","country","banner","curacao"]},"flag-christmas-island":{"a":"Flag: Christmas Island","b":"1F1E8-1F1FD","j":["flag","christmas","island","nation","country","banner","christmas_island"]},"flag-cyprus":{"a":"Flag: Cyprus","b":"1F1E8-1F1FE","j":["flag","cy","nation","country","banner","cyprus"]},"flag-czechia":{"a":"Flag: Czechia","b":"1F1E8-1F1FF","j":["flag","cz","nation","country","banner","czechia"]},"flag-germany":{"a":"Flag: Germany","b":"1F1E9-1F1EA","j":["flag","german","nation","country","banner","germany"]},"flag-diego-garcia":{"a":"Flag: Diego Garcia","b":"1F1E9-1F1EC","j":["flag"]},"flag-djibouti":{"a":"Flag: Djibouti","b":"1F1E9-1F1EF","j":["flag","dj","nation","country","banner","djibouti"]},"flag-denmark":{"a":"Flag: Denmark","b":"1F1E9-1F1F0","j":["flag","dk","nation","country","banner","denmark"]},"flag-dominica":{"a":"Flag: Dominica","b":"1F1E9-1F1F2","j":["flag","dm","nation","country","banner","dominica"]},"flag-dominican-republic":{"a":"Flag: Dominican Republic","b":"1F1E9-1F1F4","j":["flag","dominican","republic","nation","country","banner","dominican_republic"]},"flag-algeria":{"a":"Flag: Algeria","b":"1F1E9-1F1FF","j":["flag","dz","nation","country","banner","algeria"]},"flag-ceuta--melilla":{"a":"Flag: Ceuta & Melilla","b":"1F1EA-1F1E6","j":["flag","flag_ceuta_melilla"]},"flag-ecuador":{"a":"Flag: Ecuador","b":"1F1EA-1F1E8","j":["flag","ec","nation","country","banner","ecuador"]},"flag-estonia":{"a":"Flag: Estonia","b":"1F1EA-1F1EA","j":["flag","ee","nation","country","banner","estonia"]},"flag-egypt":{"a":"Flag: Egypt","b":"1F1EA-1F1EC","j":["flag","eg","nation","country","banner","egypt"]},"flag-western-sahara":{"a":"Flag: Western Sahara","b":"1F1EA-1F1ED","j":["flag","western","sahara","nation","country","banner","western_sahara"]},"flag-eritrea":{"a":"Flag: Eritrea","b":"1F1EA-1F1F7","j":["flag","er","nation","country","banner","eritrea"]},"flag-spain":{"a":"Flag: Spain","b":"1F1EA-1F1F8","j":["flag","spain","nation","country","banner"]},"flag-ethiopia":{"a":"Flag: Ethiopia","b":"1F1EA-1F1F9","j":["flag","et","nation","country","banner","ethiopia"]},"flag-european-union":{"a":"Flag: European Union","b":"1F1EA-1F1FA","j":["flag","european","union","banner"]},"flag-finland":{"a":"Flag: Finland","b":"1F1EB-1F1EE","j":["flag","fi","nation","country","banner","finland"]},"flag-fiji":{"a":"Flag: Fiji","b":"1F1EB-1F1EF","j":["flag","fj","nation","country","banner","fiji"]},"flag-falkland-islands":{"a":"Flag: Falkland Islands","b":"1F1EB-1F1F0","j":["flag","falkland","islands","malvinas","nation","country","banner","falkland_islands"]},"flag-micronesia":{"a":"Flag: Micronesia","b":"1F1EB-1F1F2","j":["flag","micronesia","federated","states","nation","country","banner"]},"flag-faroe-islands":{"a":"Flag: Faroe Islands","b":"1F1EB-1F1F4","j":["flag","faroe","islands","nation","country","banner","faroe_islands"]},"flag-france":{"a":"Flag: France","b":"1F1EB-1F1F7","j":["flag","banner","nation","france","french","country"]},"flag-gabon":{"a":"Flag: Gabon","b":"1F1EC-1F1E6","j":["flag","ga","nation","country","banner","gabon"]},"flag-united-kingdom":{"a":"Flag: United Kingdom","b":"1F1EC-1F1E7","j":["flag","united","kingdom","great","britain","northern","ireland","nation","country","banner","british","UK","english","england","union jack","united_kingdom"]},"flag-grenada":{"a":"Flag: Grenada","b":"1F1EC-1F1E9","j":["flag","gd","nation","country","banner","grenada"]},"flag-georgia":{"a":"Flag: Georgia","b":"1F1EC-1F1EA","j":["flag","ge","nation","country","banner","georgia"]},"flag-french-guiana":{"a":"Flag: French Guiana","b":"1F1EC-1F1EB","j":["flag","french","guiana","nation","country","banner","french_guiana"]},"flag-guernsey":{"a":"Flag: Guernsey","b":"1F1EC-1F1EC","j":["flag","gg","nation","country","banner","guernsey"]},"flag-ghana":{"a":"Flag: Ghana","b":"1F1EC-1F1ED","j":["flag","gh","nation","country","banner","ghana"]},"flag-gibraltar":{"a":"Flag: Gibraltar","b":"1F1EC-1F1EE","j":["flag","gi","nation","country","banner","gibraltar"]},"flag-greenland":{"a":"Flag: Greenland","b":"1F1EC-1F1F1","j":["flag","gl","nation","country","banner","greenland"]},"flag-gambia":{"a":"Flag: Gambia","b":"1F1EC-1F1F2","j":["flag","gm","nation","country","banner","gambia"]},"flag-guinea":{"a":"Flag: Guinea","b":"1F1EC-1F1F3","j":["flag","gn","nation","country","banner","guinea"]},"flag-guadeloupe":{"a":"Flag: Guadeloupe","b":"1F1EC-1F1F5","j":["flag","gp","nation","country","banner","guadeloupe"]},"flag-equatorial-guinea":{"a":"Flag: Equatorial Guinea","b":"1F1EC-1F1F6","j":["flag","equatorial","gn","nation","country","banner","equatorial_guinea"]},"flag-greece":{"a":"Flag: Greece","b":"1F1EC-1F1F7","j":["flag","gr","nation","country","banner","greece"]},"flag-south-georgia--south-sandwich-islands":{"a":"Flag: South Georgia & South Sandwich Islands","b":"1F1EC-1F1F8","j":["flag","flag_south_georgia_south_sandwich_islands","south","georgia","sandwich","islands","nation","country","banner","south_georgia_south_sandwich_islands"]},"flag-guatemala":{"a":"Flag: Guatemala","b":"1F1EC-1F1F9","j":["flag","gt","nation","country","banner","guatemala"]},"flag-guam":{"a":"Flag: Guam","b":"1F1EC-1F1FA","j":["flag","gu","nation","country","banner","guam"]},"flag-guineabissau":{"a":"Flag: Guinea-Bissau","b":"1F1EC-1F1FC","j":["flag","flag_guinea_bissau","gw","bissau","nation","country","banner","guinea_bissau"]},"flag-guyana":{"a":"Flag: Guyana","b":"1F1EC-1F1FE","j":["flag","gy","nation","country","banner","guyana"]},"flag-hong-kong-sar-china":{"a":"Flag: Hong Kong Sar China","b":"1F1ED-1F1F0","j":["flag","hong","kong","nation","country","banner","hong_kong_sar_china"]},"flag-heard--mcdonald-islands":{"a":"Flag: Heard & Mcdonald Islands","b":"1F1ED-1F1F2","j":["flag","flag_heard_mcdonald_islands"]},"flag-honduras":{"a":"Flag: Honduras","b":"1F1ED-1F1F3","j":["flag","hn","nation","country","banner","honduras"]},"flag-croatia":{"a":"Flag: Croatia","b":"1F1ED-1F1F7","j":["flag","hr","nation","country","banner","croatia"]},"flag-haiti":{"a":"Flag: Haiti","b":"1F1ED-1F1F9","j":["flag","ht","nation","country","banner","haiti"]},"flag-hungary":{"a":"Flag: Hungary","b":"1F1ED-1F1FA","j":["flag","hu","nation","country","banner","hungary"]},"flag-canary-islands":{"a":"Flag: Canary Islands","b":"1F1EE-1F1E8","j":["flag","canary","islands","nation","country","banner","canary_islands"]},"flag-indonesia":{"a":"Flag: Indonesia","b":"1F1EE-1F1E9","j":["flag","nation","country","banner","indonesia"]},"flag-ireland":{"a":"Flag: Ireland","b":"1F1EE-1F1EA","j":["flag","ie","nation","country","banner","ireland"]},"flag-israel":{"a":"Flag: Israel","b":"1F1EE-1F1F1","j":["flag","il","nation","country","banner","israel"]},"flag-isle-of-man":{"a":"Flag: Isle of Man","b":"1F1EE-1F1F2","j":["flag","isle","man","nation","country","banner","isle_of_man"]},"flag-india":{"a":"Flag: India","b":"1F1EE-1F1F3","j":["flag","in","nation","country","banner","india"]},"flag-british-indian-ocean-territory":{"a":"Flag: British Indian Ocean Territory","b":"1F1EE-1F1F4","j":["flag","british","indian","ocean","territory","nation","country","banner","british_indian_ocean_territory"]},"flag-iraq":{"a":"Flag: Iraq","b":"1F1EE-1F1F6","j":["flag","iq","nation","country","banner","iraq"]},"flag-iran":{"a":"Flag: Iran","b":"1F1EE-1F1F7","j":["flag","iran","islamic","republic","nation","country","banner"]},"flag-iceland":{"a":"Flag: Iceland","b":"1F1EE-1F1F8","j":["flag","is","nation","country","banner","iceland"]},"flag-italy":{"a":"Flag: Italy","b":"1F1EE-1F1F9","j":["flag","italy","nation","country","banner"]},"flag-jersey":{"a":"Flag: Jersey","b":"1F1EF-1F1EA","j":["flag","je","nation","country","banner","jersey"]},"flag-jamaica":{"a":"Flag: Jamaica","b":"1F1EF-1F1F2","j":["flag","jm","nation","country","banner","jamaica"]},"flag-jordan":{"a":"Flag: Jordan","b":"1F1EF-1F1F4","j":["flag","jo","nation","country","banner","jordan"]},"flag-japan":{"a":"Flag: Japan","b":"1F1EF-1F1F5","j":["flag","japanese","nation","country","banner","japan"]},"flag-kenya":{"a":"Flag: Kenya","b":"1F1F0-1F1EA","j":["flag","ke","nation","country","banner","kenya"]},"flag-kyrgyzstan":{"a":"Flag: Kyrgyzstan","b":"1F1F0-1F1EC","j":["flag","kg","nation","country","banner","kyrgyzstan"]},"flag-cambodia":{"a":"Flag: Cambodia","b":"1F1F0-1F1ED","j":["flag","kh","nation","country","banner","cambodia"]},"flag-kiribati":{"a":"Flag: Kiribati","b":"1F1F0-1F1EE","j":["flag","ki","nation","country","banner","kiribati"]},"flag-comoros":{"a":"Flag: Comoros","b":"1F1F0-1F1F2","j":["flag","km","nation","country","banner","comoros"]},"flag-st-kitts--nevis":{"a":"Flag: St. Kitts & Nevis","b":"1F1F0-1F1F3","j":["flag","flag_st_kitts_nevis","saint","kitts","nevis","nation","country","banner","st_kitts_nevis"]},"flag-north-korea":{"a":"Flag: North Korea","b":"1F1F0-1F1F5","j":["flag","north","korea","nation","country","banner","north_korea"]},"flag-south-korea":{"a":"Flag: South Korea","b":"1F1F0-1F1F7","j":["flag","south","korea","nation","country","banner","south_korea"]},"flag-kuwait":{"a":"Flag: Kuwait","b":"1F1F0-1F1FC","j":["flag","kw","nation","country","banner","kuwait"]},"flag-cayman-islands":{"a":"Flag: Cayman Islands","b":"1F1F0-1F1FE","j":["flag","cayman","islands","nation","country","banner","cayman_islands"]},"flag-kazakhstan":{"a":"Flag: Kazakhstan","b":"1F1F0-1F1FF","j":["flag","kz","nation","country","banner","kazakhstan"]},"flag-laos":{"a":"Flag: Laos","b":"1F1F1-1F1E6","j":["flag","lao","democratic","republic","nation","country","banner","laos"]},"flag-lebanon":{"a":"Flag: Lebanon","b":"1F1F1-1F1E7","j":["flag","lb","nation","country","banner","lebanon"]},"flag-st-lucia":{"a":"Flag: St. Lucia","b":"1F1F1-1F1E8","j":["flag","saint","lucia","nation","country","banner","st_lucia"]},"flag-liechtenstein":{"a":"Flag: Liechtenstein","b":"1F1F1-1F1EE","j":["flag","li","nation","country","banner","liechtenstein"]},"flag-sri-lanka":{"a":"Flag: Sri Lanka","b":"1F1F1-1F1F0","j":["flag","sri","lanka","nation","country","banner","sri_lanka"]},"flag-liberia":{"a":"Flag: Liberia","b":"1F1F1-1F1F7","j":["flag","lr","nation","country","banner","liberia"]},"flag-lesotho":{"a":"Flag: Lesotho","b":"1F1F1-1F1F8","j":["flag","ls","nation","country","banner","lesotho"]},"flag-lithuania":{"a":"Flag: Lithuania","b":"1F1F1-1F1F9","j":["flag","lt","nation","country","banner","lithuania"]},"flag-luxembourg":{"a":"Flag: Luxembourg","b":"1F1F1-1F1FA","j":["flag","lu","nation","country","banner","luxembourg"]},"flag-latvia":{"a":"Flag: Latvia","b":"1F1F1-1F1FB","j":["flag","lv","nation","country","banner","latvia"]},"flag-libya":{"a":"Flag: Libya","b":"1F1F1-1F1FE","j":["flag","ly","nation","country","banner","libya"]},"flag-morocco":{"a":"Flag: Morocco","b":"1F1F2-1F1E6","j":["flag","ma","nation","country","banner","morocco"]},"flag-monaco":{"a":"Flag: Monaco","b":"1F1F2-1F1E8","j":["flag","mc","nation","country","banner","monaco"]},"flag-moldova":{"a":"Flag: Moldova","b":"1F1F2-1F1E9","j":["flag","moldova","republic","nation","country","banner"]},"flag-montenegro":{"a":"Flag: Montenegro","b":"1F1F2-1F1EA","j":["flag","me","nation","country","banner","montenegro"]},"flag-st-martin":{"a":"Flag: St. Martin","b":"1F1F2-1F1EB","j":["flag"]},"flag-madagascar":{"a":"Flag: Madagascar","b":"1F1F2-1F1EC","j":["flag","mg","nation","country","banner","madagascar"]},"flag-marshall-islands":{"a":"Flag: Marshall Islands","b":"1F1F2-1F1ED","j":["flag","marshall","islands","nation","country","banner","marshall_islands"]},"flag-north-macedonia":{"a":"Flag: North Macedonia","b":"1F1F2-1F1F0","j":["flag","macedonia","nation","country","banner","north_macedonia"]},"flag-mali":{"a":"Flag: Mali","b":"1F1F2-1F1F1","j":["flag","ml","nation","country","banner","mali"]},"flag-myanmar-burma":{"a":"Flag: Myanmar (Burma)","b":"1F1F2-1F1F2","j":["flag","flag_myanmar","mm","nation","country","banner","myanmar"]},"flag-mongolia":{"a":"Flag: Mongolia","b":"1F1F2-1F1F3","j":["flag","mn","nation","country","banner","mongolia"]},"flag-macao-sar-china":{"a":"Flag: Macao Sar China","b":"1F1F2-1F1F4","j":["flag","macao","nation","country","banner","macao_sar_china"]},"flag-northern-mariana-islands":{"a":"Flag: Northern Mariana Islands","b":"1F1F2-1F1F5","j":["flag","northern","mariana","islands","nation","country","banner","northern_mariana_islands"]},"flag-martinique":{"a":"Flag: Martinique","b":"1F1F2-1F1F6","j":["flag","mq","nation","country","banner","martinique"]},"flag-mauritania":{"a":"Flag: Mauritania","b":"1F1F2-1F1F7","j":["flag","mr","nation","country","banner","mauritania"]},"flag-montserrat":{"a":"Flag: Montserrat","b":"1F1F2-1F1F8","j":["flag","ms","nation","country","banner","montserrat"]},"flag-malta":{"a":"Flag: Malta","b":"1F1F2-1F1F9","j":["flag","mt","nation","country","banner","malta"]},"flag-mauritius":{"a":"Flag: Mauritius","b":"1F1F2-1F1FA","j":["flag","mu","nation","country","banner","mauritius"]},"flag-maldives":{"a":"Flag: Maldives","b":"1F1F2-1F1FB","j":["flag","mv","nation","country","banner","maldives"]},"flag-malawi":{"a":"Flag: Malawi","b":"1F1F2-1F1FC","j":["flag","mw","nation","country","banner","malawi"]},"flag-mexico":{"a":"Flag: Mexico","b":"1F1F2-1F1FD","j":["flag","mx","nation","country","banner","mexico"]},"flag-malaysia":{"a":"Flag: Malaysia","b":"1F1F2-1F1FE","j":["flag","my","nation","country","banner","malaysia"]},"flag-mozambique":{"a":"Flag: Mozambique","b":"1F1F2-1F1FF","j":["flag","mz","nation","country","banner","mozambique"]},"flag-namibia":{"a":"Flag: Namibia","b":"1F1F3-1F1E6","j":["flag","na","nation","country","banner","namibia"]},"flag-new-caledonia":{"a":"Flag: New Caledonia","b":"1F1F3-1F1E8","j":["flag","new","caledonia","nation","country","banner","new_caledonia"]},"flag-niger":{"a":"Flag: Niger","b":"1F1F3-1F1EA","j":["flag","ne","nation","country","banner","niger"]},"flag-norfolk-island":{"a":"Flag: Norfolk Island","b":"1F1F3-1F1EB","j":["flag","norfolk","island","nation","country","banner","norfolk_island"]},"flag-nigeria":{"a":"Flag: Nigeria","b":"1F1F3-1F1EC","j":["flag","nation","country","banner","nigeria"]},"flag-nicaragua":{"a":"Flag: Nicaragua","b":"1F1F3-1F1EE","j":["flag","ni","nation","country","banner","nicaragua"]},"flag-netherlands":{"a":"Flag: Netherlands","b":"1F1F3-1F1F1","j":["flag","nl","nation","country","banner","netherlands"]},"flag-norway":{"a":"Flag: Norway","b":"1F1F3-1F1F4","j":["flag","no","nation","country","banner","norway"]},"flag-nepal":{"a":"Flag: Nepal","b":"1F1F3-1F1F5","j":["flag","np","nation","country","banner","nepal"]},"flag-nauru":{"a":"Flag: Nauru","b":"1F1F3-1F1F7","j":["flag","nr","nation","country","banner","nauru"]},"flag-niue":{"a":"Flag: Niue","b":"1F1F3-1F1FA","j":["flag","nu","nation","country","banner","niue"]},"flag-new-zealand":{"a":"Flag: New Zealand","b":"1F1F3-1F1FF","j":["flag","new","zealand","nation","country","banner","new_zealand"]},"flag-oman":{"a":"Flag: Oman","b":"1F1F4-1F1F2","j":["flag","om_symbol","nation","country","banner","oman"]},"flag-panama":{"a":"Flag: Panama","b":"1F1F5-1F1E6","j":["flag","pa","nation","country","banner","panama"]},"flag-peru":{"a":"Flag: Peru","b":"1F1F5-1F1EA","j":["flag","pe","nation","country","banner","peru"]},"flag-french-polynesia":{"a":"Flag: French Polynesia","b":"1F1F5-1F1EB","j":["flag","french","polynesia","nation","country","banner","french_polynesia"]},"flag-papua-new-guinea":{"a":"Flag: Papua New Guinea","b":"1F1F5-1F1EC","j":["flag","papua","new","guinea","nation","country","banner","papua_new_guinea"]},"flag-philippines":{"a":"Flag: Philippines","b":"1F1F5-1F1ED","j":["flag","ph","nation","country","banner","philippines"]},"flag-pakistan":{"a":"Flag: Pakistan","b":"1F1F5-1F1F0","j":["flag","pk","nation","country","banner","pakistan"]},"flag-poland":{"a":"Flag: Poland","b":"1F1F5-1F1F1","j":["flag","pl","nation","country","banner","poland"]},"flag-st-pierre--miquelon":{"a":"Flag: St. Pierre & Miquelon","b":"1F1F5-1F1F2","j":["flag","flag_st_pierre_miquelon","saint","pierre","miquelon","nation","country","banner","st_pierre_miquelon"]},"flag-pitcairn-islands":{"a":"Flag: Pitcairn Islands","b":"1F1F5-1F1F3","j":["flag","pitcairn","nation","country","banner","pitcairn_islands"]},"flag-puerto-rico":{"a":"Flag: Puerto Rico","b":"1F1F5-1F1F7","j":["flag","puerto","rico","nation","country","banner","puerto_rico"]},"flag-palestinian-territories":{"a":"Flag: Palestinian Territories","b":"1F1F5-1F1F8","j":["flag","palestine","palestinian","territories","nation","country","banner","palestinian_territories"]},"flag-portugal":{"a":"Flag: Portugal","b":"1F1F5-1F1F9","j":["flag","pt","nation","country","banner","portugal"]},"flag-palau":{"a":"Flag: Palau","b":"1F1F5-1F1FC","j":["flag","pw","nation","country","banner","palau"]},"flag-paraguay":{"a":"Flag: Paraguay","b":"1F1F5-1F1FE","j":["flag","py","nation","country","banner","paraguay"]},"flag-qatar":{"a":"Flag: Qatar","b":"1F1F6-1F1E6","j":["flag","qa","nation","country","banner","qatar"]},"flag-runion":{"a":"Flag: Réunion","b":"1F1F7-1F1EA","j":["flag","flag_reunion","réunion","nation","country","banner","reunion"]},"flag-romania":{"a":"Flag: Romania","b":"1F1F7-1F1F4","j":["flag","ro","nation","country","banner","romania"]},"flag-serbia":{"a":"Flag: Serbia","b":"1F1F7-1F1F8","j":["flag","rs","nation","country","banner","serbia"]},"flag-russia":{"a":"Flag: Russia","b":"1F1F7-1F1FA","j":["flag","russian","federation","nation","country","banner","russia"]},"flag-rwanda":{"a":"Flag: Rwanda","b":"1F1F7-1F1FC","j":["flag","rw","nation","country","banner","rwanda"]},"flag-saudi-arabia":{"a":"Flag: Saudi Arabia","b":"1F1F8-1F1E6","j":["flag","nation","country","banner","saudi_arabia"]},"flag-solomon-islands":{"a":"Flag: Solomon Islands","b":"1F1F8-1F1E7","j":["flag","solomon","islands","nation","country","banner","solomon_islands"]},"flag-seychelles":{"a":"Flag: Seychelles","b":"1F1F8-1F1E8","j":["flag","sc","nation","country","banner","seychelles"]},"flag-sudan":{"a":"Flag: Sudan","b":"1F1F8-1F1E9","j":["flag","sd","nation","country","banner","sudan"]},"flag-sweden":{"a":"Flag: Sweden","b":"1F1F8-1F1EA","j":["flag","se","nation","country","banner","sweden"]},"flag-singapore":{"a":"Flag: Singapore","b":"1F1F8-1F1EC","j":["flag","sg","nation","country","banner","singapore"]},"flag-st-helena":{"a":"Flag: St. Helena","b":"1F1F8-1F1ED","j":["flag","saint","helena","ascension","tristan","cunha","nation","country","banner","st_helena"]},"flag-slovenia":{"a":"Flag: Slovenia","b":"1F1F8-1F1EE","j":["flag","si","nation","country","banner","slovenia"]},"flag-svalbard--jan-mayen":{"a":"Flag: Svalbard & Jan Mayen","b":"1F1F8-1F1EF","j":["flag","flag_svalbard_jan_mayen"]},"flag-slovakia":{"a":"Flag: Slovakia","b":"1F1F8-1F1F0","j":["flag","sk","nation","country","banner","slovakia"]},"flag-sierra-leone":{"a":"Flag: Sierra Leone","b":"1F1F8-1F1F1","j":["flag","sierra","leone","nation","country","banner","sierra_leone"]},"flag-san-marino":{"a":"Flag: San Marino","b":"1F1F8-1F1F2","j":["flag","san","marino","nation","country","banner","san_marino"]},"flag-senegal":{"a":"Flag: Senegal","b":"1F1F8-1F1F3","j":["flag","sn","nation","country","banner","senegal"]},"flag-somalia":{"a":"Flag: Somalia","b":"1F1F8-1F1F4","j":["flag","so","nation","country","banner","somalia"]},"flag-suriname":{"a":"Flag: Suriname","b":"1F1F8-1F1F7","j":["flag","sr","nation","country","banner","suriname"]},"flag-south-sudan":{"a":"Flag: South Sudan","b":"1F1F8-1F1F8","j":["flag","south","sd","nation","country","banner","south_sudan"]},"flag-so-tom--prncipe":{"a":"Flag: São Tomé & Príncipe","b":"1F1F8-1F1F9","j":["flag","flag_sao_tome_principe","sao","tome","principe","nation","country","banner","sao_tome_principe"]},"flag-el-salvador":{"a":"Flag: El Salvador","b":"1F1F8-1F1FB","j":["flag","el","salvador","nation","country","banner","el_salvador"]},"flag-sint-maarten":{"a":"Flag: Sint Maarten","b":"1F1F8-1F1FD","j":["flag","sint","maarten","dutch","nation","country","banner","sint_maarten"]},"flag-syria":{"a":"Flag: Syria","b":"1F1F8-1F1FE","j":["flag","syrian","arab","republic","nation","country","banner","syria"]},"flag-eswatini":{"a":"Flag: Eswatini","b":"1F1F8-1F1FF","j":["flag","sz","nation","country","banner","eswatini"]},"flag-tristan-da-cunha":{"a":"Flag: Tristan Da Cunha","b":"1F1F9-1F1E6","j":["flag"]},"flag-turks--caicos-islands":{"a":"Flag: Turks & Caicos Islands","b":"1F1F9-1F1E8","j":["flag","flag_turks_caicos_islands","turks","caicos","islands","nation","country","banner","turks_caicos_islands"]},"flag-chad":{"a":"Flag: Chad","b":"1F1F9-1F1E9","j":["flag","td","nation","country","banner","chad"]},"flag-french-southern-territories":{"a":"Flag: French Southern Territories","b":"1F1F9-1F1EB","j":["flag","french","southern","territories","nation","country","banner","french_southern_territories"]},"flag-togo":{"a":"Flag: Togo","b":"1F1F9-1F1EC","j":["flag","tg","nation","country","banner","togo"]},"flag-thailand":{"a":"Flag: Thailand","b":"1F1F9-1F1ED","j":["flag","th","nation","country","banner","thailand"]},"flag-tajikistan":{"a":"Flag: Tajikistan","b":"1F1F9-1F1EF","j":["flag","tj","nation","country","banner","tajikistan"]},"flag-tokelau":{"a":"Flag: Tokelau","b":"1F1F9-1F1F0","j":["flag","tk","nation","country","banner","tokelau"]},"flag-timorleste":{"a":"Flag: Timor-Leste","b":"1F1F9-1F1F1","j":["flag","flag_timor_leste","timor","leste","nation","country","banner","timor_leste"]},"flag-turkmenistan":{"a":"Flag: Turkmenistan","b":"1F1F9-1F1F2","j":["flag","nation","country","banner","turkmenistan"]},"flag-tunisia":{"a":"Flag: Tunisia","b":"1F1F9-1F1F3","j":["flag","tn","nation","country","banner","tunisia"]},"flag-tonga":{"a":"Flag: Tonga","b":"1F1F9-1F1F4","j":["flag","to","nation","country","banner","tonga"]},"flag-turkey":{"a":"Flag: Turkey","b":"1F1F9-1F1F7","j":["flag","turkey","nation","country","banner"]},"flag-trinidad--tobago":{"a":"Flag: Trinidad & Tobago","b":"1F1F9-1F1F9","j":["flag","flag_trinidad_tobago","trinidad","tobago","nation","country","banner","trinidad_tobago"]},"flag-tuvalu":{"a":"Flag: Tuvalu","b":"1F1F9-1F1FB","j":["flag","nation","country","banner","tuvalu"]},"flag-taiwan":{"a":"Flag: Taiwan","b":"1F1F9-1F1FC","j":["flag","tw","nation","country","banner","taiwan"]},"flag-tanzania":{"a":"Flag: Tanzania","b":"1F1F9-1F1FF","j":["flag","tanzania","united","republic","nation","country","banner"]},"flag-ukraine":{"a":"Flag: Ukraine","b":"1F1FA-1F1E6","j":["flag","ua","nation","country","banner","ukraine"]},"flag-uganda":{"a":"Flag: Uganda","b":"1F1FA-1F1EC","j":["flag","ug","nation","country","banner","uganda"]},"flag-us-outlying-islands":{"a":"Flag: U.S. Outlying Islands","b":"1F1FA-1F1F2","j":["flag","flag_u_s_outlying_islands"]},"flag-united-nations":{"a":"Flag: United Nations","b":"1F1FA-1F1F3","j":["flag","un","banner"]},"flag-united-states":{"a":"Flag: United States","b":"1F1FA-1F1F8","j":["flag","united","states","america","nation","country","banner","united_states"]},"flag-uruguay":{"a":"Flag: Uruguay","b":"1F1FA-1F1FE","j":["flag","uy","nation","country","banner","uruguay"]},"flag-uzbekistan":{"a":"Flag: Uzbekistan","b":"1F1FA-1F1FF","j":["flag","uz","nation","country","banner","uzbekistan"]},"flag-vatican-city":{"a":"Flag: Vatican City","b":"1F1FB-1F1E6","j":["flag","vatican","city","nation","country","banner","vatican_city"]},"flag-st-vincent--grenadines":{"a":"Flag: St. Vincent & Grenadines","b":"1F1FB-1F1E8","j":["flag","flag_st_vincent_grenadines","saint","vincent","grenadines","nation","country","banner","st_vincent_grenadines"]},"flag-venezuela":{"a":"Flag: Venezuela","b":"1F1FB-1F1EA","j":["flag","ve","bolivarian","republic","nation","country","banner","venezuela"]},"flag-british-virgin-islands":{"a":"Flag: British Virgin Islands","b":"1F1FB-1F1EC","j":["flag","british","virgin","islands","bvi","nation","country","banner","british_virgin_islands"]},"flag-us-virgin-islands":{"a":"Flag: U.S. Virgin Islands","b":"1F1FB-1F1EE","j":["flag","flag_u_s_virgin_islands","virgin","islands","us","nation","country","banner","u_s_virgin_islands"]},"flag-vietnam":{"a":"Flag: Vietnam","b":"1F1FB-1F1F3","j":["flag","viet","nam","nation","country","banner","vietnam"]},"flag-vanuatu":{"a":"Flag: Vanuatu","b":"1F1FB-1F1FA","j":["flag","vu","nation","country","banner","vanuatu"]},"flag-wallis--futuna":{"a":"Flag: Wallis & Futuna","b":"1F1FC-1F1EB","j":["flag","flag_wallis_futuna","wallis","futuna","nation","country","banner","wallis_futuna"]},"flag-samoa":{"a":"Flag: Samoa","b":"1F1FC-1F1F8","j":["flag","ws","nation","country","banner","samoa"]},"flag-kosovo":{"a":"Flag: Kosovo","b":"1F1FD-1F1F0","j":["flag","xk","nation","country","banner","kosovo"]},"flag-yemen":{"a":"Flag: Yemen","b":"1F1FE-1F1EA","j":["flag","ye","nation","country","banner","yemen"]},"flag-mayotte":{"a":"Flag: Mayotte","b":"1F1FE-1F1F9","j":["flag","yt","nation","country","banner","mayotte"]},"flag-south-africa":{"a":"Flag: South Africa","b":"1F1FF-1F1E6","j":["flag","south","africa","nation","country","banner","south_africa"]},"flag-zambia":{"a":"Flag: Zambia","b":"1F1FF-1F1F2","j":["flag","zm","nation","country","banner","zambia"]},"flag-zimbabwe":{"a":"Flag: Zimbabwe","b":"1F1FF-1F1FC","j":["flag","zw","nation","country","banner","zimbabwe"]},"flag-england":{"a":"Flag: England","b":"1F3F4-E0067-E0062-E0065-E006E-E0067-E007F","j":["flag","english"]},"flag-scotland":{"a":"Flag: Scotland","b":"1F3F4-E0067-E0062-E0073-E0063-E0074-E007F","j":["flag","scottish"]},"flag-wales":{"a":"Flag: Wales","b":"1F3F4-E0067-E0062-E0077-E006C-E0073-E007F","j":["flag","welsh"]}},"aliases":{}}
\ No newline at end of file

From c89a107ca69ab02c1b4c020f9e50b4ffd84f21be Mon Sep 17 00:00:00 2001
From: bmarty 
Date: Mon, 14 Feb 2022 00:05:49 +0000
Subject: [PATCH 360/581] Sync analytics plan

---
 .../plan/{Screen.kt => MobileScreen.kt}       | 201 +++++----------
 .../app/features/analytics/plan/WebScreen.kt  | 241 ++++++++++++++++++
 2 files changed, 298 insertions(+), 144 deletions(-)
 rename vector/src/main/java/im/vector/app/features/analytics/plan/{Screen.kt => MobileScreen.kt} (67%)
 create mode 100644 vector/src/main/java/im/vector/app/features/analytics/plan/WebScreen.kt

diff --git a/vector/src/main/java/im/vector/app/features/analytics/plan/Screen.kt b/vector/src/main/java/im/vector/app/features/analytics/plan/MobileScreen.kt
similarity index 67%
rename from vector/src/main/java/im/vector/app/features/analytics/plan/Screen.kt
rename to vector/src/main/java/im/vector/app/features/analytics/plan/MobileScreen.kt
index 710ae8f6f26..758a0540bfe 100644
--- a/vector/src/main/java/im/vector/app/features/analytics/plan/Screen.kt
+++ b/vector/src/main/java/im/vector/app/features/analytics/plan/MobileScreen.kt
@@ -22,9 +22,9 @@ import im.vector.app.features.analytics.itf.VectorAnalyticsScreen
 // https://github.com/matrix-org/matrix-analytics-events/
 
 /**
- * Triggered when the user changed screen
+ * Triggered when the user changed screen on Element Android/iOS
  */
-data class Screen(
+data class MobileScreen(
         /**
          * How long the screen was displayed for in milliseconds.
          */
@@ -33,6 +33,11 @@ data class Screen(
 ) : VectorAnalyticsScreen {
 
     enum class ScreenName {
+        /**
+         * The screen that displays the user's breadcrumbs.
+         */
+        Breadcrumbs,
+
         /**
          * The screen shown to create a new (non-direct) room.
          */
@@ -43,123 +48,51 @@ data class Screen(
          */
         DeactivateAccount,
 
-        /**
-         * The form for the forgot password use case
-         */
-        ForgotPassword,
-
-        /**
-         * Legacy: The screen that shows information about a specific group.
-         */
-        Group,
-
-        /**
-         * The Home tab on iOS | possibly the same on Android? | Home page on
-         * Web
-         */
-        Home,
-
-        /**
-         * The screen that displays the login flow (when the user already has an
-         * account).
-         */
-        Login,
-
-        /**
-         * The screen that displays the user's breadcrumbs.
-         */
-        MobileBreadcrumbs,
-
         /**
          * The tab on mobile that displays the dialpad.
          */
-        MobileDialpad,
+        Dialpad,
 
         /**
          * The Favourites tab on mobile that lists your favourite people/rooms.
          */
-        MobileFavourites,
-
-        /**
-         * The screen shown to share a link to download the app.
-         */
-        MobileInviteFriends,
-
-        /**
-         * The People tab on mobile that lists all the DM rooms you have joined.
-         */
-        MobilePeople,
-
-        /**
-         * The Rooms tab on mobile that lists all the (non-direct) rooms you've
-         * joined.
-         */
-        MobileRooms,
-
-        /**
-         * The Files tab shown in the global search screen on Mobile.
-         */
-        MobileSearchFiles,
+        Favourites,
 
         /**
-         * The Messages tab shown in the global search screen on Mobile.
-         */
-        MobileSearchMessages,
-
-        /**
-         * The People tab shown in the global search screen on Mobile.
-         */
-        MobileSearchPeople,
-
-        /**
-         * The Rooms tab shown in the global search screen on Mobile.
-         */
-        MobileSearchRooms,
-
-        /**
-         * The global settings screen shown in the app.
-         */
-        MobileSettings,
-
-        /**
-         * The settings screen to change the default notification options.
-         */
-        MobileSettingsDefaultNotifications,
-
-        /**
-         * The settings screen to manage notification mentions and keywords.
+         * The form for the forgot password use case
          */
-        MobileSettingsMentionsAndKeywords,
+        ForgotPassword,
 
         /**
-         * The global security settings screen.
+         * Legacy: The screen that shows information about a specific group.
          */
-        MobileSettingsSecurity,
+        Group,
 
         /**
-         * The sidebar shown on mobile with spaces, settings etc.
+         * The Home tab on iOS | possibly the same on Android?
          */
-        MobileSidebar,
+        Home,
 
         /**
-         * Screen that displays the list of members of a space
+         * The screen shown to share a link to download the app.
          */
-        MobileSpaceMembers,
+        InviteFriends,
 
         /**
-         * The bottom sheet that list all space options
+         * The screen that displays the login flow (when the user already has an
+         * account).
          */
-        MobileSpaceMenu,
+        Login,
 
         /**
-         * The screen shown to select which room directory you'd like to use.
+         * Legacy: The screen that shows all groups/communities you have joined.
          */
-        MobileSwitchDirectory,
+        MyGroups,
 
         /**
-         * Legacy: The screen that shows all groups/communities you have joined.
+         * The People tab on mobile that lists all the DM rooms you have joined.
          */
-        MyGroups,
+        People,
 
         /**
          * The screen that displays the registration flow (when the user wants
@@ -217,105 +150,85 @@ data class Screen(
         RoomUploads,
 
         /**
-         * Screen that displays the list of rooms and spaces of a space
-         */
-        SpaceExploreRooms,
-
-        /**
-         * The screen shown to create a new direct room.
-         */
-        StartChat,
-
-        /**
-         * A screen that shows information about a room member.
-         */
-        User,
-
-        /**
-         * Element Web showing flow to trust this new device with cross-signing.
-         */
-        WebCompleteSecurity,
-
-        /**
-         * Element Web showing flow to setup SSSS / cross-signing on this
-         * account.
+         * The Rooms tab on mobile that lists all the (non-direct) rooms you've
+         * joined.
          */
-        WebE2ESetup,
+        Rooms,
 
         /**
-         * Element Web loading spinner.
+         * The Files tab shown in the global search screen on Mobile.
          */
-        WebLoading,
+        SearchFiles,
 
         /**
-         * Element Web device has been soft logged out by the server.
+         * The Messages tab shown in the global search screen on Mobile.
          */
-        WebSoftLogout,
+        SearchMessages,
 
         /**
-         * Legacy: Element Web User Settings Flair Tab.
+         * The People tab shown in the global search screen on Mobile.
          */
-        WebUserSettingFlair,
+        SearchPeople,
 
         /**
-         * Element Web User Settings Mjolnir (labs) Tab.
+         * The Rooms tab shown in the global search screen on Mobile.
          */
-        WebUserSettingMjolnir,
+        SearchRooms,
 
         /**
-         * Element Web User Settings Appearance Tab.
+         * The global settings screen shown in the app.
          */
-        WebUserSettingsAppearance,
+        Settings,
 
         /**
-         * Element Web User Settings General Tab.
+         * The settings screen to change the default notification options.
          */
-        WebUserSettingsGeneral,
+        SettingsDefaultNotifications,
 
         /**
-         * Element Web User Settings Help & About Tab.
+         * The settings screen to manage notification mentions and keywords.
          */
-        WebUserSettingsHelpAbout,
+        SettingsMentionsAndKeywords,
 
         /**
-         * Element Web User Settings Ignored Users Tab.
+         * The global security settings screen.
          */
-        WebUserSettingsIgnoredUsers,
+        SettingsSecurity,
 
         /**
-         * Element Web User Settings Keyboard Tab.
+         * The sidebar shown on mobile with spaces, settings etc.
          */
-        WebUserSettingsKeyboard,
+        Sidebar,
 
         /**
-         * Element Web User Settings Labs Tab.
+         * Screen that displays the list of rooms and spaces of a space
          */
-        WebUserSettingsLabs,
+        SpaceExploreRooms,
 
         /**
-         * Element Web User Settings Notifications Tab.
+         * Screen that displays the list of members of a space
          */
-        WebUserSettingsNotifications,
+        SpaceMembers,
 
         /**
-         * Element Web User Settings Preferences Tab.
+         * The bottom sheet that list all space options
          */
-        WebUserSettingsPreferences,
+        SpaceMenu,
 
         /**
-         * Element Web User Settings Security & Privacy Tab.
+         * The screen shown to create a new direct room.
          */
-        WebUserSettingsSecurityPrivacy,
+        StartChat,
 
         /**
-         * Element Web User Settings Sidebar Tab.
+         * The screen shown to select which room directory you'd like to use.
          */
-        WebUserSettingsSidebar,
+        SwitchDirectory,
 
         /**
-         * Element Web User Settings Voice & Video Tab.
+         * A screen that shows information about a room member.
          */
-        WebUserSettingsVoiceVideo,
+        User,
 
         /**
          * The splash screen.
diff --git a/vector/src/main/java/im/vector/app/features/analytics/plan/WebScreen.kt b/vector/src/main/java/im/vector/app/features/analytics/plan/WebScreen.kt
new file mode 100644
index 00000000000..b43d6d1b872
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/analytics/plan/WebScreen.kt
@@ -0,0 +1,241 @@
+/*
+ * 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.analytics.plan
+
+import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
+
+// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT
+// https://github.com/matrix-org/matrix-analytics-events/
+
+/**
+ * Triggered when the user changed screen on Element Web/Desktop
+ */
+data class WebScreen(
+        val $current_url: $current_url,
+        /**
+         * How long the screen took to load, if applicable.
+         */
+        val durationMs: Int? = null,
+) : VectorAnalyticsEvent {
+
+    enum class $current_url {
+        /**
+         * Screen showing flow to trust this new device with cross-signing.
+         */
+        CompleteSecurity,
+
+        /**
+         * The screen shown to create a new (non-direct) room.
+         */
+        CreateRoom,
+
+        /**
+         * The confirmation screen shown before deactivating an account.
+         */
+        DeactivateAccount,
+
+        /**
+         * Screen showing flow to setup SSSS / cross-signing on this account.
+         */
+        E2ESetup,
+
+        /**
+         * The form for the forgot password use case
+         */
+        ForgotPassword,
+
+        /**
+         * Legacy: The screen that shows information about a specific group.
+         */
+        Group,
+
+        /**
+         * Home page.
+         */
+        Home,
+
+        /**
+         * Screen showing loading spinner.
+         */
+        Loading,
+
+        /**
+         * The screen that displays the login flow (when the user already has an
+         * account).
+         */
+        Login,
+
+        /**
+         * Legacy: The screen that shows all groups/communities you have joined.
+         */
+        MyGroups,
+
+        /**
+         * The screen that displays the registration flow (when the user wants
+         * to create an account)
+         */
+        Register,
+
+        /**
+         * The screen that displays the messages and events received in a room.
+         */
+        Room,
+
+        /**
+         * The screen shown when tapping the name of a room from the Room
+         * screen.
+         */
+        RoomDetails,
+
+        /**
+         * The screen that lists public rooms for you to discover.
+         */
+        RoomDirectory,
+
+        /**
+         * The screen that lists all the user's rooms and let them filter the
+         * rooms.
+         */
+        RoomFilter,
+
+        /**
+         * The screen that displays the list of members that are part of a room.
+         */
+        RoomMembers,
+
+        /**
+         * The notifications settings screen shown from the Room Details screen.
+         */
+        RoomNotifications,
+
+        /**
+         * The screen that allows you to search for messages/files in a specific
+         * room.
+         */
+        RoomSearch,
+
+        /**
+         * The settings screen shown from the Room Details screen.
+         */
+        RoomSettings,
+
+        /**
+         * The screen that allows you to see all of the files sent in a specific
+         * room.
+         */
+        RoomUploads,
+
+        /**
+         * Screen showing device has been soft logged out by the server.
+         */
+        SoftLogout,
+
+        /**
+         * Screen that displays the list of rooms and spaces of a space
+         */
+        SpaceExploreRooms,
+
+        /**
+         * The screen shown to create a new direct room.
+         */
+        StartChat,
+
+        /**
+         * A screen that shows information about a room member.
+         */
+        User,
+
+        /**
+         * Legacy: screen showing User Settings Flair Tab.
+         */
+        UserSettingFlair,
+
+        /**
+         * Screen showing User Settings Mjolnir (labs) Tab.
+         */
+        UserSettingMjolnir,
+
+        /**
+         * Screen showing User Settings Appearance Tab.
+         */
+        UserSettingsAppearance,
+
+        /**
+         * Screen showing User Settings General Tab.
+         */
+        UserSettingsGeneral,
+
+        /**
+         * Screen showing User Settings Help & About Tab.
+         */
+        UserSettingsHelpAbout,
+
+        /**
+         * Screen showing User Settings Ignored Users Tab.
+         */
+        UserSettingsIgnoredUsers,
+
+        /**
+         * Screen showing User Settings Keyboard Tab.
+         */
+        UserSettingsKeyboard,
+
+        /**
+         * Screen showing User Settings Labs Tab.
+         */
+        UserSettingsLabs,
+
+        /**
+         * Screen showing User Settings Notifications Tab.
+         */
+        UserSettingsNotifications,
+
+        /**
+         * Screen showing User Settings Preferences Tab.
+         */
+        UserSettingsPreferences,
+
+        /**
+         * Screen showing User Settings Security & Privacy Tab.
+         */
+        UserSettingsSecurityPrivacy,
+
+        /**
+         * Screen showing User Settings Sidebar Tab.
+         */
+        UserSettingsSidebar,
+
+        /**
+         * Screen showing User Settings Voice & Video Tab.
+         */
+        UserSettingsVoiceVideo,
+
+        /**
+         * The splash screen.
+         */
+        Welcome,
+    }
+
+    override fun getName() = "$pageview"
+
+    override fun getProperties(): Map? {
+        return mutableMapOf().apply {
+            put("$current_url", $current_url.name)
+            durationMs?.let { put("durationMs", it) }
+        }.takeIf { it.isNotEmpty() }
+    }
+}

From 62d9c81420a1210a438b446a32819ef7910fe713 Mon Sep 17 00:00:00 2001
From: Adam Brown 
Date: Mon, 14 Feb 2022 11:38:53 +0000
Subject: [PATCH 361/581] adding UI docs section for contributing to the sanity
 tests - explaining the robot pattern and giving examples

---
 docs/ui-tests.md | 73 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 73 insertions(+)

diff --git a/docs/ui-tests.md b/docs/ui-tests.md
index 6ebb52abe85..05eb50f5259 100644
--- a/docs/ui-tests.md
+++ b/docs/ui-tests.md
@@ -104,3 +104,76 @@ fun initAccount() {
   existingSession = createAccountAndSync(matrix, userName, password, true)
 }
 ```
+
+### Contributing to the UiAllScreensSanityTest
+
+The `UiAllScreensSanityTest` makes use of the Robot pattern in order to model pages, components and interactions.
+Each Robot aims to return the UI back to its original state after the interaction, allowing for a reusable and consistent DSL.
+
+```kotlin
+// launches and closes settings after executing the block
+elementRobot.settings {
+    // whilst in the settings, launches and closes the advanced settings sub screen
+    advancedSettings {
+        // crawls all the pages within the advanced settings
+        crawl()
+    }
+}
+
+// enables developer mode by navigating to the settings, enabling the toggle and then returning to the starting point to execute the block
+// on block completion the Robot disables developer mode by navigating back to the settings and finally returning to the original starting point
+elementRobot.withDeveloperMode {
+    // the same starting point as the example above
+    settings {
+        advancedSettings { crawlDeveloperOptions() }
+    }
+}
+```
+
+The Robots used in the example above...
+
+```kotlin
+class ElementRobot {
+    fun settings(block: SettingsRobot.() -> Unit) {
+        // double check we're where we think we are
+        waitUntilViewVisible(withId(R.id.bottomNavigationView))
+
+        // navigate to the settings
+        openDrawer()
+        clickOn(R.id.homeDrawerHeaderSettingsView)
+
+        // execute the robot with the context of the settings screen
+        block(SettingsRobot())
+
+        // close the settings and ensure we're back at the starting point
+        pressBack()
+        waitUntilViewVisible(withId(R.id.bottomNavigationView))
+    }
+
+    fun withDeveloperMode(block: ElementRobot.() -> Unit) {
+        settings { toggleDeveloperMode() }
+        block()
+        settings { toggleDeveloperMode() }
+    }
+}
+
+class SettingsRobot {
+    fun toggleDeveloperMode() {
+        advancedSettings {
+            toggleDeveloperMode()
+        }
+    }
+
+    fun advancedSettings(block: SettingsAdvancedRobot.() -> Unit) {
+        clickOn(R.string.settings_advanced_settings)
+        block(SettingsAdvancedRobot())
+        pressBack()
+    }
+}
+
+class SettingsAdvancedRobot {
+    fun toggleDeveloperMode() {
+        clickOn(R.string.settings_developer_mode_summary)
+    }
+}
+```
\ No newline at end of file

From 4519dec7eb2d95c5dc8a16419a5ec9b737973673 Mon Sep 17 00:00:00 2001
From: Adam Brown 
Date: Mon, 14 Feb 2022 12:05:44 +0000
Subject: [PATCH 362/581] scheduling a refresh of the homeserver capabilities
 on introduction of new fields

---
 .../database/migration/MigrateSessionTo025.kt         |  2 ++
 .../sdk/internal/extensions/RealmExtensions.kt        | 11 +++++++++++
 2 files changed, 13 insertions(+)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo025.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo025.kt
index 35267d2a166..237b016ac25 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo025.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo025.kt
@@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.database.migration
 
 import io.realm.DynamicRealm
 import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields
+import org.matrix.android.sdk.internal.extensions.forceRefreshOfHomeServerCapabilities
 import org.matrix.android.sdk.internal.util.database.RealmMigrator
 
 class MigrateSessionTo025(realm: DynamicRealm) : RealmMigrator(realm, 25) {
@@ -27,5 +28,6 @@ class MigrateSessionTo025(realm: DynamicRealm) : RealmMigrator(realm, 25) {
                 ?.addField(HomeServerCapabilitiesEntityFields.CAN_CHANGE_DISPLAY_NAME, Boolean::class.java)
                 ?.addField(HomeServerCapabilitiesEntityFields.CAN_CHANGE_AVATAR, Boolean::class.java)
                 ?.addField(HomeServerCapabilitiesEntityFields.CAN_CHANGE3PID, Boolean::class.java)
+                ?.forceRefreshOfHomeServerCapabilities()
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/RealmExtensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/RealmExtensions.kt
index e52e32e16ad..28b9f641882 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/RealmExtensions.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/RealmExtensions.kt
@@ -18,6 +18,8 @@ package org.matrix.android.sdk.internal.extensions
 
 import io.realm.RealmList
 import io.realm.RealmObject
+import io.realm.RealmObjectSchema
+import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields
 
 internal fun RealmObject.assertIsManaged() {
     check(isManaged) { "${javaClass.simpleName} entity should be managed to use this function" }
@@ -31,3 +33,12 @@ internal fun  RealmList.clearWith(delete: (T) -> Unit) {
         first()?.let { delete.invoke(it) }
     }
 }
+
+/**
+ * Schedule a refresh of the HomeServers capabilities
+ */
+internal fun RealmObjectSchema?.forceRefreshOfHomeServerCapabilities(): RealmObjectSchema? {
+    return this?.transform { obj ->
+        obj.setLong(HomeServerCapabilitiesEntityFields.LAST_UPDATED_TIMESTAMP, 0)
+    }
+}

From fed549f647259fa3c4d09bb1b095a9be690bc454 Mon Sep 17 00:00:00 2001
From: Adam Brown 
Date: Mon, 14 Feb 2022 12:05:53 +0000
Subject: [PATCH 363/581] reusing refresh extension

---
 .../sdk/internal/database/migration/MigrateSessionTo003.kt | 7 ++-----
 .../sdk/internal/database/migration/MigrateSessionTo016.kt | 6 ++----
 2 files changed, 4 insertions(+), 9 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo003.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo003.kt
index ab848d129a1..bc0b79d7e64 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo003.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo003.kt
@@ -17,7 +17,7 @@
 package org.matrix.android.sdk.internal.database.migration
 
 import io.realm.DynamicRealm
-import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields
+import org.matrix.android.sdk.internal.extensions.forceRefreshOfHomeServerCapabilities
 import org.matrix.android.sdk.internal.util.database.RealmMigrator
 
 class MigrateSessionTo003(realm: DynamicRealm) : RealmMigrator(realm, 3) {
@@ -25,9 +25,6 @@ class MigrateSessionTo003(realm: DynamicRealm) : RealmMigrator(realm, 3) {
     override fun doMigrate(realm: DynamicRealm) {
         realm.schema.get("HomeServerCapabilitiesEntity")
                 ?.addField("preferredJitsiDomain", String::class.java)
-                ?.transform { obj ->
-                    // Schedule a refresh of the capabilities
-                    obj.setLong(HomeServerCapabilitiesEntityFields.LAST_UPDATED_TIMESTAMP, 0)
-                }
+                ?.forceRefreshOfHomeServerCapabilities()
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo016.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo016.kt
index e6b3d4a4631..b2fa54a05c3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo016.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo016.kt
@@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.database.migration
 
 import io.realm.DynamicRealm
 import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields
+import org.matrix.android.sdk.internal.extensions.forceRefreshOfHomeServerCapabilities
 import org.matrix.android.sdk.internal.util.database.RealmMigrator
 
 class MigrateSessionTo016(realm: DynamicRealm) : RealmMigrator(realm, 16) {
@@ -25,9 +26,6 @@ class MigrateSessionTo016(realm: DynamicRealm) : RealmMigrator(realm, 16) {
     override fun doMigrate(realm: DynamicRealm) {
         realm.schema.get("HomeServerCapabilitiesEntity")
                 ?.addField(HomeServerCapabilitiesEntityFields.ROOM_VERSIONS_JSON, String::class.java)
-                ?.transform { obj ->
-                    // Schedule a refresh of the capabilities
-                    obj.setLong(HomeServerCapabilitiesEntityFields.LAST_UPDATED_TIMESTAMP, 0)
-                }
+                ?.forceRefreshOfHomeServerCapabilities()
     }
 }

From 674aea97a8db26f63bf2c72ab68412368e1d6d2e Mon Sep 17 00:00:00 2001
From: Adam Brown 
Date: Mon, 14 Feb 2022 12:30:47 +0000
Subject: [PATCH 364/581] injecting the room name provider as we're within
 hilts scope

---
 vector/src/main/java/im/vector/app/core/di/SingletonModule.kt | 4 ++--
 .../features/room/VectorRoomDisplayNameFallbackProvider.kt    | 3 ++-
 2 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt b/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt
index 2629d2056bc..7db88b801b2 100644
--- a/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt
+++ b/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt
@@ -110,10 +110,10 @@ object VectorStaticModule {
     }
 
     @Provides
-    fun providesMatrixConfiguration(context: Context): MatrixConfiguration {
+    fun providesMatrixConfiguration(vectorRoomDisplayNameFallbackProvider: VectorRoomDisplayNameFallbackProvider): MatrixConfiguration {
         return MatrixConfiguration(
                 applicationFlavor = BuildConfig.FLAVOR_DESCRIPTION,
-                roomDisplayNameFallbackProvider = VectorRoomDisplayNameFallbackProvider(context)
+                roomDisplayNameFallbackProvider = vectorRoomDisplayNameFallbackProvider
         )
     }
 
diff --git a/vector/src/main/java/im/vector/app/features/room/VectorRoomDisplayNameFallbackProvider.kt b/vector/src/main/java/im/vector/app/features/room/VectorRoomDisplayNameFallbackProvider.kt
index 33e63434ce4..0be2a21ef46 100644
--- a/vector/src/main/java/im/vector/app/features/room/VectorRoomDisplayNameFallbackProvider.kt
+++ b/vector/src/main/java/im/vector/app/features/room/VectorRoomDisplayNameFallbackProvider.kt
@@ -19,8 +19,9 @@ package im.vector.app.features.room
 import android.content.Context
 import im.vector.app.R
 import org.matrix.android.sdk.api.RoomDisplayNameFallbackProvider
+import javax.inject.Inject
 
-class VectorRoomDisplayNameFallbackProvider(
+class VectorRoomDisplayNameFallbackProvider @Inject constructor(
         private val context: Context
 ) : RoomDisplayNameFallbackProvider {
 

From fd2d9287e7b536ac259f950ad66ecfb11ef029f8 Mon Sep 17 00:00:00 2001
From: Adam Brown 
Date: Mon, 14 Feb 2022 12:46:07 +0000
Subject: [PATCH 365/581] creating separate creator for the matrix instance to
 avoid ambiguous non singleton/duplicated singleton usages - also documents
 the static methods

---
 .../java/org/matrix/android/sdk/api/Matrix.kt    | 16 ++++++++++++++++
 .../im/vector/app/core/utils/TestMatrixHelper.kt |  3 +--
 .../im/vector/app/core/di/SingletonModule.kt     |  3 +--
 3 files changed, 18 insertions(+), 4 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt
index 901ba75d169..a121b8a85d3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt
@@ -99,12 +99,28 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
         private lateinit var instance: Matrix
         private val isInit = AtomicBoolean(false)
 
+        /**
+         * Creates a new instance of Matrix, it's recommend to manage this instance as a singleton.
+         * To make use of the built in singleton use Matrix.initialise() and/or Matrix.getInstance(context) instead
+         **/
+        fun createInstance(context: Context, matrixConfiguration: MatrixConfiguration): Matrix {
+            return Matrix(context.applicationContext, matrixConfiguration)
+        }
+
+        /**
+         * Initializes a singleton instance of Matrix for the given MatrixConfiguration
+         * This instance will be returned by Matrix.getInstance(context)
+         */
         fun initialize(context: Context, matrixConfiguration: MatrixConfiguration) {
             if (isInit.compareAndSet(false, true)) {
                 instance = Matrix(context.applicationContext, matrixConfiguration)
             }
         }
 
+        /**
+         * Either provides an already initialized singleton Matrix instance or queries the application context for a MatrixConfiguration.Provider
+         * to lazily create and store the instance.
+         */
         fun getInstance(context: Context): Matrix {
             if (isInit.compareAndSet(false, true)) {
                 val appContext = context.applicationContext
diff --git a/vector/src/androidTest/java/im/vector/app/core/utils/TestMatrixHelper.kt b/vector/src/androidTest/java/im/vector/app/core/utils/TestMatrixHelper.kt
index fab41a68bb4..322f5fa23d9 100644
--- a/vector/src/androidTest/java/im/vector/app/core/utils/TestMatrixHelper.kt
+++ b/vector/src/androidTest/java/im/vector/app/core/utils/TestMatrixHelper.kt
@@ -26,6 +26,5 @@ fun getMatrixInstance(): Matrix {
     val configuration = MatrixConfiguration(
             roomDisplayNameFallbackProvider = VectorRoomDisplayNameFallbackProvider(context)
     )
-    Matrix.initialize(context, configuration)
-    return Matrix.getInstance(context)
+    return Matrix.createInstance(context, configuration)
 }
diff --git a/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt b/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt
index 7db88b801b2..b4bee417b49 100644
--- a/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt
+++ b/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt
@@ -120,8 +120,7 @@ object VectorStaticModule {
     @Provides
     @Singleton
     fun providesMatrix(context: Context, configuration: MatrixConfiguration): Matrix {
-        Matrix.initialize(context, configuration)
-        return Matrix.getInstance(context)
+        return Matrix.createInstance(context, configuration)
     }
 
     @Provides

From 2f7f86b8bb04c1426dfb962b2ab01db85d4cd06f Mon Sep 17 00:00:00 2001
From: Adam Brown 
Date: Mon, 14 Feb 2022 13:41:27 +0000
Subject: [PATCH 366/581] Update
 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt

Co-authored-by: Benoit Marty 
---
 .../src/main/java/org/matrix/android/sdk/api/Matrix.kt          | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt
index a121b8a85d3..7dd19c27440 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt
@@ -100,7 +100,7 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
         private val isInit = AtomicBoolean(false)
 
         /**
-         * Creates a new instance of Matrix, it's recommend to manage this instance as a singleton.
+         * Creates a new instance of Matrix, it's recommended to manage this instance as a singleton.
          * To make use of the built in singleton use Matrix.initialise() and/or Matrix.getInstance(context) instead
          **/
         fun createInstance(context: Context, matrixConfiguration: MatrixConfiguration): Matrix {

From 2eb417ab050c27b454a867c7379570727710d745 Mon Sep 17 00:00:00 2001
From: Adam Brown 
Date: Mon, 14 Feb 2022 13:41:40 +0000
Subject: [PATCH 367/581] Update
 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt

Co-authored-by: Benoit Marty 
---
 .../src/main/java/org/matrix/android/sdk/api/Matrix.kt          | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt
index 7dd19c27440..b2f81891a77 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt
@@ -101,7 +101,7 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
 
         /**
          * Creates a new instance of Matrix, it's recommended to manage this instance as a singleton.
-         * To make use of the built in singleton use Matrix.initialise() and/or Matrix.getInstance(context) instead
+         * To make use of the built in singleton use Matrix.initialize() and/or Matrix.getInstance(context) instead
          **/
         fun createInstance(context: Context, matrixConfiguration: MatrixConfiguration): Matrix {
             return Matrix(context.applicationContext, matrixConfiguration)

From 88ecfa367891a0a26dafb4948a2fc19d78c5cba3 Mon Sep 17 00:00:00 2001
From: Benoit Marty 
Date: Mon, 14 Feb 2022 16:40:11 +0100
Subject: [PATCH 368/581] Fix compilation issue, Screen has been renamed to
 MobileScreen Also manually delete WebScreen, which does not compile. Will
 have to fix that upstream.

---
 .../app/core/platform/VectorBaseActivity.kt   |   4 +-
 .../VectorBaseBottomSheetDialogFragment.kt    |   4 +-
 .../app/core/platform/VectorBaseFragment.kt   |   4 +-
 .../app/features/analytics/plan/WebScreen.kt  | 241 ------------------
 .../features/analytics/screen/ScreenEvent.kt  |   8 +-
 .../features/call/dialpad/DialPadFragment.kt  |   4 +-
 .../createdirect/CreateDirectRoomActivity.kt  |   4 +-
 .../vector/app/features/home/HomeActivity.kt  |   6 +-
 .../app/features/home/HomeDrawerFragment.kt   |   4 +-
 .../home/room/detail/RoomDetailActivity.kt    |   4 +-
 .../home/room/detail/TimelineFragment.kt      |   4 +-
 .../room/filtered/FilteredRoomsActivity.kt    |   4 +-
 .../home/room/list/RoomListFragment.kt        |   6 +-
 .../app/features/login/LoginActivity.kt       |   6 +-
 .../login/LoginResetPasswordFragment.kt       |   4 +-
 .../app/features/login/LoginSplashFragment.kt |   4 +-
 .../roomdirectory/RoomDirectoryActivity.kt    |   4 +-
 .../createroom/CreateRoomActivity.kt          |   4 +-
 .../picker/RoomDirectoryPickerFragment.kt     |   4 +-
 .../RoomMemberProfileFragment.kt              |   4 +-
 .../roomprofile/RoomProfileFragment.kt        |   4 +-
 .../uploads/RoomUploadsFragment.kt            |   4 +-
 .../settings/VectorSettingsBaseFragment.kt    |   4 +-
 .../settings/VectorSettingsRootFragment.kt    |   4 +-
 .../VectorSettingsSecurityPrivacyFragment.kt  |   4 +-
 .../deactivation/DeactivateAccountFragment.kt |   4 +-
 ...gsDefaultNotificationPreferenceFragment.kt |   4 +-
 ...dMentionsNotificationPreferenceFragment.kt |   4 +-
 28 files changed, 59 insertions(+), 300 deletions(-)
 delete mode 100644 vector/src/main/java/im/vector/app/features/analytics/plan/WebScreen.kt

diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt
index 8164df9c55e..5767acd44b4 100644
--- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt
+++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt
@@ -67,7 +67,7 @@ import im.vector.app.core.utils.toast
 import im.vector.app.features.MainActivity
 import im.vector.app.features.MainActivityArgs
 import im.vector.app.features.analytics.AnalyticsTracker
-import im.vector.app.features.analytics.plan.Screen
+import im.vector.app.features.analytics.plan.MobileScreen
 import im.vector.app.features.analytics.screen.ScreenEvent
 import im.vector.app.features.configuration.VectorConfiguration
 import im.vector.app.features.consent.ConsentNotGivenHelper
@@ -97,7 +97,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver
      * Analytics
      * ========================================================================================== */
 
-    protected var analyticsScreenName: Screen.ScreenName? = null
+    protected var analyticsScreenName: MobileScreen.ScreenName? = null
     private var screenEvent: ScreenEvent? = null
 
     protected lateinit var analyticsTracker: AnalyticsTracker
diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt
index 7e6a4292740..869a12e8712 100644
--- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt
+++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt
@@ -38,7 +38,7 @@ import im.vector.app.core.extensions.singletonEntryPoint
 import im.vector.app.core.extensions.toMvRxBundle
 import im.vector.app.core.utils.DimensionConverter
 import im.vector.app.features.analytics.AnalyticsTracker
-import im.vector.app.features.analytics.plan.Screen
+import im.vector.app.features.analytics.plan.MobileScreen
 import im.vector.app.features.analytics.screen.ScreenEvent
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
@@ -53,7 +53,7 @@ abstract class VectorBaseBottomSheetDialogFragment : BottomShe
      * Analytics
      * ========================================================================================== */
 
-    protected var analyticsScreenName: Screen.ScreenName? = null
+    protected var analyticsScreenName: MobileScreen.ScreenName? = null
     private var screenEvent: ScreenEvent? = null
 
     protected lateinit var analyticsTracker: AnalyticsTracker
diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt
index 8a1b9051cc5..6bd62707f2e 100644
--- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt
+++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt
@@ -44,7 +44,7 @@ import im.vector.app.core.extensions.singletonEntryPoint
 import im.vector.app.core.extensions.toMvRxBundle
 import im.vector.app.core.utils.ToolbarConfig
 import im.vector.app.features.analytics.AnalyticsTracker
-import im.vector.app.features.analytics.plan.Screen
+import im.vector.app.features.analytics.plan.MobileScreen
 import im.vector.app.features.analytics.screen.ScreenEvent
 import im.vector.app.features.navigation.Navigator
 import im.vector.lib.ui.styles.dialogs.MaterialProgressDialog
@@ -58,7 +58,7 @@ abstract class VectorBaseFragment : Fragment(), MavericksView
      * Analytics
      * ========================================================================================== */
 
-    protected var analyticsScreenName: Screen.ScreenName? = null
+    protected var analyticsScreenName: MobileScreen.ScreenName? = null
     private var screenEvent: ScreenEvent? = null
 
     protected lateinit var analyticsTracker: AnalyticsTracker
diff --git a/vector/src/main/java/im/vector/app/features/analytics/plan/WebScreen.kt b/vector/src/main/java/im/vector/app/features/analytics/plan/WebScreen.kt
deleted file mode 100644
index b43d6d1b872..00000000000
--- a/vector/src/main/java/im/vector/app/features/analytics/plan/WebScreen.kt
+++ /dev/null
@@ -1,241 +0,0 @@
-/*
- * 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.analytics.plan
-
-import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
-
-// GENERATED FILE, DO NOT EDIT. FOR MORE INFORMATION VISIT
-// https://github.com/matrix-org/matrix-analytics-events/
-
-/**
- * Triggered when the user changed screen on Element Web/Desktop
- */
-data class WebScreen(
-        val $current_url: $current_url,
-        /**
-         * How long the screen took to load, if applicable.
-         */
-        val durationMs: Int? = null,
-) : VectorAnalyticsEvent {
-
-    enum class $current_url {
-        /**
-         * Screen showing flow to trust this new device with cross-signing.
-         */
-        CompleteSecurity,
-
-        /**
-         * The screen shown to create a new (non-direct) room.
-         */
-        CreateRoom,
-
-        /**
-         * The confirmation screen shown before deactivating an account.
-         */
-        DeactivateAccount,
-
-        /**
-         * Screen showing flow to setup SSSS / cross-signing on this account.
-         */
-        E2ESetup,
-
-        /**
-         * The form for the forgot password use case
-         */
-        ForgotPassword,
-
-        /**
-         * Legacy: The screen that shows information about a specific group.
-         */
-        Group,
-
-        /**
-         * Home page.
-         */
-        Home,
-
-        /**
-         * Screen showing loading spinner.
-         */
-        Loading,
-
-        /**
-         * The screen that displays the login flow (when the user already has an
-         * account).
-         */
-        Login,
-
-        /**
-         * Legacy: The screen that shows all groups/communities you have joined.
-         */
-        MyGroups,
-
-        /**
-         * The screen that displays the registration flow (when the user wants
-         * to create an account)
-         */
-        Register,
-
-        /**
-         * The screen that displays the messages and events received in a room.
-         */
-        Room,
-
-        /**
-         * The screen shown when tapping the name of a room from the Room
-         * screen.
-         */
-        RoomDetails,
-
-        /**
-         * The screen that lists public rooms for you to discover.
-         */
-        RoomDirectory,
-
-        /**
-         * The screen that lists all the user's rooms and let them filter the
-         * rooms.
-         */
-        RoomFilter,
-
-        /**
-         * The screen that displays the list of members that are part of a room.
-         */
-        RoomMembers,
-
-        /**
-         * The notifications settings screen shown from the Room Details screen.
-         */
-        RoomNotifications,
-
-        /**
-         * The screen that allows you to search for messages/files in a specific
-         * room.
-         */
-        RoomSearch,
-
-        /**
-         * The settings screen shown from the Room Details screen.
-         */
-        RoomSettings,
-
-        /**
-         * The screen that allows you to see all of the files sent in a specific
-         * room.
-         */
-        RoomUploads,
-
-        /**
-         * Screen showing device has been soft logged out by the server.
-         */
-        SoftLogout,
-
-        /**
-         * Screen that displays the list of rooms and spaces of a space
-         */
-        SpaceExploreRooms,
-
-        /**
-         * The screen shown to create a new direct room.
-         */
-        StartChat,
-
-        /**
-         * A screen that shows information about a room member.
-         */
-        User,
-
-        /**
-         * Legacy: screen showing User Settings Flair Tab.
-         */
-        UserSettingFlair,
-
-        /**
-         * Screen showing User Settings Mjolnir (labs) Tab.
-         */
-        UserSettingMjolnir,
-
-        /**
-         * Screen showing User Settings Appearance Tab.
-         */
-        UserSettingsAppearance,
-
-        /**
-         * Screen showing User Settings General Tab.
-         */
-        UserSettingsGeneral,
-
-        /**
-         * Screen showing User Settings Help & About Tab.
-         */
-        UserSettingsHelpAbout,
-
-        /**
-         * Screen showing User Settings Ignored Users Tab.
-         */
-        UserSettingsIgnoredUsers,
-
-        /**
-         * Screen showing User Settings Keyboard Tab.
-         */
-        UserSettingsKeyboard,
-
-        /**
-         * Screen showing User Settings Labs Tab.
-         */
-        UserSettingsLabs,
-
-        /**
-         * Screen showing User Settings Notifications Tab.
-         */
-        UserSettingsNotifications,
-
-        /**
-         * Screen showing User Settings Preferences Tab.
-         */
-        UserSettingsPreferences,
-
-        /**
-         * Screen showing User Settings Security & Privacy Tab.
-         */
-        UserSettingsSecurityPrivacy,
-
-        /**
-         * Screen showing User Settings Sidebar Tab.
-         */
-        UserSettingsSidebar,
-
-        /**
-         * Screen showing User Settings Voice & Video Tab.
-         */
-        UserSettingsVoiceVideo,
-
-        /**
-         * The splash screen.
-         */
-        Welcome,
-    }
-
-    override fun getName() = "$pageview"
-
-    override fun getProperties(): Map? {
-        return mutableMapOf().apply {
-            put("$current_url", $current_url.name)
-            durationMs?.let { put("durationMs", it) }
-        }.takeIf { it.isNotEmpty() }
-    }
-}
diff --git a/vector/src/main/java/im/vector/app/features/analytics/screen/ScreenEvent.kt b/vector/src/main/java/im/vector/app/features/analytics/screen/ScreenEvent.kt
index 8e0513f25ae..1ad4a1fa324 100644
--- a/vector/src/main/java/im/vector/app/features/analytics/screen/ScreenEvent.kt
+++ b/vector/src/main/java/im/vector/app/features/analytics/screen/ScreenEvent.kt
@@ -18,13 +18,13 @@ package im.vector.app.features.analytics.screen
 
 import android.os.SystemClock
 import im.vector.app.features.analytics.AnalyticsTracker
-import im.vector.app.features.analytics.plan.Screen
+import im.vector.app.features.analytics.plan.MobileScreen
 import timber.log.Timber
 
 /**
  * Track a screen display. Unique usage.
  */
-class ScreenEvent(val screenName: Screen.ScreenName) {
+class ScreenEvent(val screenName: MobileScreen.ScreenName) {
     private val startTime = SystemClock.elapsedRealtime()
 
     // Protection to avoid multiple sending
@@ -34,14 +34,14 @@ class ScreenEvent(val screenName: Screen.ScreenName) {
      * @param screenNameOverride can be used to override the screen name passed in constructor parameter
      */
     fun send(analyticsTracker: AnalyticsTracker,
-             screenNameOverride: Screen.ScreenName? = null) {
+             screenNameOverride: MobileScreen.ScreenName? = null) {
         if (isSent) {
             Timber.w("Event $screenName Already sent!")
             return
         }
         isSent = true
         analyticsTracker.screen(
-                Screen(
+                MobileScreen(
                         screenName = screenNameOverride ?: screenName,
                         durationMs = (SystemClock.elapsedRealtime() - startTime).toInt()
                 )
diff --git a/vector/src/main/java/im/vector/app/features/call/dialpad/DialPadFragment.kt b/vector/src/main/java/im/vector/app/features/call/dialpad/DialPadFragment.kt
index 5fc866a4dd3..b33ce25f559 100644
--- a/vector/src/main/java/im/vector/app/features/call/dialpad/DialPadFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/call/dialpad/DialPadFragment.kt
@@ -40,7 +40,7 @@ import com.android.dialer.dialpadview.DigitsEditText
 import im.vector.app.R
 import im.vector.app.core.extensions.singletonEntryPoint
 import im.vector.app.features.analytics.AnalyticsTracker
-import im.vector.app.features.analytics.plan.Screen
+import im.vector.app.features.analytics.plan.MobileScreen
 import im.vector.app.features.analytics.screen.ScreenEvent
 import im.vector.app.features.themes.ThemeUtils
 
@@ -69,7 +69,7 @@ class DialPadFragment : Fragment(), TextWatcher {
     private var screenEvent: ScreenEvent? = null
     override fun onResume() {
         super.onResume()
-        screenEvent = ScreenEvent(Screen.ScreenName.MobileDialpad)
+        screenEvent = ScreenEvent(MobileScreen.ScreenName.Dialpad)
     }
 
     override fun onPause() {
diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt
index 2d93bab6a3b..a6b34eda25f 100644
--- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt
@@ -43,7 +43,7 @@ import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
 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.features.analytics.plan.Screen
+import im.vector.app.features.analytics.plan.MobileScreen
 import im.vector.app.features.contactsbook.ContactsBookFragment
 import im.vector.app.features.qrcode.QrCodeScannerEvents
 import im.vector.app.features.qrcode.QrCodeScannerFragment
@@ -71,7 +71,7 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
-        analyticsScreenName = Screen.ScreenName.StartChat
+        analyticsScreenName = MobileScreen.ScreenName.StartChat
         views.toolbar.visibility = View.GONE
 
         sharedActionViewModel = viewModelProvider.get(UserListSharedActionViewModel::class.java)
diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt
index 6b6be634808..f6b32973a06 100644
--- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt
@@ -48,7 +48,7 @@ import im.vector.app.databinding.ActivityHomeBinding
 import im.vector.app.features.MainActivity
 import im.vector.app.features.MainActivityArgs
 import im.vector.app.features.analytics.accountdata.AnalyticsAccountDataViewModel
-import im.vector.app.features.analytics.plan.Screen
+import im.vector.app.features.analytics.plan.MobileScreen
 import im.vector.app.features.analytics.screen.ScreenEvent
 import im.vector.app.features.disclaimer.showDisclaimerDialog
 import im.vector.app.features.matrixto.MatrixToBottomSheet
@@ -165,7 +165,7 @@ class HomeActivity :
     private val drawerListener = object : DrawerLayout.SimpleDrawerListener() {
         private var drawerScreenEvent: ScreenEvent? = null
         override fun onDrawerOpened(drawerView: View) {
-            drawerScreenEvent = ScreenEvent(Screen.ScreenName.MobileSidebar)
+            drawerScreenEvent = ScreenEvent(MobileScreen.ScreenName.Sidebar)
         }
 
         override fun onDrawerClosed(drawerView: View) {
@@ -184,7 +184,7 @@ class HomeActivity :
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
-        analyticsScreenName = Screen.ScreenName.Home
+        analyticsScreenName = MobileScreen.ScreenName.Home
         supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, false)
         FcmHelper.ensureFcmTokenIsRetrieved(this, pushManager, vectorPreferences.areNotificationEnabledForDevice())
         sharedActionViewModel = viewModelProvider.get(HomeSharedActionViewModel::class.java)
diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt b/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt
index 9af06ef8010..1aee0257f48 100644
--- a/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt
@@ -30,7 +30,7 @@ import im.vector.app.core.extensions.replaceChildFragment
 import im.vector.app.core.platform.VectorBaseFragment
 import im.vector.app.core.utils.startSharePlainTextIntent
 import im.vector.app.databinding.FragmentHomeDrawerBinding
-import im.vector.app.features.analytics.plan.Screen
+import im.vector.app.features.analytics.plan.MobileScreen
 import im.vector.app.features.settings.VectorPreferences
 import im.vector.app.features.settings.VectorSettingsActivity
 import im.vector.app.features.spaces.SpaceListFragment
@@ -98,7 +98,7 @@ class HomeDrawerFragment @Inject constructor(
 
         views.homeDrawerInviteFriendButton.debouncedClicks {
             session.permalinkService().createPermalink(sharedActionViewModel.session.myUserId)?.let { permalink ->
-                analyticsTracker.screen(Screen(screenName = Screen.ScreenName.MobileInviteFriends))
+                analyticsTracker.screen(MobileScreen(screenName = MobileScreen.ScreenName.InviteFriends))
                 val text = getString(R.string.invite_friends_text, permalink)
 
                 startSharePlainTextIntent(
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt
index ae24052aa2b..f5bf086e960 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt
@@ -35,7 +35,7 @@ import im.vector.app.core.extensions.keepScreenOn
 import im.vector.app.core.extensions.replaceFragment
 import im.vector.app.core.platform.VectorBaseActivity
 import im.vector.app.databinding.ActivityRoomDetailBinding
-import im.vector.app.features.analytics.plan.Screen
+import im.vector.app.features.analytics.plan.MobileScreen
 import im.vector.app.features.analytics.screen.ScreenEvent
 import im.vector.app.features.home.room.breadcrumbs.BreadcrumbsFragment
 import im.vector.app.features.home.room.detail.arguments.TimelineArgs
@@ -160,7 +160,7 @@ class RoomDetailActivity :
     private val drawerListener = object : DrawerLayout.SimpleDrawerListener() {
         private var drawerScreenEvent: ScreenEvent? = null
         override fun onDrawerOpened(drawerView: View) {
-            drawerScreenEvent = ScreenEvent(Screen.ScreenName.MobileBreadcrumbs)
+            drawerScreenEvent = ScreenEvent(MobileScreen.ScreenName.Breadcrumbs)
         }
 
         override fun onDrawerClosed(drawerView: View) {
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 92319fabdc6..ff392eaf1ba 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
@@ -120,7 +120,7 @@ import im.vector.app.core.utils.toast
 import im.vector.app.databinding.DialogReportContentBinding
 import im.vector.app.databinding.FragmentTimelineBinding
 import im.vector.app.features.analytics.plan.Composer
-import im.vector.app.features.analytics.plan.Screen
+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
@@ -342,7 +342,7 @@ class TimelineFragment @Inject constructor(
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
-        analyticsScreenName = Screen.ScreenName.Room
+        analyticsScreenName = MobileScreen.ScreenName.Room
         setFragmentResultListener(MigrateRoomBottomSheet.REQUEST_KEY) { _, bundle ->
             bundle.getString(MigrateRoomBottomSheet.BUNDLE_KEY_REPLACEMENT_ROOM)?.let { replacementRoomId ->
                 timelineViewModel.handle(RoomDetailAction.RoomUpgradeSuccess(replacementRoomId))
diff --git a/vector/src/main/java/im/vector/app/features/home/room/filtered/FilteredRoomsActivity.kt b/vector/src/main/java/im/vector/app/features/home/room/filtered/FilteredRoomsActivity.kt
index 0e16b4b0df6..cf7c4a0e803 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/filtered/FilteredRoomsActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/filtered/FilteredRoomsActivity.kt
@@ -24,7 +24,7 @@ import dagger.hilt.android.AndroidEntryPoint
 import im.vector.app.core.extensions.replaceFragment
 import im.vector.app.core.platform.VectorBaseActivity
 import im.vector.app.databinding.ActivityFilteredRoomsBinding
-import im.vector.app.features.analytics.plan.Screen
+import im.vector.app.features.analytics.plan.MobileScreen
 import im.vector.app.features.home.RoomListDisplayMode
 import im.vector.app.features.home.room.list.RoomListFragment
 import im.vector.app.features.home.room.list.RoomListParams
@@ -43,7 +43,7 @@ class FilteredRoomsActivity : VectorBaseActivity()
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
-        analyticsScreenName = Screen.ScreenName.RoomFilter
+        analyticsScreenName = MobileScreen.ScreenName.RoomFilter
         setupToolbar(views.filteredRoomsToolbar)
                 .allowBack()
         if (isFirstCreation()) {
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 b6481c9cbb0..b023c26590b 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
@@ -42,7 +42,7 @@ import im.vector.app.core.platform.StateView
 import im.vector.app.core.platform.VectorBaseFragment
 import im.vector.app.core.resources.UserPreferencesProvider
 import im.vector.app.databinding.FragmentRoomListBinding
-import im.vector.app.features.analytics.plan.Screen
+import im.vector.app.features.analytics.plan.MobileScreen
 import im.vector.app.features.home.RoomListDisplayMode
 import im.vector.app.features.home.room.filtered.FilteredRoomFooterItem
 import im.vector.app.features.home.room.list.actions.RoomListQuickActionsBottomSheet
@@ -104,8 +104,8 @@ class RoomListFragment @Inject constructor(
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         analyticsScreenName = when (roomListParams.displayMode) {
-            RoomListDisplayMode.PEOPLE -> Screen.ScreenName.MobilePeople
-            RoomListDisplayMode.ROOMS  -> Screen.ScreenName.MobileRooms
+            RoomListDisplayMode.PEOPLE -> MobileScreen.ScreenName.People
+            RoomListDisplayMode.ROOMS  -> MobileScreen.ScreenName.Rooms
             else                       -> null
         }
     }
diff --git a/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt b/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt
index edc77d73f67..bf596fc6aa3 100644
--- a/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt
@@ -38,7 +38,7 @@ import im.vector.app.core.extensions.addFragmentToBackstack
 import im.vector.app.core.extensions.exhaustive
 import im.vector.app.core.platform.VectorBaseActivity
 import im.vector.app.databinding.ActivityLoginBinding
-import im.vector.app.features.analytics.plan.Screen
+import im.vector.app.features.analytics.plan.MobileScreen
 import im.vector.app.features.home.HomeActivity
 import im.vector.app.features.login.terms.LoginTermsFragment
 import im.vector.app.features.login.terms.LoginTermsFragmentArgument
@@ -81,7 +81,7 @@ open class LoginActivity : VectorBaseActivity(), UnlockedA
     override fun getCoordinatorLayout() = views.coordinatorLayout
 
     override fun initUiAndData() {
-        analyticsScreenName = Screen.ScreenName.Login
+        analyticsScreenName = MobileScreen.ScreenName.Login
 
         if (isFirstCreation()) {
             addFirstFragment()
@@ -203,7 +203,7 @@ open class LoginActivity : VectorBaseActivity(), UnlockedA
         if (loginViewState.isUserLogged()) {
             if (loginViewState.signMode == SignMode.SignUp) {
                 // change the screen name
-                analyticsScreenName = Screen.ScreenName.Register
+                analyticsScreenName = MobileScreen.ScreenName.Register
             }
             val intent = HomeActivity.newIntent(
                     this,
diff --git a/vector/src/main/java/im/vector/app/features/login/LoginResetPasswordFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginResetPasswordFragment.kt
index 0328d09427c..d1212455328 100644
--- a/vector/src/main/java/im/vector/app/features/login/LoginResetPasswordFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/login/LoginResetPasswordFragment.kt
@@ -31,7 +31,7 @@ import im.vector.app.core.extensions.hidePassword
 import im.vector.app.core.extensions.isEmail
 import im.vector.app.core.extensions.toReducedUrl
 import im.vector.app.databinding.FragmentLoginResetPasswordBinding
-import im.vector.app.features.analytics.plan.Screen
+import im.vector.app.features.analytics.plan.MobileScreen
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.map
@@ -48,7 +48,7 @@ class LoginResetPasswordFragment @Inject constructor() : AbstractLoginFragment(), Matri
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
-        analyticsScreenName = Screen.ScreenName.RoomDirectory
+        analyticsScreenName = MobileScreen.ScreenName.RoomDirectory
         sharedActionViewModel = viewModelProvider.get(RoomDirectorySharedActionViewModel::class.java)
 
         if (isFirstCreation()) {
diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomActivity.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomActivity.kt
index 339c819a65f..e4c350b88e1 100644
--- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomActivity.kt
@@ -26,7 +26,7 @@ import dagger.hilt.android.AndroidEntryPoint
 import im.vector.app.core.extensions.addFragment
 import im.vector.app.core.platform.VectorBaseActivity
 import im.vector.app.databinding.ActivitySimpleBinding
-import im.vector.app.features.analytics.plan.Screen
+import im.vector.app.features.analytics.plan.MobileScreen
 import im.vector.app.features.roomdirectory.RoomDirectorySharedAction
 import im.vector.app.features.roomdirectory.RoomDirectorySharedActionViewModel
 import kotlinx.coroutines.flow.launchIn
@@ -57,7 +57,7 @@ class CreateRoomActivity : VectorBaseActivity() {
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
-        analyticsScreenName = Screen.ScreenName.CreateRoom
+        analyticsScreenName = MobileScreen.ScreenName.CreateRoom
         sharedActionViewModel = viewModelProvider.get(RoomDirectorySharedActionViewModel::class.java)
         sharedActionViewModel
                 .stream()
diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerFragment.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerFragment.kt
index 48610dda7b1..cb71f93a0e7 100644
--- a/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/roomdirectory/picker/RoomDirectoryPickerFragment.kt
@@ -29,7 +29,7 @@ import im.vector.app.core.extensions.configureWith
 import im.vector.app.core.platform.OnBackPressed
 import im.vector.app.core.platform.VectorBaseFragment
 import im.vector.app.databinding.FragmentRoomDirectoryPickerBinding
-import im.vector.app.features.analytics.plan.Screen
+import im.vector.app.features.analytics.plan.MobileScreen
 import im.vector.app.features.roomdirectory.RoomDirectoryAction
 import im.vector.app.features.roomdirectory.RoomDirectoryData
 import im.vector.app.features.roomdirectory.RoomDirectoryServer
@@ -54,7 +54,7 @@ class RoomDirectoryPickerFragment @Inject constructor(private val roomDirectoryP
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
-        analyticsScreenName = Screen.ScreenName.MobileSwitchDirectory
+        analyticsScreenName = MobileScreen.ScreenName.SwitchDirectory
     }
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt
index c68bfca973e..fcebe9adbbf 100644
--- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileFragment.kt
@@ -47,7 +47,7 @@ import im.vector.app.databinding.DialogBaseEditTextBinding
 import im.vector.app.databinding.DialogShareQrCodeBinding
 import im.vector.app.databinding.FragmentMatrixProfileBinding
 import im.vector.app.databinding.ViewStubRoomMemberProfileHeaderBinding
-import im.vector.app.features.analytics.plan.Screen
+import im.vector.app.features.analytics.plan.MobileScreen
 import im.vector.app.features.crypto.verification.VerificationBottomSheet
 import im.vector.app.features.displayname.getBestName
 import im.vector.app.features.home.AvatarRenderer
@@ -91,7 +91,7 @@ class RoomMemberProfileFragment @Inject constructor(
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
-        analyticsScreenName = Screen.ScreenName.User
+        analyticsScreenName = MobileScreen.ScreenName.User
     }
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
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 8acf53088d6..251b99e3185 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
@@ -44,7 +44,7 @@ import im.vector.app.core.utils.copyToClipboard
 import im.vector.app.core.utils.startSharePlainTextIntent
 import im.vector.app.databinding.FragmentMatrixProfileBinding
 import im.vector.app.databinding.ViewStubRoomProfileHeaderBinding
-import im.vector.app.features.analytics.plan.Screen
+import im.vector.app.features.analytics.plan.MobileScreen
 import im.vector.app.features.home.AvatarRenderer
 import im.vector.app.features.home.room.detail.RoomDetailPendingAction
 import im.vector.app.features.home.room.detail.RoomDetailPendingActionStore
@@ -89,7 +89,7 @@ class RoomProfileFragment @Inject constructor(
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
-        analyticsScreenName = Screen.ScreenName.RoomSettings
+        analyticsScreenName = MobileScreen.ScreenName.RoomSettings
         setFragmentResultListener(MigrateRoomBottomSheet.REQUEST_KEY) { _, bundle ->
             bundle.getString(MigrateRoomBottomSheet.BUNDLE_KEY_REPLACEMENT_ROOM)?.let { replacementRoomId ->
                 roomDetailPendingActionStore.data = RoomDetailPendingAction.OpenRoom(replacementRoomId, closeCurrentRoom = true)
diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsFragment.kt
index 3c1a7630724..a0adf42d5be 100644
--- a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsFragment.kt
@@ -34,7 +34,7 @@ import im.vector.app.core.platform.VectorBaseFragment
 import im.vector.app.core.utils.saveMedia
 import im.vector.app.core.utils.shareMedia
 import im.vector.app.databinding.FragmentRoomUploadsBinding
-import im.vector.app.features.analytics.plan.Screen
+import im.vector.app.features.analytics.plan.MobileScreen
 import im.vector.app.features.home.AvatarRenderer
 import im.vector.app.features.notifications.NotificationUtils
 import im.vector.app.features.roomprofile.RoomProfileArgs
@@ -57,7 +57,7 @@ class RoomUploadsFragment @Inject constructor(
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
-        analyticsScreenName = Screen.ScreenName.RoomUploads
+        analyticsScreenName = MobileScreen.ScreenName.RoomUploads
     }
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsBaseFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsBaseFragment.kt
index 7cefd202694..4185fde6636 100644
--- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsBaseFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsBaseFragment.kt
@@ -30,7 +30,7 @@ import im.vector.app.core.extensions.singletonEntryPoint
 import im.vector.app.core.platform.VectorBaseActivity
 import im.vector.app.core.utils.toast
 import im.vector.app.features.analytics.AnalyticsTracker
-import im.vector.app.features.analytics.plan.Screen
+import im.vector.app.features.analytics.plan.MobileScreen
 import im.vector.app.features.analytics.screen.ScreenEvent
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
@@ -43,7 +43,7 @@ abstract class VectorSettingsBaseFragment : PreferenceFragmentCompat(), Maverick
      * Analytics
      * ========================================================================================== */
 
-    protected var analyticsScreenName: Screen.ScreenName? = null
+    protected var analyticsScreenName: MobileScreen.ScreenName? = null
     private var screenEvent: ScreenEvent? = null
 
     protected lateinit var analyticsTracker: AnalyticsTracker
diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsRootFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsRootFragment.kt
index cd76efac58d..51011e29a2b 100644
--- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsRootFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsRootFragment.kt
@@ -19,7 +19,7 @@ package im.vector.app.features.settings
 import android.os.Bundle
 import im.vector.app.R
 import im.vector.app.core.preference.VectorPreference
-import im.vector.app.features.analytics.plan.Screen
+import im.vector.app.features.analytics.plan.MobileScreen
 import javax.inject.Inject
 
 class VectorSettingsRootFragment @Inject constructor() : VectorSettingsBaseFragment() {
@@ -29,7 +29,7 @@ class VectorSettingsRootFragment @Inject constructor() : VectorSettingsBaseFragm
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
-        analyticsScreenName = Screen.ScreenName.MobileSettings
+        analyticsScreenName = MobileScreen.ScreenName.Settings
     }
 
     override fun bindPref() {
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 e4e287e83ab..ef87d908ea8 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
@@ -51,7 +51,7 @@ import im.vector.app.core.utils.copyToClipboard
 import im.vector.app.core.utils.openFileSelection
 import im.vector.app.core.utils.toast
 import im.vector.app.databinding.DialogImportE2eKeysBinding
-import im.vector.app.features.analytics.plan.Screen
+import im.vector.app.features.analytics.plan.MobileScreen
 import im.vector.app.features.analytics.ui.consent.AnalyticsConsentViewActions
 import im.vector.app.features.analytics.ui.consent.AnalyticsConsentViewModel
 import im.vector.app.features.analytics.ui.consent.AnalyticsConsentViewState
@@ -94,7 +94,7 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
-        analyticsScreenName = Screen.ScreenName.MobileSettingsSecurity
+        analyticsScreenName = MobileScreen.ScreenName.SettingsSecurity
     }
 
     // cryptography
diff --git a/vector/src/main/java/im/vector/app/features/settings/account/deactivation/DeactivateAccountFragment.kt b/vector/src/main/java/im/vector/app/features/settings/account/deactivation/DeactivateAccountFragment.kt
index 867526c0091..631c375e62c 100644
--- a/vector/src/main/java/im/vector/app/features/settings/account/deactivation/DeactivateAccountFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/account/deactivation/DeactivateAccountFragment.kt
@@ -31,7 +31,7 @@ import im.vector.app.core.platform.VectorBaseFragment
 import im.vector.app.databinding.FragmentDeactivateAccountBinding
 import im.vector.app.features.MainActivity
 import im.vector.app.features.MainActivityArgs
-import im.vector.app.features.analytics.plan.Screen
+import im.vector.app.features.analytics.plan.MobileScreen
 import im.vector.app.features.auth.ReAuthActivity
 import im.vector.app.features.settings.VectorSettingsActivity
 import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
@@ -66,7 +66,7 @@ class DeactivateAccountFragment @Inject constructor() : VectorBaseFragment
Date: Mon, 14 Feb 2022 15:53:34 +0000
Subject: [PATCH 369/581] providing more alternatives when Matrix.getInstance
 fails

---
 .../src/main/java/org/matrix/android/sdk/api/Matrix.kt         | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt
index a121b8a85d3..7df1912b0c3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt
@@ -129,7 +129,8 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
                     instance = Matrix(appContext, matrixConfiguration)
                 } else {
                     throw IllegalStateException("Matrix is not initialized properly." +
-                            " You should call Matrix.initialize or let your application implements MatrixConfiguration.Provider.")
+                            " If you want to manage your own Matrix instance use Matrix.createInstance" +
+                            " otherwise you should call Matrix.initialize or let your application implement MatrixConfiguration.Provider.")
                 }
             }
             return instance

From 95df3e7e2b4ee249995eb9b3e90fb188dc5d6aa2 Mon Sep 17 00:00:00 2001
From: Adam Brown 
Date: Mon, 14 Feb 2022 16:04:47 +0000
Subject: [PATCH 370/581] deprecating the Matrix.initialize and
 Matrix.getInstance entry points in favour of clients controlling their own
 instances

---
 changelog.d/5185.sdk                                            | 2 +-
 .../src/main/java/org/matrix/android/sdk/api/Matrix.kt          | 2 ++
 .../main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt | 1 +
 3 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/changelog.d/5185.sdk b/changelog.d/5185.sdk
index 14071c29616..9eda2e7c9b8 100644
--- a/changelog.d/5185.sdk
+++ b/changelog.d/5185.sdk
@@ -1 +1 @@
-Avoids using Matrix.getInstance() within the SyncService and instead relies on the SDK client to provide the matrix instance
\ No newline at end of file
+Deprecates Matrix.initialize and Matrix.getInstance in favour of the client providing its own singleton instance via Matrix.createInstance
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt
index 7df1912b0c3..269a885d0fc 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt
@@ -111,6 +111,7 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
          * Initializes a singleton instance of Matrix for the given MatrixConfiguration
          * This instance will be returned by Matrix.getInstance(context)
          */
+        @Deprecated("Use Matrix.createInstance and manage the instance manually")
         fun initialize(context: Context, matrixConfiguration: MatrixConfiguration) {
             if (isInit.compareAndSet(false, true)) {
                 instance = Matrix(context.applicationContext, matrixConfiguration)
@@ -121,6 +122,7 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
          * Either provides an already initialized singleton Matrix instance or queries the application context for a MatrixConfiguration.Provider
          * to lazily create and store the instance.
          */
+        @Deprecated("Use Matrix.createInstance and manage the instance manually")
         fun getInstance(context: Context): Matrix {
             if (isInit.compareAndSet(false, true)) {
                 val appContext = context.applicationContext
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt
index 306ed455003..c87f21d7ac3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt
@@ -66,6 +66,7 @@ data class MatrixConfiguration(
     /**
      * Can be implemented by your Application class.
      */
+    @Deprecated("Use Matrix.createInstance and manage the instance manually instead of Matrix.getInstance")
     interface Provider {
         fun providesMatrixConfiguration(): MatrixConfiguration
     }

From ffd2a762aff34dd9c069131e5db16db8c7652963 Mon Sep 17 00:00:00 2001
From: Benoit Marty 
Date: Mon, 14 Feb 2022 17:07:52 +0100
Subject: [PATCH 371/581] Let the Activity that created the result intent
 deserialize it.

---
 .../vector/app/features/call/VectorCallActivity.kt   | 11 ++++-------
 .../features/call/transfer/CallTransferActivity.kt   | 12 +++++++++---
 2 files changed, 13 insertions(+), 10 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt
index 65759495529..23c7b799147 100644
--- a/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt
@@ -55,7 +55,6 @@ import im.vector.app.databinding.ActivityCallBinding
 import im.vector.app.features.call.dialpad.CallDialPadBottomSheet
 import im.vector.app.features.call.dialpad.DialPadFragment
 import im.vector.app.features.call.transfer.CallTransferActivity
-import im.vector.app.features.call.transfer.CallTransferResult
 import im.vector.app.features.call.utils.EglUtils
 import im.vector.app.features.call.webrtc.WebRtcCall
 import im.vector.app.features.call.webrtc.WebRtcCallManager
@@ -525,22 +524,20 @@ class VectorCallActivity : VectorBaseActivity(), CallContro
                 val callId = withState(callViewModel) { it.callId }
                 navigator.openCallTransfer(this, callTransferActivityResultLauncher, callId)
             }
-            is VectorCallViewEvents.FailToTransfer -> showSnackbar(getString(R.string.call_transfer_failure))
+            is VectorCallViewEvents.FailToTransfer         -> showSnackbar(getString(R.string.call_transfer_failure))
             null                                           -> {
             }
         }
     }
 
     private val callTransferActivityResultLauncher = registerStartForActivityResult { activityResult ->
-
         when (activityResult.resultCode) {
             Activity.RESULT_CANCELED -> {
                 callViewModel.handle(VectorCallViewActions.CallTransferSelectionCancelled)
             }
-            Activity.RESULT_OK -> {
-                activityResult.data?.extras?.getParcelable(CallTransferActivity.EXTRA_TRANSFER_RESULT)?.also {
-                    callViewModel.handle(VectorCallViewActions.CallTransferSelectionResult(it))
-                }
+            Activity.RESULT_OK       -> {
+                CallTransferActivity.getCallTransferResult(activityResult.data)
+                        ?.let { callViewModel.handle(VectorCallViewActions.CallTransferSelectionResult(it)) }
             }
         }
     }
diff --git a/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferActivity.kt b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferActivity.kt
index d4b77c77390..d8eede6a551 100644
--- a/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferActivity.kt
@@ -26,6 +26,7 @@ import com.google.android.material.tabs.TabLayoutMediator
 import dagger.hilt.android.AndroidEntryPoint
 import im.vector.app.R
 import im.vector.app.core.error.ErrorFormatter
+import im.vector.app.core.extensions.exhaustive
 import im.vector.app.core.platform.VectorBaseActivity
 import im.vector.app.databinding.ActivityCallTransferBinding
 import kotlinx.parcelize.Parcelize
@@ -55,8 +56,8 @@ class CallTransferActivity : VectorBaseActivity() {
 
         callTransferViewModel.observeViewEvents {
             when (it) {
-                is CallTransferViewEvents.Complete        -> handleComplete()
-            }
+                is CallTransferViewEvents.Complete -> handleComplete()
+            }.exhaustive
         }
 
         sectionsPagerAdapter = CallTransferPagerAdapter(this)
@@ -104,11 +105,16 @@ class CallTransferActivity : VectorBaseActivity() {
     }
 
     companion object {
-        const val EXTRA_TRANSFER_RESULT = "EXTRA_TRANSFER_RESULT"
+        private const val EXTRA_TRANSFER_RESULT = "EXTRA_TRANSFER_RESULT"
+
         fun newIntent(context: Context, callId: String): Intent {
             return Intent(context, CallTransferActivity::class.java).also {
                 it.putExtra(Mavericks.KEY_ARG, CallTransferArgs(callId))
             }
         }
+
+        fun getCallTransferResult(intent: Intent?): CallTransferResult? {
+            return intent?.extras?.getParcelable(EXTRA_TRANSFER_RESULT)
+        }
     }
 }

From 5b851f1cb77a25326c849ea24282609c7ac8dec1 Mon Sep 17 00:00:00 2001
From: Adam Brown 
Date: Mon, 14 Feb 2022 16:33:25 +0000
Subject: [PATCH 372/581] suppressing deprecated warning on an unused method

---
 .../src/main/java/org/matrix/android/sdk/api/Matrix.kt           | 1 +
 1 file changed, 1 insertion(+)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt
index 43f8dbd6566..5fedff53f0d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt
@@ -122,6 +122,7 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
          * Either provides an already initialized singleton Matrix instance or queries the application context for a MatrixConfiguration.Provider
          * to lazily create and store the instance.
          */
+        @Suppress("deprecation") // suppressing warning as this method is unused but is still provided for SDK clients
         @Deprecated("Use Matrix.createInstance and manage the instance manually")
         fun getInstance(context: Context): Matrix {
             if (isInit.compareAndSet(false, true)) {

From 580ecc9c44c05d16187ad6700c28b24b5a6e7bca Mon Sep 17 00:00:00 2001
From: Adam Brown 
Date: Tue, 15 Feb 2022 12:19:39 +0000
Subject: [PATCH 373/581] adding lateinit user property factory for use when
 calling the initial identify tracking - this will allow us send the pending
 onboarding properties once consent is given

---
 .../impl/LateInitUserPropertiesFactory.kt     | 36 +++++++++
 .../impl/LateInitUserPropertiesFactoryTest.kt | 73 +++++++++++++++++++
 .../test/fakes/FakeActiveSessionDataSource.kt | 30 ++++++++
 .../im/vector/app/test/fakes/FakeSession.kt   | 16 ++++
 .../vector/app/test/fakes/FakeVectorStore.kt  | 34 +++++++++
 5 files changed, 189 insertions(+)
 create mode 100644 vector/src/main/java/im/vector/app/features/analytics/impl/LateInitUserPropertiesFactory.kt
 create mode 100644 vector/src/test/java/im/vector/app/features/analytics/impl/LateInitUserPropertiesFactoryTest.kt
 create mode 100644 vector/src/test/java/im/vector/app/test/fakes/FakeActiveSessionDataSource.kt
 create mode 100644 vector/src/test/java/im/vector/app/test/fakes/FakeVectorStore.kt

diff --git a/vector/src/main/java/im/vector/app/features/analytics/impl/LateInitUserPropertiesFactory.kt b/vector/src/main/java/im/vector/app/features/analytics/impl/LateInitUserPropertiesFactory.kt
new file mode 100644
index 00000000000..d961ceaadc1
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/analytics/impl/LateInitUserPropertiesFactory.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.ActiveSessionDataSource
+import im.vector.app.core.extensions.vectorStore
+import im.vector.app.features.analytics.extensions.toTrackingValue
+import im.vector.app.features.analytics.plan.UserProperties
+import javax.inject.Inject
+
+class LateInitUserPropertiesFactory @Inject constructor(
+        private val activeSessionDataSource: ActiveSessionDataSource,
+        private val context: Context,
+) {
+    suspend fun createUserProperties(): UserProperties? {
+        val useCase = activeSessionDataSource.currentValue?.orNull()?.vectorStore(context)?.readUseCase()
+        return useCase?.let {
+            UserProperties(ftueUseCaseSelection = it.toTrackingValue())
+        }
+    }
+}
diff --git a/vector/src/test/java/im/vector/app/features/analytics/impl/LateInitUserPropertiesFactoryTest.kt b/vector/src/test/java/im/vector/app/features/analytics/impl/LateInitUserPropertiesFactoryTest.kt
new file mode 100644
index 00000000000..c2fa50f7895
--- /dev/null
+++ b/vector/src/test/java/im/vector/app/features/analytics/impl/LateInitUserPropertiesFactoryTest.kt
@@ -0,0 +1,73 @@
+/*
+ * 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 im.vector.app.features.analytics.plan.UserProperties
+import im.vector.app.features.onboarding.FtueUseCase
+import im.vector.app.test.fakes.FakeActiveSessionDataSource
+import im.vector.app.test.fakes.FakeContext
+import im.vector.app.test.fakes.FakeSession
+import im.vector.app.test.fakes.FakeVectorStore
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runBlockingTest
+import org.amshove.kluent.shouldBeEqualTo
+import org.junit.Test
+
+@ExperimentalCoroutinesApi
+class LateInitUserPropertiesFactoryTest {
+
+    private val fakeActiveSessionDataSource = FakeActiveSessionDataSource()
+    private val fakeVectorStore = FakeVectorStore()
+    private val fakeContext = FakeContext()
+    private val fakeSession = FakeSession().also {
+        it.givenVectorStore(fakeVectorStore.instance)
+    }
+
+    private val lateInitUserProperties = LateInitUserPropertiesFactory(
+            fakeActiveSessionDataSource.instance,
+            fakeContext.instance
+    )
+
+    @Test
+    fun `given no active session when creating properties then returns null`() = runBlockingTest {
+        val result = lateInitUserProperties.createUserProperties()
+
+        result shouldBeEqualTo null
+    }
+
+    @Test
+    fun `given no use case set on an active session when creating properties then returns null`() = runBlockingTest {
+        fakeVectorStore.givenUseCase(null)
+        fakeSession.givenVectorStore(fakeVectorStore.instance)
+        fakeActiveSessionDataSource.setActiveSession(fakeSession)
+
+        val result = lateInitUserProperties.createUserProperties()
+
+        result shouldBeEqualTo null
+    }
+
+    @Test
+    fun `given use case set on an active session when creating properties then includes the use case`() = runBlockingTest {
+        fakeVectorStore.givenUseCase(FtueUseCase.TEAMS)
+        fakeActiveSessionDataSource.setActiveSession(fakeSession)
+        val result = lateInitUserProperties.createUserProperties()
+
+        result shouldBeEqualTo UserProperties(
+                ftueUseCaseSelection = UserProperties.FtueUseCaseSelection.WorkMessaging
+        )
+    }
+}
diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeActiveSessionDataSource.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeActiveSessionDataSource.kt
new file mode 100644
index 00000000000..4dab6daf3bd
--- /dev/null
+++ b/vector/src/test/java/im/vector/app/test/fakes/FakeActiveSessionDataSource.kt
@@ -0,0 +1,30 @@
+/*
+ * 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 arrow.core.Option
+import im.vector.app.ActiveSessionDataSource
+import org.matrix.android.sdk.api.session.Session
+
+class FakeActiveSessionDataSource {
+
+    val instance = ActiveSessionDataSource()
+
+    fun setActiveSession(session: Session) {
+        instance.post(Option.just(session))
+    }
+}
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 91403b3b2c4..a23c43b9868 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
@@ -16,8 +16,12 @@
 
 package im.vector.app.test.fakes
 
+import im.vector.app.core.extensions.vectorStore
+import im.vector.app.features.session.VectorSessionStore
 import im.vector.app.test.testCoroutineDispatchers
+import io.mockk.coEvery
 import io.mockk.mockk
+import io.mockk.mockkStatic
 import org.matrix.android.sdk.api.session.Session
 
 class FakeSession(
@@ -25,7 +29,19 @@ class FakeSession(
         val fakeSharedSecretStorageService: FakeSharedSecretStorageService = FakeSharedSecretStorageService()
 ) : Session by mockk(relaxed = true) {
 
+    init {
+        mockkStatic("im.vector.app.core.extensions.SessionKt")
+    }
+
     override fun cryptoService() = fakeCryptoService
     override val sharedSecretStorageService = fakeSharedSecretStorageService
     override val coroutineDispatchers = testCoroutineDispatchers
+
+    fun givenVectorStore(vectorSessionStore: VectorSessionStore) {
+        coEvery {
+            this@FakeSession.vectorStore(any())
+        } coAnswers {
+            vectorSessionStore
+        }
+    }
 }
diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeVectorStore.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeVectorStore.kt
new file mode 100644
index 00000000000..22a4a5f6cf7
--- /dev/null
+++ b/vector/src/test/java/im/vector/app/test/fakes/FakeVectorStore.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.onboarding.FtueUseCase
+import im.vector.app.features.session.VectorSessionStore
+import io.mockk.coEvery
+import io.mockk.mockk
+
+class FakeVectorStore {
+    val instance = mockk()
+
+    fun givenUseCase(useCase: FtueUseCase?) {
+        coEvery {
+            instance.readUseCase()
+        } coAnswers {
+            useCase
+        }
+    }
+}

From f1f8f518052af3259f2d611cee15945f069287cb Mon Sep 17 00:00:00 2001
From: Adam Brown 
Date: Tue, 15 Feb 2022 12:26:08 +0000
Subject: [PATCH 374/581] lifting the global scope to a provide to allow for
 unit testing the analytics impl

---
 .../im/vector/app/core/di/NamedGlobalScope.kt | 23 +++++++++++++++++++
 .../im/vector/app/core/di/SingletonModule.kt  |  8 +++++++
 .../analytics/impl/DefaultVectorAnalytics.kt  | 12 +++++-----
 3 files changed, 37 insertions(+), 6 deletions(-)
 create mode 100644 vector/src/main/java/im/vector/app/core/di/NamedGlobalScope.kt

diff --git a/vector/src/main/java/im/vector/app/core/di/NamedGlobalScope.kt b/vector/src/main/java/im/vector/app/core/di/NamedGlobalScope.kt
new file mode 100644
index 00000000000..cc1ac829a1a
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/core/di/NamedGlobalScope.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 im.vector.app.core.di
+
+import javax.inject.Qualifier
+
+@Qualifier
+@Retention(AnnotationRetention.RUNTIME)
+annotation class NamedGlobalScope
diff --git a/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt b/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt
index 0e19cd43881..94d8df36921 100644
--- a/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt
+++ b/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt
@@ -46,6 +46,7 @@ import im.vector.app.features.ui.SharedPreferencesUiStateRepository
 import im.vector.app.features.ui.UiStateRepository
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
 import kotlinx.coroutines.SupervisorJob
 import org.matrix.android.sdk.api.Matrix
 import org.matrix.android.sdk.api.auth.AuthenticationService
@@ -147,4 +148,11 @@ object VectorStaticModule {
     fun providesCoroutineDispatchers(): CoroutineDispatchers {
         return CoroutineDispatchers(io = Dispatchers.IO, computation = Dispatchers.Default)
     }
+
+    @Suppress("EXPERIMENTAL_API_USAGE")
+    @Provides
+    @NamedGlobalScope
+    fun providesGlobalScope(): CoroutineScope {
+        return GlobalScope
+    }
 }
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 6dbf412d836..3694ef7e09c 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
@@ -22,13 +22,14 @@ import com.posthog.android.PostHog
 import com.posthog.android.Properties
 import im.vector.app.BuildConfig
 import im.vector.app.config.analyticsConfig
+import im.vector.app.core.di.NamedGlobalScope
 import im.vector.app.features.analytics.VectorAnalytics
 import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
 import im.vector.app.features.analytics.itf.VectorAnalyticsScreen
 import im.vector.app.features.analytics.log.analyticsTag
 import im.vector.app.features.analytics.plan.UserProperties
 import im.vector.app.features.analytics.store.AnalyticsStore
-import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
@@ -42,7 +43,8 @@ private val IGNORED_OPTIONS: Options? = null
 @Singleton
 class DefaultVectorAnalytics @Inject constructor(
         private val context: Context,
-        private val analyticsStore: AnalyticsStore
+        private val analyticsStore: AnalyticsStore,
+        @NamedGlobalScope private val globalScope: CoroutineScope
 ) : VectorAnalytics {
     private var posthog: PostHog? = null
 
@@ -88,7 +90,6 @@ class DefaultVectorAnalytics @Inject constructor(
         createAnalyticsClient()
     }
 
-    @Suppress("EXPERIMENTAL_API_USAGE")
     private fun observeAnalyticsId() {
         getAnalyticsId()
                 .onEach { id ->
@@ -96,7 +97,7 @@ class DefaultVectorAnalytics @Inject constructor(
                     analyticsId = id
                     identifyPostHog()
                 }
-                .launchIn(GlobalScope)
+                .launchIn(globalScope)
     }
 
     private fun identifyPostHog() {
@@ -110,7 +111,6 @@ class DefaultVectorAnalytics @Inject constructor(
         }
     }
 
-    @Suppress("EXPERIMENTAL_API_USAGE")
     private fun observeUserConsent() {
         getUserConsent()
                 .onEach { consent ->
@@ -118,7 +118,7 @@ class DefaultVectorAnalytics @Inject constructor(
                     userConsent = consent
                     optOutPostHog()
                 }
-                .launchIn(GlobalScope)
+                .launchIn(globalScope)
     }
 
     private fun optOutPostHog() {

From 837caabcec07ec31cb2efeed5b1f57da94bb7310 Mon Sep 17 00:00:00 2001
From: Adam Brown 
Date: Tue, 15 Feb 2022 12:51:21 +0000
Subject: [PATCH 375/581] providing the posthog creation and analytics config
 via hilt in order to make the analytics impl testable

---
 .../im/vector/app/core/di/SingletonModule.kt  |  7 +++
 .../analytics/impl/DefaultVectorAnalytics.kt  | 33 ++----------
 .../features/analytics/impl/PostHogFactory.kt | 52 +++++++++++++++++++
 3 files changed, 64 insertions(+), 28 deletions(-)
 create mode 100644 vector/src/main/java/im/vector/app/features/analytics/impl/PostHogFactory.kt

diff --git a/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt b/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt
index 94d8df36921..56ae63a682e 100644
--- a/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt
+++ b/vector/src/main/java/im/vector/app/core/di/SingletonModule.kt
@@ -28,11 +28,13 @@ import dagger.hilt.InstallIn
 import dagger.hilt.components.SingletonComponent
 import im.vector.app.EmojiCompatWrapper
 import im.vector.app.EmojiSpanify
+import im.vector.app.config.analyticsConfig
 import im.vector.app.core.dispatchers.CoroutineDispatchers
 import im.vector.app.core.error.DefaultErrorFormatter
 import im.vector.app.core.error.ErrorFormatter
 import im.vector.app.core.time.Clock
 import im.vector.app.core.time.DefaultClock
+import im.vector.app.features.analytics.AnalyticsConfig
 import im.vector.app.features.analytics.AnalyticsTracker
 import im.vector.app.features.analytics.VectorAnalytics
 import im.vector.app.features.analytics.impl.DefaultVectorAnalytics
@@ -155,4 +157,9 @@ object VectorStaticModule {
     fun providesGlobalScope(): CoroutineScope {
         return GlobalScope
     }
+
+    @Provides
+    fun providesAnalyticsConfig(): AnalyticsConfig {
+        return analyticsConfig
+    }
 }
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 3694ef7e09c..2cbdbf5b5f2 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
@@ -16,13 +16,11 @@
 
 package im.vector.app.features.analytics.impl
 
-import android.content.Context
 import com.posthog.android.Options
 import com.posthog.android.PostHog
 import com.posthog.android.Properties
-import im.vector.app.BuildConfig
-import im.vector.app.config.analyticsConfig
 import im.vector.app.core.di.NamedGlobalScope
+import im.vector.app.features.analytics.AnalyticsConfig
 import im.vector.app.features.analytics.VectorAnalytics
 import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
 import im.vector.app.features.analytics.itf.VectorAnalyticsScreen
@@ -42,8 +40,9 @@ private val IGNORED_OPTIONS: Options? = null
 
 @Singleton
 class DefaultVectorAnalytics @Inject constructor(
-        private val context: Context,
+        private val postHogFactory: PostHogFactory,
         private val analyticsStore: AnalyticsStore,
+        private val analyticsConfig: AnalyticsConfig,
         @NamedGlobalScope private val globalScope: CoroutineScope
 ) : VectorAnalytics {
     private var posthog: PostHog? = null
@@ -85,9 +84,9 @@ class DefaultVectorAnalytics @Inject constructor(
     }
 
     override fun init() {
+        createAnalyticsClient()
         observeUserConsent()
         observeAnalyticsId()
-        createAnalyticsClient()
     }
 
     private fun observeAnalyticsId() {
@@ -133,34 +132,12 @@ class DefaultVectorAnalytics @Inject constructor(
             return
         }
 
-        posthog = PostHog.Builder(context, analyticsConfig.postHogApiKey, analyticsConfig.postHogHost)
-                // Record certain application events automatically! (off/false by default)
-                // .captureApplicationLifecycleEvents()
-                // Record screen views automatically! (off/false by default)
-                // .recordScreenViews()
-                // Capture deep links as part of the screen call. (off by default)
-                // .captureDeepLinks()
-                // Maximum number of events to keep in queue before flushing (default 20)
-                // .flushQueueSize(20)
-                // Max delay before flushing the queue (30 seconds)
-                // .flushInterval(30, TimeUnit.SECONDS)
-                // Enable or disable collection of ANDROID_ID (true)
-                .collectDeviceId(false)
-                .logLevel(getLogLevel())
-                .build()
+        posthog = postHogFactory.createPosthog()
 
         optOutPostHog()
         identifyPostHog()
     }
 
-    private fun getLogLevel(): PostHog.LogLevel {
-        return if (BuildConfig.DEBUG) {
-            PostHog.LogLevel.DEBUG
-        } else {
-            PostHog.LogLevel.INFO
-        }
-    }
-
     override fun capture(event: VectorAnalyticsEvent) {
         Timber.tag(analyticsTag.value).d("capture($event)")
         posthog
diff --git a/vector/src/main/java/im/vector/app/features/analytics/impl/PostHogFactory.kt b/vector/src/main/java/im/vector/app/features/analytics/impl/PostHogFactory.kt
new file mode 100644
index 00000000000..029732f76cd
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/analytics/impl/PostHogFactory.kt
@@ -0,0 +1,52 @@
+/*
+ * 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 com.posthog.android.PostHog
+import im.vector.app.BuildConfig
+import im.vector.app.config.analyticsConfig
+import javax.inject.Inject
+
+class PostHogFactory @Inject constructor(private val context: Context) {
+
+    fun createPosthog(): PostHog {
+        return PostHog.Builder(context, analyticsConfig.postHogApiKey, analyticsConfig.postHogHost)
+                // Record certain application events automatically! (off/false by default)
+                // .captureApplicationLifecycleEvents()
+                // Record screen views automatically! (off/false by default)
+                // .recordScreenViews()
+                // Capture deep links as part of the screen call. (off by default)
+                // .captureDeepLinks()
+                // Maximum number of events to keep in queue before flushing (default 20)
+                // .flushQueueSize(20)
+                // Max delay before flushing the queue (30 seconds)
+                // .flushInterval(30, TimeUnit.SECONDS)
+                // Enable or disable collection of ANDROID_ID (true)
+                .collectDeviceId(false)
+                .logLevel(getLogLevel())
+                .build()
+    }
+
+    private fun getLogLevel(): PostHog.LogLevel {
+        return if (BuildConfig.DEBUG) {
+            PostHog.LogLevel.DEBUG
+        } else {
+            PostHog.LogLevel.INFO
+        }
+    }
+}

From e36e67c54cbc82e3cd15c5117a7c91a374176bcc Mon Sep 17 00:00:00 2001
From: Adam Brown 
Date: Tue, 15 Feb 2022 14:23:26 +0000
Subject: [PATCH 376/581] adding unit tests around the analytics impl

---
 .../analytics/impl/DefaultVectorAnalytics.kt  |   4 +
 .../impl/DefaultVectorAnalyticsTest.kt        | 144 ++++++++++++++++++
 .../app/test/fakes/FakeAnalyticsStore.kt      |  58 +++++++
 .../im/vector/app/test/fakes/FakePostHog.kt   |  75 +++++++++
 .../app/test/fakes/FakePostHogFactory.kt      |  28 ++++
 .../test/fixtures/AnalyticsConfigFixture.kt   |  33 ++++
 6 files changed, 342 insertions(+)
 create mode 100644 vector/src/test/java/im/vector/app/features/analytics/impl/DefaultVectorAnalyticsTest.kt
 create mode 100644 vector/src/test/java/im/vector/app/test/fakes/FakeAnalyticsStore.kt
 create mode 100644 vector/src/test/java/im/vector/app/test/fakes/FakePostHog.kt
 create mode 100644 vector/src/test/java/im/vector/app/test/fakes/FakePostHogFactory.kt
 create mode 100644 vector/src/test/java/im/vector/app/test/fixtures/AnalyticsConfigFixture.kt

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 2cbdbf5b5f2..37649d7c39b 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
@@ -113,6 +113,7 @@ class DefaultVectorAnalytics @Inject constructor(
     private fun observeUserConsent() {
         getUserConsent()
                 .onEach { consent ->
+                    println("!!!, got consent: $consent")
                     Timber.tag(analyticsTag.value).d("User consent updated to $consent")
                     userConsent = consent
                     optOutPostHog()
@@ -147,6 +148,9 @@ class DefaultVectorAnalytics @Inject constructor(
 
     override fun screen(screen: VectorAnalyticsScreen) {
         Timber.tag(analyticsTag.value).d("screen($screen)")
+
+        println("userconsnet: $userConsent")
+
         posthog
                 ?.takeIf { userConsent == true }
                 ?.screen(screen.getName(), screen.getProperties()?.toPostHogProperties())
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
new file mode 100644
index 00000000000..2680979d7ec
--- /dev/null
+++ b/vector/src/test/java/im/vector/app/features/analytics/impl/DefaultVectorAnalyticsTest.kt
@@ -0,0 +1,144 @@
+/*
+ * 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 com.posthog.android.Properties
+import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
+import im.vector.app.features.analytics.itf.VectorAnalyticsScreen
+import im.vector.app.test.fakes.FakeAnalyticsStore
+import im.vector.app.test.fakes.FakePostHog
+import im.vector.app.test.fakes.FakePostHogFactory
+import im.vector.app.test.fixtures.AnalyticsConfigFixture.anAnalyticsConfig
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Before
+import org.junit.Test
+
+private const val AN_ANALYTICS_ID = "analytics-id"
+private val A_SCREEN_EVENT = object : VectorAnalyticsScreen {
+    override fun getName() = "a-screen-event-name"
+    override fun getProperties() = mapOf("property-name" to "property-value")
+}
+private val AN_EVENT = object : VectorAnalyticsEvent {
+    override fun getName() = "an-event-name"
+    override fun getProperties() = mapOf("property-name" to "property-value")
+}
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class DefaultVectorAnalyticsTest {
+
+    private val fakePostHog = FakePostHog()
+    private val fakeAnalyticsStore = FakeAnalyticsStore()
+
+    private val defaultVectorAnalytics = DefaultVectorAnalytics(
+            postHogFactory = FakePostHogFactory(fakePostHog.instance).instance,
+            analyticsStore = fakeAnalyticsStore.instance,
+            globalScope = CoroutineScope(Dispatchers.Unconfined),
+            analyticsConfig = anAnalyticsConfig(isEnabled = true)
+    )
+
+    @Before
+    fun setUp() {
+        defaultVectorAnalytics.init()
+    }
+
+    @Test
+    fun `when setting user consent then updates analytics store`() = runBlockingTest {
+        defaultVectorAnalytics.setUserConsent(true)
+
+        fakeAnalyticsStore.verifyConsentUpdated(updatedValue = true)
+    }
+
+    @Test
+    fun `when consenting to analytics then updates posthog opt out to false`() = runBlockingTest {
+        fakeAnalyticsStore.givenUserContent(consent = true)
+
+        fakePostHog.verifyOptOutStatus(optedOut = false)
+    }
+
+    @Test
+    fun `when revoking consent to analytics then updates posthog opt out to true`() = runBlockingTest {
+        fakeAnalyticsStore.givenUserContent(consent = false)
+
+        fakePostHog.verifyOptOutStatus(optedOut = true)
+    }
+
+    @Test
+    fun `when setting the analytics id then updates analytics store`() = runBlockingTest {
+        defaultVectorAnalytics.setAnalyticsId(AN_ANALYTICS_ID)
+
+        fakeAnalyticsStore.verifyAnalyticsIdUpdated(updatedValue = AN_ANALYTICS_ID)
+    }
+
+    @Test
+    fun `when valid analytics id updates then identify`() = runBlockingTest {
+        fakeAnalyticsStore.givenAnalyticsId(AN_ANALYTICS_ID)
+
+        fakePostHog.verifyIdentifies(AN_ANALYTICS_ID)
+    }
+
+    @Test
+    fun `when signing out analytics id updates then resets`() = runBlockingTest {
+        fakeAnalyticsStore.allowSettingAnalyticsIdToCallBackingFlow()
+
+        defaultVectorAnalytics.onSignOut()
+
+        fakePostHog.verifyReset()
+    }
+
+    @Test
+    fun `given user consent when tracking screen events then submits to posthog`() = runBlockingTest {
+        fakeAnalyticsStore.givenUserContent(consent = true)
+
+        defaultVectorAnalytics.screen(A_SCREEN_EVENT)
+
+        fakePostHog.verifyScreenTracked(A_SCREEN_EVENT.getName(), Properties().also {
+            it.putAll(A_SCREEN_EVENT.getProperties())
+        })
+    }
+
+    @Test
+    fun `given user has not consented when tracking screen events then does not track`() = runBlockingTest {
+        fakeAnalyticsStore.givenUserContent(consent = false)
+
+        defaultVectorAnalytics.screen(A_SCREEN_EVENT)
+
+        fakePostHog.verifyNoScreenTracking()
+    }
+
+    @Test
+    fun `given user consent when tracking events then submits to posthog`() = runBlockingTest {
+        fakeAnalyticsStore.givenUserContent(consent = true)
+
+        defaultVectorAnalytics.capture(AN_EVENT)
+
+        fakePostHog.verifyEventTracked(AN_EVENT.getName(), Properties().also {
+            it.putAll(AN_EVENT.getProperties())
+        })
+    }
+
+    @Test
+    fun `given user has not consented when tracking events then does not track`() = runBlockingTest {
+        fakeAnalyticsStore.givenUserContent(consent = false)
+
+        defaultVectorAnalytics.capture(AN_EVENT)
+
+        fakePostHog.verifyNoEventTracking()
+    }
+}
diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeAnalyticsStore.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeAnalyticsStore.kt
new file mode 100644
index 00000000000..6304da8d379
--- /dev/null
+++ b/vector/src/test/java/im/vector/app/test/fakes/FakeAnalyticsStore.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.store.AnalyticsStore
+import io.mockk.coEvery
+import io.mockk.coVerify
+import io.mockk.every
+import io.mockk.mockk
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.runBlocking
+
+class FakeAnalyticsStore {
+
+    private val _consentFlow = MutableSharedFlow()
+    private val _idFlow = MutableSharedFlow()
+
+    val instance = mockk(relaxed = true) {
+        every { userConsentFlow } returns _consentFlow
+        every { analyticsIdFlow } returns _idFlow
+    }
+
+    fun allowSettingAnalyticsIdToCallBackingFlow() {
+        coEvery { instance.setAnalyticsId(any()) } answers {
+            runBlocking { _idFlow.emit(firstArg()) }
+        }
+    }
+
+    fun verifyConsentUpdated(updatedValue: Boolean) {
+        coVerify { instance.setUserConsent(updatedValue) }
+    }
+
+    suspend fun givenUserContent(consent: Boolean) {
+        _consentFlow.emit(consent)
+    }
+
+    fun verifyAnalyticsIdUpdated(updatedValue: String) {
+        coVerify { instance.setAnalyticsId(updatedValue) }
+    }
+
+    suspend fun givenAnalyticsId(id: String) {
+        _idFlow.emit(id)
+    }
+}
diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakePostHog.kt b/vector/src/test/java/im/vector/app/test/fakes/FakePostHog.kt
new file mode 100644
index 00000000000..631e09aada7
--- /dev/null
+++ b/vector/src/test/java/im/vector/app/test/fakes/FakePostHog.kt
@@ -0,0 +1,75 @@
+/*
+ * 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 android.os.Looper
+import com.posthog.android.PostHog
+import com.posthog.android.Properties
+import io.mockk.every
+import io.mockk.mockk
+import io.mockk.mockkStatic
+import io.mockk.verify
+
+class FakePostHog {
+
+    init {
+        // workaround to avoid PostHog.HANDLER failing
+        mockkStatic(Looper::class)
+        val looper = mockk {
+            every { thread } returns Thread.currentThread()
+        }
+        every { Looper.getMainLooper() } returns looper
+    }
+
+    val instance = mockk(relaxed = true)
+
+    fun verifyOptOutStatus(optedOut: Boolean) {
+        verify { instance.optOut(optedOut) }
+    }
+
+    fun verifyIdentifies(analyticsId: String) {
+        verify { instance.identify(analyticsId) }
+    }
+
+    fun verifyReset() {
+        verify { instance.reset() }
+    }
+
+    fun verifyScreenTracked(name: String, properties: Properties) {
+        verify { instance.screen(name, properties) }
+    }
+
+    fun verifyNoScreenTracking() {
+        verify(exactly = 0) {
+            instance.screen(any())
+            instance.screen(any(), any())
+            instance.screen(any(), any(), any())
+        }
+    }
+
+    fun verifyEventTracked(name: String, properties: Properties) {
+        verify { instance.capture(name, properties) }
+    }
+
+    fun verifyNoEventTracking() {
+        verify(exactly = 0) {
+            instance.capture(any())
+            instance.capture(any(), any())
+            instance.capture(any(), any(), any())
+        }
+    }
+}
diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakePostHogFactory.kt b/vector/src/test/java/im/vector/app/test/fakes/FakePostHogFactory.kt
new file mode 100644
index 00000000000..1d18c97d325
--- /dev/null
+++ b/vector/src/test/java/im/vector/app/test/fakes/FakePostHogFactory.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 com.posthog.android.PostHog
+import im.vector.app.features.analytics.impl.PostHogFactory
+import io.mockk.every
+import io.mockk.mockk
+
+class FakePostHogFactory(postHog: PostHog) {
+    val instance = mockk().also {
+        every { it.createPosthog() } returns postHog
+    }
+}
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
new file mode 100644
index 00000000000..5fbcdd98d1f
--- /dev/null
+++ b/vector/src/test/java/im/vector/app/test/fixtures/AnalyticsConfigFixture.kt
@@ -0,0 +1,33 @@
+/*
+ * 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 im.vector.app.features.analytics.AnalyticsConfig
+
+object AnalyticsConfigFixture {
+    fun anAnalyticsConfig(
+            isEnabled: Boolean = false,
+            postHogHost: String = "http://posthog.url",
+            postHogApiKey: String = "api-key",
+            policyLink: String = "http://policy.link"
+    ) = object : AnalyticsConfig {
+        override val isEnabled: Boolean = isEnabled
+        override val postHogHost = postHogHost
+        override val postHogApiKey = postHogApiKey
+        override val policyLink = policyLink
+    }
+}

From 3236d87323c63b76b46c96e8697a780548c9ea47 Mon Sep 17 00:00:00 2001
From: Adam Brown 
Date: Tue, 15 Feb 2022 15:39:05 +0000
Subject: [PATCH 377/581] inlining posthog creation and removing eager optout
 and identify calls as the backing values are null which means they aren't
 actually being called

---
 .../analytics/impl/DefaultVectorAnalytics.kt  | 42 +++++++------------
 1 file changed, 15 insertions(+), 27 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/analytics/impl/DefaultVectorAnalytics.kt b/vector/src/main/java/im/vector/app/features/analytics/impl/DefaultVectorAnalytics.kt
index 37649d7c39b..1ccdbd8bbc1 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
@@ -40,17 +40,29 @@ private val IGNORED_OPTIONS: Options? = null
 
 @Singleton
 class DefaultVectorAnalytics @Inject constructor(
-        private val postHogFactory: PostHogFactory,
+        postHogFactory: PostHogFactory,
+        analyticsConfig: AnalyticsConfig,
         private val analyticsStore: AnalyticsStore,
-        private val analyticsConfig: AnalyticsConfig,
         @NamedGlobalScope private val globalScope: CoroutineScope
 ) : VectorAnalytics {
-    private var posthog: PostHog? = null
+
+    private val posthog: PostHog? = when {
+        analyticsConfig.isEnabled -> postHogFactory.createPosthog()
+        else                      -> {
+            Timber.tag(analyticsTag.value).w("Analytics is disabled")
+            null
+        }
+    }
 
     // Cache for the store values
     private var userConsent: Boolean? = null
     private var analyticsId: String? = null
 
+    override fun init() {
+        observeUserConsent()
+        observeAnalyticsId()
+    }
+
     override fun getUserConsent(): Flow {
         return analyticsStore.userConsentFlow
     }
@@ -83,12 +95,6 @@ class DefaultVectorAnalytics @Inject constructor(
         setAnalyticsId("")
     }
 
-    override fun init() {
-        createAnalyticsClient()
-        observeUserConsent()
-        observeAnalyticsId()
-    }
-
     private fun observeAnalyticsId() {
         getAnalyticsId()
                 .onEach { id ->
@@ -113,7 +119,6 @@ class DefaultVectorAnalytics @Inject constructor(
     private fun observeUserConsent() {
         getUserConsent()
                 .onEach { consent ->
-                    println("!!!, got consent: $consent")
                     Timber.tag(analyticsTag.value).d("User consent updated to $consent")
                     userConsent = consent
                     optOutPostHog()
@@ -125,20 +130,6 @@ class DefaultVectorAnalytics @Inject constructor(
         userConsent?.let { posthog?.optOut(!it) }
     }
 
-    private fun createAnalyticsClient() {
-        Timber.tag(analyticsTag.value).d("createAnalyticsClient()")
-
-        if (analyticsConfig.isEnabled.not()) {
-            Timber.tag(analyticsTag.value).w("Analytics is disabled")
-            return
-        }
-
-        posthog = postHogFactory.createPosthog()
-
-        optOutPostHog()
-        identifyPostHog()
-    }
-
     override fun capture(event: VectorAnalyticsEvent) {
         Timber.tag(analyticsTag.value).d("capture($event)")
         posthog
@@ -148,9 +139,6 @@ class DefaultVectorAnalytics @Inject constructor(
 
     override fun screen(screen: VectorAnalyticsScreen) {
         Timber.tag(analyticsTag.value).d("screen($screen)")
-
-        println("userconsnet: $userConsent")
-
         posthog
                 ?.takeIf { userConsent == true }
                 ?.screen(screen.getName(), screen.getProperties()?.toPostHogProperties())

From d99a2f8d14d4c19d2c53c22b9f019d945744c9df Mon Sep 17 00:00:00 2001
From: Adam Brown 
Date: Tue, 15 Feb 2022 16:03:21 +0000
Subject: [PATCH 378/581] creating and passing stored user properties on post
 hog initialisation - this allows information captured during the onboarding
 to be sent once the user has opt'd in

---
 .../analytics/impl/DefaultVectorAnalytics.kt  |  5 +-
 .../impl/DefaultVectorAnalyticsTest.kt        | 47 ++++++++++++-------
 .../FakeLateInitUserPropertiesFactory.kt      | 31 ++++++++++++
 .../im/vector/app/test/fakes/FakePostHog.kt   | 14 ++++--
 .../test/fixtures/UserPropertiesFixture.kt    | 26 ++++++++++
 .../test/fixtures/VectorAnalyticsFixture.kt   | 36 ++++++++++++++
 6 files changed, 135 insertions(+), 24 deletions(-)
 create mode 100644 vector/src/test/java/im/vector/app/test/fakes/FakeLateInitUserPropertiesFactory.kt
 create mode 100644 vector/src/test/java/im/vector/app/test/fixtures/UserPropertiesFixture.kt
 create mode 100644 vector/src/test/java/im/vector/app/test/fixtures/VectorAnalyticsFixture.kt

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 1ccdbd8bbc1..7b653ef44bd 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
@@ -43,6 +43,7 @@ class DefaultVectorAnalytics @Inject constructor(
         postHogFactory: PostHogFactory,
         analyticsConfig: AnalyticsConfig,
         private val analyticsStore: AnalyticsStore,
+        private val lateInitUserPropertiesFactory: LateInitUserPropertiesFactory,
         @NamedGlobalScope private val globalScope: CoroutineScope
 ) : VectorAnalytics {
 
@@ -105,14 +106,14 @@ class DefaultVectorAnalytics @Inject constructor(
                 .launchIn(globalScope)
     }
 
-    private fun identifyPostHog() {
+    private suspend fun identifyPostHog() {
         val id = analyticsId ?: return
         if (id.isEmpty()) {
             Timber.tag(analyticsTag.value).d("reset")
             posthog?.reset()
         } else {
             Timber.tag(analyticsTag.value).d("identify")
-            posthog?.identify(id)
+            posthog?.identify(id, lateInitUserPropertiesFactory.createUserProperties()?.getProperties()?.toPostHogUserProperties(), IGNORED_OPTIONS)
         }
     }
 
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 2680979d7ec..b17c1a8bba7 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
@@ -20,9 +20,13 @@ import com.posthog.android.Properties
 import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
 import im.vector.app.features.analytics.itf.VectorAnalyticsScreen
 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.fixtures.AnalyticsConfigFixture.anAnalyticsConfig
+import im.vector.app.test.fixtures.aUserProperties
+import im.vector.app.test.fixtures.aVectorAnalyticsEvent
+import im.vector.app.test.fixtures.aVectorAnalyticsScreen
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -31,26 +35,23 @@ import org.junit.Before
 import org.junit.Test
 
 private const val AN_ANALYTICS_ID = "analytics-id"
-private val A_SCREEN_EVENT = object : VectorAnalyticsScreen {
-    override fun getName() = "a-screen-event-name"
-    override fun getProperties() = mapOf("property-name" to "property-value")
-}
-private val AN_EVENT = object : VectorAnalyticsEvent {
-    override fun getName() = "an-event-name"
-    override fun getProperties() = mapOf("property-name" to "property-value")
-}
+private val A_SCREEN_EVENT = aVectorAnalyticsScreen()
+private val AN_EVENT = aVectorAnalyticsEvent()
+private val A_LATE_INIT_USER_PROPERTIES = aUserProperties()
 
 @OptIn(ExperimentalCoroutinesApi::class)
 class DefaultVectorAnalyticsTest {
 
     private val fakePostHog = FakePostHog()
     private val fakeAnalyticsStore = FakeAnalyticsStore()
+    private val fakeLateInitUserPropertiesFactory = FakeLateInitUserPropertiesFactory()
 
     private val defaultVectorAnalytics = DefaultVectorAnalytics(
             postHogFactory = FakePostHogFactory(fakePostHog.instance).instance,
             analyticsStore = fakeAnalyticsStore.instance,
             globalScope = CoroutineScope(Dispatchers.Unconfined),
-            analyticsConfig = anAnalyticsConfig(isEnabled = true)
+            analyticsConfig = anAnalyticsConfig(isEnabled = true),
+            lateInitUserPropertiesFactory = fakeLateInitUserPropertiesFactory.instance
     )
 
     @Before
@@ -87,14 +88,16 @@ class DefaultVectorAnalyticsTest {
     }
 
     @Test
-    fun `when valid analytics id updates then identify`() = runBlockingTest {
+    fun `given lateinit user properties when valid analytics id updates then identify with lateinit properties`() = runBlockingTest {
+        fakeLateInitUserPropertiesFactory.givenCreatesProperties(A_LATE_INIT_USER_PROPERTIES)
+
         fakeAnalyticsStore.givenAnalyticsId(AN_ANALYTICS_ID)
 
-        fakePostHog.verifyIdentifies(AN_ANALYTICS_ID)
+        fakePostHog.verifyIdentifies(AN_ANALYTICS_ID, A_LATE_INIT_USER_PROPERTIES)
     }
 
     @Test
-    fun `when signing out analytics id updates then resets`() = runBlockingTest {
+    fun `when signing out then resets posthog`() = runBlockingTest {
         fakeAnalyticsStore.allowSettingAnalyticsIdToCallBackingFlow()
 
         defaultVectorAnalytics.onSignOut()
@@ -108,9 +111,7 @@ class DefaultVectorAnalyticsTest {
 
         defaultVectorAnalytics.screen(A_SCREEN_EVENT)
 
-        fakePostHog.verifyScreenTracked(A_SCREEN_EVENT.getName(), Properties().also {
-            it.putAll(A_SCREEN_EVENT.getProperties())
-        })
+        fakePostHog.verifyScreenTracked(A_SCREEN_EVENT.getName(), A_SCREEN_EVENT.toPostHogProperties())
     }
 
     @Test
@@ -128,9 +129,7 @@ class DefaultVectorAnalyticsTest {
 
         defaultVectorAnalytics.capture(AN_EVENT)
 
-        fakePostHog.verifyEventTracked(AN_EVENT.getName(), Properties().also {
-            it.putAll(AN_EVENT.getProperties())
-        })
+        fakePostHog.verifyEventTracked(AN_EVENT.getName(), AN_EVENT.toPostHogProperties())
     }
 
     @Test
@@ -142,3 +141,15 @@ class DefaultVectorAnalyticsTest {
         fakePostHog.verifyNoEventTracking()
     }
 }
+
+private fun VectorAnalyticsScreen.toPostHogProperties(): Properties? {
+    return getProperties()?.let { properties ->
+        Properties().also { it.putAll(properties) }
+    }
+}
+
+private fun VectorAnalyticsEvent.toPostHogProperties(): Properties? {
+    return getProperties()?.let { properties ->
+        Properties().also { it.putAll(properties) }
+    }
+}
diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeLateInitUserPropertiesFactory.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeLateInitUserPropertiesFactory.kt
new file mode 100644
index 00000000000..9b442ece738
--- /dev/null
+++ b/vector/src/test/java/im/vector/app/test/fakes/FakeLateInitUserPropertiesFactory.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.LateInitUserPropertiesFactory
+import im.vector.app.features.analytics.plan.UserProperties
+import io.mockk.coEvery
+import io.mockk.mockk
+
+class FakeLateInitUserPropertiesFactory {
+
+    val instance = mockk()
+
+    fun givenCreatesProperties(userProperties: UserProperties?) {
+        coEvery { instance.createUserProperties() } returns userProperties
+    }
+}
diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakePostHog.kt b/vector/src/test/java/im/vector/app/test/fakes/FakePostHog.kt
index 631e09aada7..e14f809e666 100644
--- a/vector/src/test/java/im/vector/app/test/fakes/FakePostHog.kt
+++ b/vector/src/test/java/im/vector/app/test/fakes/FakePostHog.kt
@@ -19,6 +19,7 @@ package im.vector.app.test.fakes
 import android.os.Looper
 import com.posthog.android.PostHog
 import com.posthog.android.Properties
+import im.vector.app.features.analytics.plan.UserProperties
 import io.mockk.every
 import io.mockk.mockk
 import io.mockk.mockkStatic
@@ -41,15 +42,20 @@ class FakePostHog {
         verify { instance.optOut(optedOut) }
     }
 
-    fun verifyIdentifies(analyticsId: String) {
-        verify { instance.identify(analyticsId) }
+    fun verifyIdentifies(analyticsId: String, userProperties: UserProperties?) {
+        verify {
+            val postHogProperties = userProperties?.getProperties()
+                    ?.let { rawProperties -> Properties().also { it.putAll(rawProperties) } }
+                    ?.takeIf { it.isNotEmpty() }
+            instance.identify(analyticsId, postHogProperties, null)
+        }
     }
 
     fun verifyReset() {
         verify { instance.reset() }
     }
 
-    fun verifyScreenTracked(name: String, properties: Properties) {
+    fun verifyScreenTracked(name: String, properties: Properties?) {
         verify { instance.screen(name, properties) }
     }
 
@@ -61,7 +67,7 @@ class FakePostHog {
         }
     }
 
-    fun verifyEventTracked(name: String, properties: Properties) {
+    fun verifyEventTracked(name: String, properties: Properties?) {
         verify { instance.capture(name, properties) }
     }
 
diff --git a/vector/src/test/java/im/vector/app/test/fixtures/UserPropertiesFixture.kt b/vector/src/test/java/im/vector/app/test/fixtures/UserPropertiesFixture.kt
new file mode 100644
index 00000000000..5a911e2bc9d
--- /dev/null
+++ b/vector/src/test/java/im/vector/app/test/fixtures/UserPropertiesFixture.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.test.fixtures
+
+import im.vector.app.features.analytics.plan.UserProperties
+import im.vector.app.features.analytics.plan.UserProperties.FtueUseCaseSelection
+
+fun aUserProperties(
+        ftueUseCase: FtueUseCaseSelection? = FtueUseCaseSelection.Skip
+) = UserProperties(
+        ftueUseCaseSelection = ftueUseCase
+)
diff --git a/vector/src/test/java/im/vector/app/test/fixtures/VectorAnalyticsFixture.kt b/vector/src/test/java/im/vector/app/test/fixtures/VectorAnalyticsFixture.kt
new file mode 100644
index 00000000000..95590b0a442
--- /dev/null
+++ b/vector/src/test/java/im/vector/app/test/fixtures/VectorAnalyticsFixture.kt
@@ -0,0 +1,36 @@
+/*
+ * 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 im.vector.app.features.analytics.itf.VectorAnalyticsEvent
+import im.vector.app.features.analytics.itf.VectorAnalyticsScreen
+
+fun aVectorAnalyticsScreen(
+        name: String = "a-screen-name",
+        properties: Map? = null
+) = object : VectorAnalyticsScreen {
+    override fun getName() = name
+    override fun getProperties() = properties
+}
+
+fun aVectorAnalyticsEvent(
+        name: String = "an-event-name",
+        properties: Map? = null
+) = object : VectorAnalyticsEvent {
+    override fun getName() = name
+    override fun getProperties() = properties
+}

From 3c226002a03f8c472fd2b955cd614f08f818d070 Mon Sep 17 00:00:00 2001
From: Adam Brown 
Date: Tue, 15 Feb 2022 16:24:22 +0000
Subject: [PATCH 379/581] adding changelog entry

---
 changelog.d/5234.bugfix | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 changelog.d/5234.bugfix

diff --git a/changelog.d/5234.bugfix b/changelog.d/5234.bugfix
new file mode 100644
index 00000000000..2b5d4dee371
--- /dev/null
+++ b/changelog.d/5234.bugfix
@@ -0,0 +1 @@
+Analytics: Fixes missing use case identity values from within the onboarding flow
\ No newline at end of file

From 29088acdb324c447951dacbac8d619077cf15a5a Mon Sep 17 00:00:00 2001
From: NIkita Fedrunov 
Date: Wed, 2 Feb 2022 11:29:27 +0100
Subject: [PATCH 380/581] Code review fixes

---
 .../java/im/vector/app/EspressoExt.kt         |   1 +
 .../vector/app/ui/UiAllScreensSanityTest.kt   | 151 ++++++------
 .../im/vector/app/ui/robot/ElementRobot.kt    |   1 +
 .../java/im/vector/app/ui/robot/SpaceRobot.kt | 214 ------------------
 .../app/ui/robot/space/SpaceCreateRobot.kt    | 102 +++++++++
 .../app/ui/robot/space/SpaceMenuRobot.kt      | 116 ++++++++++
 .../vector/app/ui/robot/space/SpaceRobot.kt   |  38 ++++
 .../app/ui/robot/space/SpaceSettingsRobot.kt  |  68 ++++++
 8 files changed, 403 insertions(+), 288 deletions(-)
 delete mode 100644 vector/src/androidTest/java/im/vector/app/ui/robot/SpaceRobot.kt
 create mode 100644 vector/src/androidTest/java/im/vector/app/ui/robot/space/SpaceCreateRobot.kt
 create mode 100644 vector/src/androidTest/java/im/vector/app/ui/robot/space/SpaceMenuRobot.kt
 create mode 100644 vector/src/androidTest/java/im/vector/app/ui/robot/space/SpaceRobot.kt
 create mode 100644 vector/src/androidTest/java/im/vector/app/ui/robot/space/SpaceSettingsRobot.kt

diff --git a/vector/src/androidTest/java/im/vector/app/EspressoExt.kt b/vector/src/androidTest/java/im/vector/app/EspressoExt.kt
index 59ad122f363..1c3799f81d1 100644
--- a/vector/src/androidTest/java/im/vector/app/EspressoExt.kt
+++ b/vector/src/androidTest/java/im/vector/app/EspressoExt.kt
@@ -195,6 +195,7 @@ fun activityIdlingResource(activityClass: Class<*>): IdlingResource {
                         println("*** [$name]  onActivityLifecycleChanged callback: $callback")
                         callback?.onTransitionToIdle()
                     }
+                    else          -> {}
                 }
             }
         }
diff --git a/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt b/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt
index 8d1f93bc1a8..1f47f3a7984 100644
--- a/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt
+++ b/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt
@@ -20,13 +20,9 @@ import androidx.test.espresso.IdlingPolicies
 import androidx.test.ext.junit.rules.ActivityScenarioRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
-import com.adevinta.android.barista.interaction.BaristaDrawerInteractions.openDrawer
-import im.vector.app.R
 import im.vector.app.espresso.tools.ScreenshotFailureRule
 import im.vector.app.features.MainActivity
-import im.vector.app.getString
 import im.vector.app.ui.robot.ElementRobot
-import im.vector.app.ui.robot.withDeveloperMode
 import org.junit.Rule
 import org.junit.Test
 import org.junit.rules.RuleChain
@@ -56,88 +52,95 @@ class UiAllScreensSanityTest {
     fun allScreensTest() {
         IdlingPolicies.setMasterPolicyTimeout(120, TimeUnit.SECONDS)
 
-        elementRobot.onboarding {
-            crawl()
-        }
+//        elementRobot.onboarding {
+//            crawl()
+//        }
 
         // Create an account
-        val userId = "UiTest_" + UUID.randomUUID().toString()
-        elementRobot.signUp(userId)
-
-        elementRobot.settings {
-            general { crawl() }
-            notifications { crawl() }
-            preferences { crawl() }
-            voiceAndVideo()
-            ignoredUsers()
-            // TODO Test analytics
-            securityAndPrivacy { crawl() }
-            labs()
-            advancedSettings { crawl() }
-            // TODO Rework this part (Legals, etc.)
-            // helpAndAbout { crawl() }
-        }
-
-        elementRobot.newDirectMessage {
-            verifyQrCodeButton()
-            verifyInviteFriendsButton()
-        }
+//        val userId = "UiTest_" + UUID.randomUUID().toString()
+//        elementRobot.signUp(userId)
+
+//        elementRobot.settings {
+//            general { crawl() }
+//            notifications { crawl() }
+//            preferences { crawl() }
+//            voiceAndVideo()
+//            ignoredUsers()
+//            // TODO Test analytics
+//            securityAndPrivacy { crawl() }
+//            labs()
+//            advancedSettings { crawl() }
+//            // TODO Rework this part (Legals, etc.)
+//            // helpAndAbout { crawl() }
+//        }
+
+//        elementRobot.newDirectMessage {
+//            verifyQrCodeButton()
+//            verifyInviteFriendsButton()
+//        }
+
+//        elementRobot.newRoom {
+//            createNewRoom {
+//                crawl()
+//                createRoom {
+//                    val message = "Hello world!"
+//                    postMessage(message)
+//                    crawl()
+//                    crawlMessage(message)
+//                    openSettings { crawl() }
+//                }
+//            }
+//        }
 
-        elementRobot.newRoom {
-            createNewRoom {
+        elementRobot.space {
+            createSpace {
                 crawl()
-                createRoom {
-                    val message = "Hello world!"
-                    postMessage(message)
+            }
+            val spaceName = UUID.randomUUID().toString()
+            createSpace {
+                createPublicSpace(spaceName)
+            }
+
+            spaceMenu(spaceName) {
+                spaceMembers()
+                spaceSettings {
                     crawl()
-                    crawlMessage(message)
-                    openSettings { crawl() }
                 }
-            }
-        }
+                exploreRooms()
 
-        elementRobot.space {
-            openDrawer()
-            createSpace()
-            openDrawer()
-            openSpaceMenu()
-            invitePeople()
-            openSpaceMenu()
-            spaceMembers()
-            spaceSettings()
-            exploreRooms()
-            addRoom()
-            openSpaceMenu()
-            addSpace()
-            openSpaceMenu()
-            leaveSpace()
-        }
+                invitePeople().also { openMenu(spaceName) }
+                addRoom().also { openMenu(spaceName) }
+                addSpace().also { openMenu(spaceName) }
 
-        elementRobot.withDeveloperMode {
-            settings {
-                advancedSettings { crawlDeveloperOptions() }
+                leaveSpace()
             }
-            roomList {
-                openRoom(getString(R.string.room_displayname_empty_room)) {
-                    val message = "Test view source"
-                    postMessage(message)
-                    openMessageMenu(message) {
-                        viewSource()
-                    }
-                }
-            }
-        }
-
-        elementRobot.roomList {
-            verifyCreatedRoom()
         }
 
-        elementRobot.signout(expectSignOutWarning = true)
+//        elementRobot.withDeveloperMode {
+//            settings {
+//                advancedSettings { crawlDeveloperOptions() }
+//            }
+//            roomList {
+//                openRoom(getString(R.string.room_displayname_empty_room)) {
+//                    val message = "Test view source"
+//                    postMessage(message)
+//                    openMessageMenu(message) {
+//                        viewSource()
+//                    }
+//                }
+//            }
+//        }
+
+//        elementRobot.roomList {
+//            verifyCreatedRoom()
+//        }
+
+//        elementRobot.signout(expectSignOutWarning = true)
 
         // Login again on the same account
-        elementRobot.login(userId)
-        elementRobot.dismissVerificationIfPresent()
-        // TODO Deactivate account instead of logout?
-        elementRobot.signout(expectSignOutWarning = false)
+//        elementRobot.login(userId)
+//        elementRobot.dismissVerificationIfPresent()
+//         TODO Deactivate account instead of logout?
+//        elementRobot.signout(expectSignOutWarning = false)
     }
 }
diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt b/vector/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt
index 4363f69fb90..023ee6ad997 100644
--- a/vector/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt
+++ b/vector/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt
@@ -35,6 +35,7 @@ import im.vector.app.features.home.HomeActivity
 import im.vector.app.features.onboarding.OnboardingActivity
 import im.vector.app.initialSyncIdlingResource
 import im.vector.app.ui.robot.settings.SettingsRobot
+import im.vector.app.ui.robot.space.SpaceRobot
 import im.vector.app.withIdlingResource
 import timber.log.Timber
 
diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/SpaceRobot.kt b/vector/src/androidTest/java/im/vector/app/ui/robot/SpaceRobot.kt
deleted file mode 100644
index 154582f2ad5..00000000000
--- a/vector/src/androidTest/java/im/vector/app/ui/robot/SpaceRobot.kt
+++ /dev/null
@@ -1,214 +0,0 @@
-/*
- * 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.ui.robot
-
-import androidx.recyclerview.widget.RecyclerView
-import androidx.test.espresso.Espresso.onView
-import androidx.test.espresso.Espresso.pressBack
-import androidx.test.espresso.action.ViewActions.click
-import androidx.test.espresso.action.ViewActions.replaceText
-import androidx.test.espresso.contrib.RecyclerViewActions
-import androidx.test.espresso.matcher.ViewMatchers
-import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
-import androidx.test.espresso.matcher.ViewMatchers.withHint
-import androidx.test.espresso.matcher.ViewMatchers.withId
-import androidx.test.espresso.matcher.ViewMatchers.withText
-import com.adevinta.android.barista.interaction.BaristaClickInteractions.clickOn
-import com.adevinta.android.barista.internal.viewaction.ClickChildAction.clickChildWithId
-import im.vector.app.EspressoHelper
-import im.vector.app.R
-import im.vector.app.espresso.tools.waitUntilActivityVisible
-import im.vector.app.espresso.tools.waitUntilDialogVisible
-import im.vector.app.espresso.tools.waitUntilViewVisible
-import im.vector.app.features.home.HomeActivity
-import im.vector.app.features.invite.InviteUsersToRoomActivity
-import im.vector.app.features.roomprofile.RoomProfileActivity
-import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleActivity
-import im.vector.app.features.spaces.SpaceCreationActivity
-import im.vector.app.features.spaces.SpaceExploreActivity
-import im.vector.app.features.spaces.leave.SpaceLeaveAdvancedActivity
-import im.vector.app.features.spaces.manage.SpaceManageActivity
-import org.hamcrest.Matchers.allOf
-import java.util.UUID
-
-class SpaceRobot {
-
-    fun createSpace() {
-        clickOn(R.string.add_space)
-        waitUntilActivityVisible {
-            waitUntilViewVisible(withId(R.id.privateButton))
-        }
-        crawlCreate()
-
-        onView(withId(R.id.roomList))
-                .perform(
-                        RecyclerViewActions.actionOnItem(
-                                ViewMatchers.hasDescendant(withText(R.string.room_displayname_empty_room)),
-                                click()
-                        ).atPosition(0)
-                )
-        clickOn(R.id.spaceAddRoomSaveItem)
-        waitUntilActivityVisible {
-            waitUntilViewVisible(withId(R.id.roomListContainer))
-        }
-    }
-
-    private fun crawlCreate() {
-        //public
-        clickOn(R.id.publicButton)
-        waitUntilViewVisible(withId(R.id.recyclerView))
-        onView(withHint(R.string.create_room_name_hint)).perform(replaceText(UUID.randomUUID().toString()))
-        clickOn(R.id.nextButton)
-        waitUntilViewVisible(withId(R.id.recyclerView))
-        pressBack()
-        pressBack()
-
-        //private
-        clickOn(R.id.privateButton)
-        waitUntilViewVisible(withId(R.id.recyclerView))
-        clickOn(R.id.nextButton)
-
-        waitUntilViewVisible(withId(R.id.teammatesButton))
-        //me and teammates
-        clickOn(R.id.teammatesButton)
-        waitUntilViewVisible(withId(R.id.recyclerView))
-        clickOn(R.id.nextButton)
-        pressBack()
-        pressBack()
-
-        //just me
-        waitUntilViewVisible(withId(R.id.justMeButton))
-        clickOn(R.id.justMeButton)
-        waitUntilActivityVisible {
-            waitUntilViewVisible(withId(R.id.roomList))
-        }
-    }
-
-    fun openSpaceMenu() {
-        waitUntilViewVisible(withId(R.id.groupListView))
-        onView(withId(R.id.groupListView))
-                .perform(
-                        RecyclerViewActions.actionOnItem(
-                                ViewMatchers.hasDescendant(allOf(withId(R.id.groupTmpLeave), withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))),
-                                clickChildWithId(R.id.groupTmpLeave)
-                        ).atPosition(0)
-                )
-        waitUntilDialogVisible(withId(R.id.spaceNameView))
-    }
-
-    fun invitePeople() {
-        clickOn(R.id.invitePeople)
-        waitUntilDialogVisible(withId(R.id.inviteByMxidButton))
-        clickOn(R.id.inviteByMxidButton)
-        waitUntilActivityVisible {
-            waitUntilViewVisible(withId(R.id.userListRecyclerView))
-        }
-        EspressoHelper.getCurrentActivity()!!.finish()
-        //close invite dialog
-        pressBack()
-    }
-
-    fun spaceMembers() {
-        clickOn(R.id.showMemberList)
-        waitUntilActivityVisible {
-            waitUntilViewVisible(withId(R.id.roomSettingsRecyclerView))
-        }
-        pressBack()
-    }
-
-    fun spaceSettings() {
-        clickOn(R.id.spaceSettings)
-        waitUntilActivityVisible() {
-            waitUntilViewVisible(withId(R.id.roomSettingsRecyclerView))
-        }
-        crawlSettings()
-    }
-
-    private fun crawlSettings() {
-        onView(withId(R.id.roomSettingsRecyclerView))
-                .perform(
-                        RecyclerViewActions.actionOnItem(
-                                ViewMatchers.hasDescendant(withText(R.string.room_settings_space_access_title)),
-                                click()
-                        )
-                )
-
-        waitUntilActivityVisible() {
-            waitUntilViewVisible(withId(R.id.genericRecyclerView))
-        }
-
-        pressBack()
-
-        onView(withId(R.id.roomSettingsRecyclerView))
-                .perform(
-                        RecyclerViewActions.actionOnItem(
-                                ViewMatchers.hasDescendant(withText(R.string.space_settings_manage_rooms)),
-                                click()
-                        )
-                )
-
-        waitUntilViewVisible(withId(R.id.roomList))
-        pressBack()
-
-        onView(withId(R.id.roomSettingsRecyclerView))
-                .perform(
-                        RecyclerViewActions.actionOnItem(
-                                ViewMatchers.hasDescendant(withText(R.string.space_settings_permissions_title)),
-                                click()
-                        )
-                )
-
-        waitUntilViewVisible(withId(R.id.roomSettingsRecyclerView))
-        pressBack()
-        pressBack()
-    }
-
-    fun exploreRooms() {
-        clickOn(R.id.exploreRooms)
-        waitUntilActivityVisible {
-            waitUntilViewVisible(withId(R.id.spaceDirectoryList))
-        }
-        pressBack()
-    }
-
-    fun addRoom() {
-        clickOn(R.id.addRooms)
-        waitUntilActivityVisible {
-            waitUntilViewVisible(withId(R.id.roomList))
-        }
-        pressBack()
-    }
-
-    fun addSpace() {
-        clickOn(R.id.addSpaces)
-        waitUntilActivityVisible {
-            waitUntilViewVisible(withId(R.id.roomList))
-        }
-        pressBack()
-    }
-
-    fun leaveSpace() {
-        clickOn(R.id.leaveSpace)
-        waitUntilDialogVisible(withId(R.id.leaveButton))
-        clickOn(R.id.leave_selected)
-        waitUntilActivityVisible {
-            waitUntilViewVisible(withId(R.id.roomList))
-        }
-        clickOn(R.id.spaceLeaveButton)
-        waitUntilViewVisible(withId(R.id.groupListView))
-    }
-}
diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/space/SpaceCreateRobot.kt b/vector/src/androidTest/java/im/vector/app/ui/robot/space/SpaceCreateRobot.kt
new file mode 100644
index 00000000000..082adcb1738
--- /dev/null
+++ b/vector/src/androidTest/java/im/vector/app/ui/robot/space/SpaceCreateRobot.kt
@@ -0,0 +1,102 @@
+/*
+ * 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.ui.robot.space
+
+import androidx.recyclerview.widget.RecyclerView
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.Espresso.pressBack
+import androidx.test.espresso.action.ViewActions
+import androidx.test.espresso.action.ViewActions.click
+import androidx.test.espresso.contrib.RecyclerViewActions
+import androidx.test.espresso.matcher.ViewMatchers
+import androidx.test.espresso.matcher.ViewMatchers.withId
+import androidx.test.espresso.matcher.ViewMatchers.withText
+import com.adevinta.android.barista.interaction.BaristaClickInteractions.clickOn
+import im.vector.app.R
+import im.vector.app.espresso.tools.waitUntilActivityVisible
+import im.vector.app.espresso.tools.waitUntilDialogVisible
+import im.vector.app.espresso.tools.waitUntilViewVisible
+import im.vector.app.features.home.HomeActivity
+import im.vector.app.features.home.room.detail.RoomDetailActivity
+import im.vector.app.features.spaces.manage.SpaceManageActivity
+import java.util.UUID
+
+class SpaceCreateRobot {
+
+    fun crawl() {
+        // public
+        clickOn(R.id.publicButton)
+        waitUntilViewVisible(withId(R.id.recyclerView))
+        onView(ViewMatchers.withHint(R.string.create_room_name_hint)).perform(ViewActions.replaceText(UUID.randomUUID().toString()))
+        clickOn(R.id.nextButton)
+        waitUntilViewVisible(withId(R.id.recyclerView))
+        pressBack()
+        pressBack()
+
+        // private
+        clickOn(R.id.privateButton)
+        waitUntilViewVisible(withId(R.id.recyclerView))
+        clickOn(R.id.nextButton)
+
+        waitUntilViewVisible(withId(R.id.teammatesButton))
+        // me and teammates
+        clickOn(R.id.teammatesButton)
+        waitUntilViewVisible(withId(R.id.recyclerView))
+        clickOn(R.id.nextButton)
+        pressBack()
+        pressBack()
+
+        // just me
+        waitUntilViewVisible(withId(R.id.justMeButton))
+        clickOn(R.id.justMeButton)
+        waitUntilActivityVisible {
+            waitUntilViewVisible(withId(R.id.roomList))
+        }
+
+        onView(withId(R.id.roomList))
+                .perform(
+                        RecyclerViewActions.actionOnItem(
+                                ViewMatchers.hasDescendant(withText(R.string.room_displayname_empty_room)),
+                                click()
+                        ).atPosition(0)
+                )
+        clickOn(R.id.spaceAddRoomSaveItem)
+        waitUntilActivityVisible {
+            waitUntilViewVisible(withId(R.id.roomListContainer))
+        }
+    }
+
+    fun createPublicSpace(spaceName: String) {
+        clickOn(R.id.publicButton)
+        waitUntilViewVisible(withId(R.id.recyclerView))
+        onView(ViewMatchers.withHint(R.string.create_room_name_hint)).perform(ViewActions.replaceText(spaceName))
+        clickOn(R.id.nextButton)
+        waitUntilViewVisible(withId(R.id.recyclerView))
+        clickOn(R.id.nextButton)
+        waitUntilActivityVisible {
+            waitUntilDialogVisible(withId(R.id.inviteByMxidButton))
+        }
+        // close invite dialog
+        pressBack()
+
+        waitUntilViewVisible(withId(R.id.timelineRecyclerView))
+        // close room
+        pressBack()
+
+        waitUntilViewVisible(withId(R.id.roomListContainer))
+    }
+}
diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/space/SpaceMenuRobot.kt b/vector/src/androidTest/java/im/vector/app/ui/robot/space/SpaceMenuRobot.kt
new file mode 100644
index 00000000000..74389fa7232
--- /dev/null
+++ b/vector/src/androidTest/java/im/vector/app/ui/robot/space/SpaceMenuRobot.kt
@@ -0,0 +1,116 @@
+/*
+ * 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.ui.robot.space
+
+import androidx.recyclerview.widget.RecyclerView
+import androidx.test.espresso.Espresso
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.contrib.RecyclerViewActions
+import androidx.test.espresso.matcher.ViewMatchers
+import com.adevinta.android.barista.interaction.BaristaClickInteractions.clickOn
+import com.adevinta.android.barista.internal.viewaction.ClickChildAction
+import im.vector.app.R
+import im.vector.app.espresso.tools.waitUntilActivityVisible
+import im.vector.app.espresso.tools.waitUntilDialogVisible
+import im.vector.app.espresso.tools.waitUntilViewVisible
+import im.vector.app.features.invite.InviteUsersToRoomActivity
+import im.vector.app.features.roomprofile.RoomProfileActivity
+import im.vector.app.features.spaces.SpaceExploreActivity
+import im.vector.app.features.spaces.leave.SpaceLeaveAdvancedActivity
+import im.vector.app.features.spaces.manage.SpaceManageActivity
+import org.hamcrest.Matchers
+
+class SpaceMenuRobot {
+
+    fun openMenu(spaceName: String) {
+        waitUntilViewVisible(ViewMatchers.withId(R.id.groupListView))
+        onView(ViewMatchers.withId(R.id.groupListView))
+                .perform(
+                        RecyclerViewActions.actionOnItem(
+                                ViewMatchers.hasDescendant(Matchers.allOf(ViewMatchers.withId(R.id.groupNameView), ViewMatchers.withText(spaceName))),
+                                ClickChildAction.clickChildWithId(R.id.groupTmpLeave)
+                        ).atPosition(0)
+                )
+        waitUntilDialogVisible(ViewMatchers.withId(R.id.spaceNameView))
+    }
+
+    fun invitePeople() = apply {
+        clickOn(R.id.invitePeople)
+        waitUntilDialogVisible(ViewMatchers.withId(R.id.inviteByMxidButton))
+        clickOn(R.id.inviteByMxidButton)
+        waitUntilActivityVisible {
+            waitUntilViewVisible(ViewMatchers.withId(R.id.userListRecyclerView))
+        }
+        // close keyboard
+        Espresso.pressBack()
+        // close invite view
+        Espresso.pressBack()
+
+        Espresso.pressBack()
+    }
+
+    fun spaceMembers() {
+        clickOn(R.id.showMemberList)
+        waitUntilActivityVisible {
+            waitUntilViewVisible(ViewMatchers.withId(R.id.roomSettingsRecyclerView))
+        }
+        Espresso.pressBack()
+    }
+
+    fun spaceSettings(block: SpaceSettingsRobot.() -> Unit) {
+        clickOn(R.id.spaceSettings)
+        waitUntilActivityVisible {
+            waitUntilViewVisible(ViewMatchers.withId(R.id.roomSettingsRecyclerView))
+        }
+        block(SpaceSettingsRobot())
+    }
+
+    fun exploreRooms() {
+        clickOn(R.id.exploreRooms)
+        waitUntilActivityVisible {
+            waitUntilViewVisible(ViewMatchers.withId(R.id.spaceDirectoryList))
+        }
+        Espresso.pressBack()
+    }
+
+    fun addRoom() = apply {
+        clickOn(R.id.addRooms)
+        waitUntilActivityVisible {
+            waitUntilViewVisible(ViewMatchers.withId(R.id.roomList))
+        }
+        Espresso.pressBack()
+    }
+
+    fun addSpace() = apply {
+        clickOn(R.id.addSpaces)
+        waitUntilActivityVisible {
+            waitUntilViewVisible(ViewMatchers.withId(R.id.roomList))
+        }
+        Espresso.pressBack()
+    }
+
+    fun leaveSpace() {
+        clickOn(R.id.leaveSpace)
+        waitUntilDialogVisible(ViewMatchers.withId(R.id.leaveButton))
+        clickOn(R.id.leave_selected)
+        waitUntilActivityVisible {
+            waitUntilViewVisible(ViewMatchers.withId(R.id.roomList))
+        }
+        clickOn(R.id.spaceLeaveButton)
+        waitUntilViewVisible(ViewMatchers.withId(R.id.groupListView))
+    }
+}
diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/space/SpaceRobot.kt b/vector/src/androidTest/java/im/vector/app/ui/robot/space/SpaceRobot.kt
new file mode 100644
index 00000000000..ffb3c240517
--- /dev/null
+++ b/vector/src/androidTest/java/im/vector/app/ui/robot/space/SpaceRobot.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.ui.robot.space
+
+import com.adevinta.android.barista.interaction.BaristaClickInteractions.clickOn
+import com.adevinta.android.barista.interaction.BaristaDrawerInteractions.openDrawer
+import im.vector.app.R
+
+class SpaceRobot {
+
+    fun createSpace(block: SpaceCreateRobot.() -> Unit) {
+        openDrawer()
+        clickOn(R.string.add_space)
+        block(SpaceCreateRobot())
+    }
+
+    fun spaceMenu(spaceName: String, block: SpaceMenuRobot.() -> Unit) {
+        openDrawer()
+        with(SpaceMenuRobot()) {
+            openMenu(spaceName)
+            block()
+        }
+    }
+}
diff --git a/vector/src/androidTest/java/im/vector/app/ui/robot/space/SpaceSettingsRobot.kt b/vector/src/androidTest/java/im/vector/app/ui/robot/space/SpaceSettingsRobot.kt
new file mode 100644
index 00000000000..dcd003da98e
--- /dev/null
+++ b/vector/src/androidTest/java/im/vector/app/ui/robot/space/SpaceSettingsRobot.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.ui.robot.space
+
+import androidx.recyclerview.widget.RecyclerView
+import androidx.test.espresso.Espresso
+import androidx.test.espresso.action.ViewActions
+import androidx.test.espresso.contrib.RecyclerViewActions
+import androidx.test.espresso.matcher.ViewMatchers
+import im.vector.app.R
+import im.vector.app.espresso.tools.waitUntilActivityVisible
+import im.vector.app.espresso.tools.waitUntilViewVisible
+import im.vector.app.features.roomprofile.settings.joinrule.RoomJoinRuleActivity
+
+class SpaceSettingsRobot {
+    fun crawl() {
+        Espresso.onView(ViewMatchers.withId(R.id.roomSettingsRecyclerView))
+                .perform(
+                        RecyclerViewActions.actionOnItem(
+                                ViewMatchers.hasDescendant(ViewMatchers.withText(R.string.room_settings_space_access_title)),
+                                ViewActions.click()
+                        )
+                )
+
+        waitUntilActivityVisible {
+            waitUntilViewVisible(ViewMatchers.withId(R.id.genericRecyclerView))
+        }
+
+        Espresso.pressBack()
+
+        Espresso.onView(ViewMatchers.withId(R.id.roomSettingsRecyclerView))
+                .perform(
+                        RecyclerViewActions.actionOnItem(
+                                ViewMatchers.hasDescendant(ViewMatchers.withText(R.string.space_settings_manage_rooms)),
+                                ViewActions.click()
+                        )
+                )
+
+        waitUntilViewVisible(ViewMatchers.withId(R.id.roomList))
+        Espresso.pressBack()
+
+        Espresso.onView(ViewMatchers.withId(R.id.roomSettingsRecyclerView))
+                .perform(
+                        RecyclerViewActions.actionOnItem(
+                                ViewMatchers.hasDescendant(ViewMatchers.withText(R.string.space_settings_permissions_title)),
+                                ViewActions.click()
+                        )
+                )
+
+        waitUntilViewVisible(ViewMatchers.withId(R.id.roomSettingsRecyclerView))
+        Espresso.pressBack()
+        Espresso.pressBack()
+    }
+}

From 343b8bf08daaa8784998d50a4bdfcecee4ed820b Mon Sep 17 00:00:00 2001
From: Adam Brown 
Date: Wed, 16 Feb 2022 12:29:08 +0000
Subject: [PATCH 381/581] Incrementing schema version - fixes pre-release
 launch crash (#5245)

* updating the schema version to 25 to reflect the latest migration
* adding changelog entry
---
 changelog.d/5243.bugfix                                         | 1 +
 .../android/sdk/internal/database/RealmSessionStoreMigration.kt | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)
 create mode 100644 changelog.d/5243.bugfix

diff --git a/changelog.d/5243.bugfix b/changelog.d/5243.bugfix
new file mode 100644
index 00000000000..eb323c1ca40
--- /dev/null
+++ b/changelog.d/5243.bugfix
@@ -0,0 +1 @@
+Increments database schema to take advantage of homeserver capabilities entity migration (fixes crash in pre-release builds)
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
index 4bf352c06c2..12e60da1145 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
@@ -57,7 +57,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
     override fun equals(other: Any?) = other is RealmSessionStoreMigration
     override fun hashCode() = 1000
 
-    val schemaVersion = 24L
+    val schemaVersion = 25L
 
     override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
         Timber.d("Migrating Realm Session from $oldVersion to $newVersion")

From 0a04ff9aa60f584e27fc6bcb6d6a54fa354c1dd6 Mon Sep 17 00:00:00 2001
From: Maxime Naturel 
Date: Wed, 16 Feb 2022 13:46:06 +0100
Subject: [PATCH 382/581] Adding changelog entry

---
 changelog.d/2782.misc | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 changelog.d/2782.misc

diff --git a/changelog.d/2782.misc b/changelog.d/2782.misc
new file mode 100644
index 00000000000..6c340219d53
--- /dev/null
+++ b/changelog.d/2782.misc
@@ -0,0 +1 @@
+Collapse ACL events

From 4641153df0f1a82851526f7aeec86e6de3d3a5b6 Mon Sep 17 00:00:00 2001
From: Maxime Naturel 
Date: Wed, 16 Feb 2022 16:01:31 +0100
Subject: [PATCH 383/581] Making ACL multiple successive events as collapsable

---
 .../factory/MergedHeaderItemFactory.kt        | 49 +++++++++++--------
 .../helper/TimelineDisplayableEvents.kt       |  3 +-
 ...entsItem.kt => DefaultMergedEventsItem.kt} |  6 ++-
 vector/src/main/res/values/strings.xml        |  4 ++
 4 files changed, 39 insertions(+), 23 deletions(-)
 rename vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/{MergedMembershipEventsItem.kt => DefaultMergedEventsItem.kt} (91%)

diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt
index 99a026a0cf9..bf7e93982eb 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt
@@ -16,6 +16,7 @@
 
 package im.vector.app.features.home.room.detail.timeline.factory
 
+import im.vector.app.R
 import im.vector.app.core.di.ActiveSessionHolder
 import im.vector.app.core.extensions.prevOrNull
 import im.vector.app.features.home.AvatarRenderer
@@ -26,8 +27,8 @@ import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventVisi
 import im.vector.app.features.home.room.detail.timeline.helper.canBeMerged
 import im.vector.app.features.home.room.detail.timeline.helper.isRoomConfiguration
 import im.vector.app.features.home.room.detail.timeline.item.BasedMergedItem
-import im.vector.app.features.home.room.detail.timeline.item.MergedMembershipEventsItem
-import im.vector.app.features.home.room.detail.timeline.item.MergedMembershipEventsItem_
+import im.vector.app.features.home.room.detail.timeline.item.DefaultMergedEventsItem
+import im.vector.app.features.home.room.detail.timeline.item.DefaultMergedEventsItem_
 import im.vector.app.features.home.room.detail.timeline.item.MergedRoomCreationItem
 import im.vector.app.features.home.room.detail.timeline.item.MergedRoomCreationItem_
 import im.vector.app.features.home.room.detail.timeline.tools.createLinkMovementMethod
@@ -82,7 +83,7 @@ class MergedHeaderItemFactory @Inject constructor(private val activeSessionHolde
                                                    event: TimelineEvent,
                                                    eventIdToHighlight: String?,
                                                    requestModelBuild: () -> Unit,
-                                                   callback: TimelineEventController.Callback?): MergedMembershipEventsItem_? {
+                                                   callback: TimelineEventController.Callback?): DefaultMergedEventsItem_? {
         val mergedEvents = timelineEventVisibilityHelper.prevSameTypeEvents(
                 items,
                 currentPosition,
@@ -122,23 +123,31 @@ class MergedHeaderItemFactory @Inject constructor(private val activeSessionHolde
                 collapsedEventIds.removeAll(mergedEventIds)
             }
             val mergeId = mergedEventIds.joinToString(separator = "_") { it.toString() }
-            val attributes = MergedMembershipEventsItem.Attributes(
-                    isCollapsed = isCollapsed,
-                    mergeData = mergedData,
-                    avatarRenderer = avatarRenderer,
-                    onCollapsedStateChanged = {
-                        mergeItemCollapseStates[event.localId] = it
-                        requestModelBuild()
-                    }
-            )
-            MergedMembershipEventsItem_()
-                    .id(mergeId)
-                    .leftGuideline(avatarSizeProvider.leftGuideline)
-                    .highlighted(isCollapsed && highlighted)
-                    .attributes(attributes)
-                    .also {
-                        it.setOnVisibilityStateChanged(MergedTimelineEventVisibilityStateChangedListener(callback, mergedEvents))
-                    }
+            val summaryTitleResId = when(event.root.getClearType()) {
+                EventType.STATE_ROOM_MEMBER -> R.plurals.membership_changes
+                EventType.STATE_ROOM_SERVER_ACL -> R.plurals.notice_room_server_acl_changes
+                else -> null
+            }
+            summaryTitleResId?.let { summaryTitle ->
+                val attributes = DefaultMergedEventsItem.Attributes(
+                        summaryTitleResId = summaryTitle,
+                        isCollapsed = isCollapsed,
+                        mergeData = mergedData,
+                        avatarRenderer = avatarRenderer,
+                        onCollapsedStateChanged = {
+                            mergeItemCollapseStates[event.localId] = it
+                            requestModelBuild()
+                        }
+                )
+                DefaultMergedEventsItem_()
+                        .id(mergeId)
+                        .leftGuideline(avatarSizeProvider.leftGuideline)
+                        .highlighted(isCollapsed && highlighted)
+                        .attributes(attributes)
+                        .also {
+                            it.setOnVisibilityStateChanged(MergedTimelineEventVisibilityStateChangedListener(callback, mergedEvents))
+                        }
+            }
         }
     }
 
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 bcccbc9f7cb..53a9fbbaea5 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
@@ -56,7 +56,8 @@ object TimelineDisplayableEvents {
 }
 
 fun TimelineEvent.canBeMerged(): Boolean {
-    return root.getClearType() == EventType.STATE_ROOM_MEMBER
+    return root.getClearType() == EventType.STATE_ROOM_MEMBER ||
+            root.getClearType() == EventType.STATE_ROOM_SERVER_ACL
 }
 
 fun TimelineEvent.isRoomConfiguration(roomCreatorUserId: String?): Boolean {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedMembershipEventsItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/DefaultMergedEventsItem.kt
similarity index 91%
rename from vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedMembershipEventsItem.kt
rename to vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/DefaultMergedEventsItem.kt
index e19dc33fffb..65675efca91 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedMembershipEventsItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/DefaultMergedEventsItem.kt
@@ -20,6 +20,7 @@ import android.view.View
 import android.view.ViewGroup
 import android.widget.ImageView
 import android.widget.TextView
+import androidx.annotation.PluralsRes
 import androidx.core.view.children
 import com.airbnb.epoxy.EpoxyAttribute
 import com.airbnb.epoxy.EpoxyModelClass
@@ -27,7 +28,7 @@ import im.vector.app.R
 import im.vector.app.features.home.AvatarRenderer
 
 @EpoxyModelClass(layout = R.layout.item_timeline_event_base_noinfo)
-abstract class MergedMembershipEventsItem : BasedMergedItem() {
+abstract class DefaultMergedEventsItem : BasedMergedItem() {
 
     override fun getViewStubId() = STUB_ID
 
@@ -37,7 +38,7 @@ abstract class MergedMembershipEventsItem : BasedMergedItem,
             override val avatarRenderer: AvatarRenderer,
diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml
index c155b6bb755..7387688dfe3 100644
--- a/vector/src/main/res/values/strings.xml
+++ b/vector/src/main/res/values/strings.xml
@@ -81,6 +81,10 @@
 
     %s changed the server ACLs for this room.
     You changed the server ACLs for this room.
+    
+        %d server ACLs change
+        %d server ACLs changes
+    
     • Servers matching %s are now banned.
     • Servers matching %s were removed from the ban list.
     • Servers matching %s are now allowed.

From bc45c0ce509f764f9844c37e78b6df6310e5ae37 Mon Sep 17 00:00:00 2001
From: Maxime Naturel 
Date: Wed, 16 Feb 2022 16:09:10 +0100
Subject: [PATCH 384/581] Fix code formatting

---
 .../room/detail/timeline/factory/MergedHeaderItemFactory.kt | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt
index bf7e93982eb..0b8c2eb5722 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt
@@ -123,10 +123,10 @@ class MergedHeaderItemFactory @Inject constructor(private val activeSessionHolde
                 collapsedEventIds.removeAll(mergedEventIds)
             }
             val mergeId = mergedEventIds.joinToString(separator = "_") { it.toString() }
-            val summaryTitleResId = when(event.root.getClearType()) {
-                EventType.STATE_ROOM_MEMBER -> R.plurals.membership_changes
+            val summaryTitleResId = when (event.root.getClearType()) {
+                EventType.STATE_ROOM_MEMBER     -> R.plurals.membership_changes
                 EventType.STATE_ROOM_SERVER_ACL -> R.plurals.notice_room_server_acl_changes
-                else -> null
+                else                            -> null
             }
             summaryTitleResId?.let { summaryTitle ->
                 val attributes = DefaultMergedEventsItem.Attributes(

From 4fe3b409d245636d90f4b35bb5d472b7855e7a4b Mon Sep 17 00:00:00 2001
From: Maxime Naturel 
Date: Thu, 17 Feb 2022 10:27:59 +0100
Subject: [PATCH 385/581] Replacing the search icon by a filter icon

---
 changelog.d/4643.misc             | 1 +
 vector/src/main/res/menu/home.xml | 4 ++--
 2 files changed, 3 insertions(+), 2 deletions(-)
 create mode 100644 changelog.d/4643.misc

diff --git a/changelog.d/4643.misc b/changelog.d/4643.misc
new file mode 100644
index 00000000000..9092e77942a
--- /dev/null
+++ b/changelog.d/4643.misc
@@ -0,0 +1 @@
+Replacing search icon by filter icon
diff --git a/vector/src/main/res/menu/home.xml b/vector/src/main/res/menu/home.xml
index 93947a5a7f2..f15c31b4e99 100644
--- a/vector/src/main/res/menu/home.xml
+++ b/vector/src/main/res/menu/home.xml
@@ -31,9 +31,9 @@
 
     
 
-
\ No newline at end of file
+

From b346a732430653244b508032adb7b9fd23cea677 Mon Sep 17 00:00:00 2001
From: Maxime Naturel 
Date: Thu, 17 Feb 2022 11:03:04 +0100
Subject: [PATCH 386/581] Adding more details in changelog

---
 changelog.d/4643.misc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/changelog.d/4643.misc b/changelog.d/4643.misc
index 9092e77942a..3d86baa1a2d 100644
--- a/changelog.d/4643.misc
+++ b/changelog.d/4643.misc
@@ -1 +1 @@
-Replacing search icon by filter icon
+Home screen: Replacing search icon by filter icon in the top right menu

From 2d38786d02cadb670627ede6e8759d06769e1db2 Mon Sep 17 00:00:00 2001
From: Maxime Naturel 
Date: Mon, 7 Feb 2022 13:51:10 +0100
Subject: [PATCH 387/581] Adding TODOs

---
 .../android/sdk/api/session/room/model/PowerLevelsContent.kt   | 1 +
 .../vector/app/features/autocomplete/AutocompleteMatrixItem.kt | 1 +
 .../autocomplete/member/AutocompleteMemberPresenter.kt         | 3 +++
 3 files changed, 5 insertions(+)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContent.kt
index 5c46db7166e..371fb922e6a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContent.kt
@@ -100,6 +100,7 @@ data class PowerLevelsContent(
         }
     }
 
+    // TODO use this to check if user can notify everyone => compare user role to room permission setting
     companion object {
         /**
          * Key to use for content.notifications and get the level required to trigger an @room notification. Defaults to 50 if unspecified.
diff --git a/vector/src/main/java/im/vector/app/features/autocomplete/AutocompleteMatrixItem.kt b/vector/src/main/java/im/vector/app/features/autocomplete/AutocompleteMatrixItem.kt
index dba2661927b..54d809d948c 100644
--- a/vector/src/main/java/im/vector/app/features/autocomplete/AutocompleteMatrixItem.kt
+++ b/vector/src/main/java/im/vector/app/features/autocomplete/AutocompleteMatrixItem.kt
@@ -30,6 +30,7 @@ import im.vector.app.features.displayname.getBestName
 import im.vector.app.features.home.AvatarRenderer
 import org.matrix.android.sdk.api.util.MatrixItem
 
+// TODO create a new item for sections
 @EpoxyModelClass(layout = R.layout.item_autocomplete_matrix_item)
 abstract class AutocompleteMatrixItem : VectorEpoxyModel() {
 
diff --git a/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberPresenter.kt b/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberPresenter.kt
index 4976cb39b9e..6624a15916b 100644
--- a/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberPresenter.kt
+++ b/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberPresenter.kt
@@ -72,6 +72,9 @@ class AutocompleteMemberPresenter @AssistedInject constructor(context: Context,
                 .asSequence()
                 .sortedBy { it.displayName }
                 .disambiguate()
+        // TODO check if user can notify everyone => compare user role to room permission setting
+        // TODO if user can notify everyone, add entry "@room"
+        // TODO add header sections to separate members and notification
         controller.setData(members.toList())
     }
 }

From 1d1dc8b1fdfda9ec682446277bbc535da1c131de Mon Sep 17 00:00:00 2001
From: Maxime Naturel 
Date: Wed, 9 Feb 2022 14:59:09 +0100
Subject: [PATCH 388/581] Adding changelog entry

---
 changelog.d/5123.misc | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 changelog.d/5123.misc

diff --git a/changelog.d/5123.misc b/changelog.d/5123.misc
new file mode 100644
index 00000000000..790910e493f
--- /dev/null
+++ b/changelog.d/5123.misc
@@ -0,0 +1 @@
+Add completion for @room as per element web

From 38fdfb27e4bb495f0f99ad5ec50c0d81f7917bfe Mon Sep 17 00:00:00 2001
From: Maxime Naturel 
Date: Thu, 10 Feb 2022 14:20:47 +0100
Subject: [PATCH 389/581] Creating sealed wrapper class for member items

---
 .../matrix/android/sdk/api/util/MatrixItem.kt |  1 +
 .../autocomplete/AutocompleteMatrixItem.kt    |  2 +-
 .../member/AutocompleteMemberController.kt    | 33 ++++++++++++++-----
 .../member/AutocompleteMemberItem.kt          | 26 +++++++++++++++
 .../member/AutocompleteMemberPresenter.kt     | 10 +++---
 .../home/room/detail/AutoCompleter.kt         | 14 +++++---
 .../app/features/html/PillsPostProcessor.kt   |  1 +
 7 files changed, 67 insertions(+), 20 deletions(-)
 create mode 100644 vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberItem.kt

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt
index 3396c4a6c98..31dcd007a24 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt
@@ -27,6 +27,7 @@ import org.matrix.android.sdk.api.session.room.sender.SenderInfo
 import org.matrix.android.sdk.api.session.user.model.User
 import java.util.Locale
 
+// TODO how to represent the notify everyone @room item? EveryoneItem ??
 sealed class MatrixItem(
         open val id: String,
         open val displayName: String?,
diff --git a/vector/src/main/java/im/vector/app/features/autocomplete/AutocompleteMatrixItem.kt b/vector/src/main/java/im/vector/app/features/autocomplete/AutocompleteMatrixItem.kt
index 54d809d948c..d0e2c81b567 100644
--- a/vector/src/main/java/im/vector/app/features/autocomplete/AutocompleteMatrixItem.kt
+++ b/vector/src/main/java/im/vector/app/features/autocomplete/AutocompleteMatrixItem.kt
@@ -30,7 +30,7 @@ import im.vector.app.features.displayname.getBestName
 import im.vector.app.features.home.AvatarRenderer
 import org.matrix.android.sdk.api.util.MatrixItem
 
-// TODO create a new item for sections
+// TODO create a new item for sections: AutocompleteSection
 @EpoxyModelClass(layout = R.layout.item_autocomplete_matrix_item)
 abstract class AutocompleteMatrixItem : VectorEpoxyModel() {
 
diff --git a/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberController.kt b/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberController.kt
index 9b4bd785043..5bbd4a39964 100644
--- a/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberController.kt
+++ b/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberController.kt
@@ -20,28 +20,43 @@ import com.airbnb.epoxy.TypedEpoxyController
 import im.vector.app.features.autocomplete.AutocompleteClickListener
 import im.vector.app.features.autocomplete.autocompleteMatrixItem
 import im.vector.app.features.home.AvatarRenderer
-import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
 import org.matrix.android.sdk.api.util.toMatrixItem
 import javax.inject.Inject
 
-class AutocompleteMemberController @Inject constructor() : TypedEpoxyController>() {
+class AutocompleteMemberController @Inject constructor() : TypedEpoxyController>() {
 
-    var listener: AutocompleteClickListener? = null
+    var listener: AutocompleteClickListener? = null
 
     @Inject lateinit var avatarRenderer: AvatarRenderer
 
-    override fun buildModels(data: List?) {
+    override fun buildModels(data: List?) {
         if (data.isNullOrEmpty()) {
             return
         }
-        val host = this
-        data.forEach { user ->
-            autocompleteMatrixItem {
+        data.forEach { item ->
+            when (item) {
+                is AutocompleteMemberItem.RoomMember -> buildRoomMemberItem(item)
+                is AutocompleteMemberItem.Everyone   -> buildEveryoneItem(item)
+            }
+        }
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // HELPER METHODS
+    ///////////////////////////////////////////////////////////////////////////
+
+    private fun buildRoomMemberItem(roomMember: AutocompleteMemberItem.RoomMember) {
+        autocompleteMatrixItem {
+            roomMember.roomMemberSummary.let { user ->
                 id(user.userId)
                 matrixItem(user.toMatrixItem())
-                avatarRenderer(host.avatarRenderer)
-                clickListener { host.listener?.onItemClick(user) }
+                avatarRenderer(this@AutocompleteMemberController.avatarRenderer)
+                clickListener { this@AutocompleteMemberController.listener?.onItemClick(roomMember) }
             }
         }
     }
+
+    private fun buildEveryoneItem(everyone: AutocompleteMemberItem.Everyone) {
+        // TODO
+    }
 }
diff --git a/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberItem.kt b/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberItem.kt
new file mode 100644
index 00000000000..fa9693895f7
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberItem.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.features.autocomplete.member
+
+import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
+import org.matrix.android.sdk.api.session.room.model.RoomSummary
+
+sealed class AutocompleteMemberItem {
+    // TODO add section class
+    data class RoomMember(val roomMemberSummary: RoomMemberSummary) : AutocompleteMemberItem()
+    data class Everyone(val roomSummary: RoomSummary) : AutocompleteMemberItem()
+}
diff --git a/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberPresenter.kt b/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberPresenter.kt
index 6624a15916b..bb7d41cc462 100644
--- a/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberPresenter.kt
+++ b/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberPresenter.kt
@@ -33,7 +33,7 @@ class AutocompleteMemberPresenter @AssistedInject constructor(context: Context,
                                                               @Assisted val roomId: String,
                                                               session: Session,
                                                               private val controller: AutocompleteMemberController
-) : RecyclerViewPresenter(context), AutocompleteClickListener {
+) : RecyclerViewPresenter(context), AutocompleteClickListener {
 
     private val room by lazy { session.getRoom(roomId)!! }
 
@@ -54,7 +54,7 @@ class AutocompleteMemberPresenter @AssistedInject constructor(context: Context,
         return controller.adapter
     }
 
-    override fun onItemClick(t: RoomMemberSummary) {
+    override fun onItemClick(t: AutocompleteMemberItem) {
         dispatchClick(t)
     }
 
@@ -72,10 +72,10 @@ class AutocompleteMemberPresenter @AssistedInject constructor(context: Context,
                 .asSequence()
                 .sortedBy { it.displayName }
                 .disambiguate()
-        // TODO check if user can notify everyone => compare user role to room permission setting
-        // TODO if user can notify everyone, add entry "@room"
+        // TODO check if user can notify everyone => compare user role to room permission setting: PowerLevelsContent
+        // TODO if user can notify everyone, add entry AutocompleteMemberItem.Everyone
         // TODO add header sections to separate members and notification
-        controller.setData(members.toList())
+        controller.setData(members.map { AutocompleteMemberItem.RoomMember(it) }.toList())
     }
 }
 
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/AutoCompleter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/AutoCompleter.kt
index 9f85d4015bc..88e49f7062f 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/AutoCompleter.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/AutoCompleter.kt
@@ -33,6 +33,7 @@ import im.vector.app.features.autocomplete.command.AutocompleteCommandPresenter
 import im.vector.app.features.autocomplete.command.CommandAutocompletePolicy
 import im.vector.app.features.autocomplete.emoji.AutocompleteEmojiPresenter
 import im.vector.app.features.autocomplete.group.AutocompleteGroupPresenter
+import im.vector.app.features.autocomplete.member.AutocompleteMemberItem
 import im.vector.app.features.autocomplete.member.AutocompleteMemberPresenter
 import im.vector.app.features.autocomplete.room.AutocompleteRoomPresenter
 import im.vector.app.features.command.Command
@@ -41,7 +42,6 @@ import im.vector.app.features.home.AvatarRenderer
 import im.vector.app.features.html.PillImageSpan
 import im.vector.app.features.themes.ThemeUtils
 import org.matrix.android.sdk.api.session.group.model.GroupSummary
-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.util.MatrixItem
 import org.matrix.android.sdk.api.util.toMatrixItem
@@ -125,14 +125,18 @@ class AutoCompleter @AssistedInject constructor(
 
     private fun setupMembers(backgroundDrawable: ColorDrawable, editText: EditText) {
         autocompleteMemberPresenter = autocompleteMemberPresenterFactory.create(roomId)
-        Autocomplete.on(editText)
+        Autocomplete.on(editText)
                 .with(CharPolicy('@', true))
                 .with(autocompleteMemberPresenter)
                 .with(ELEVATION)
                 .with(backgroundDrawable)
-                .with(object : AutocompleteCallback {
-                    override fun onPopupItemClicked(editable: Editable, item: RoomMemberSummary): Boolean {
-                        insertMatrixItem(editText, editable, "@", item.toMatrixItem())
+                .with(object : AutocompleteCallback {
+                    override fun onPopupItemClicked(editable: Editable, item: AutocompleteMemberItem): Boolean {
+                        when (item) {
+                            is AutocompleteMemberItem.RoomMember ->
+                                insertMatrixItem(editText, editable, "@", item.roomMemberSummary.toMatrixItem())
+                            is AutocompleteMemberItem.Everyone   -> Unit // TODO
+                        }
                         return true
                     }
 
diff --git a/vector/src/main/java/im/vector/app/features/html/PillsPostProcessor.kt b/vector/src/main/java/im/vector/app/features/html/PillsPostProcessor.kt
index f8a2ee51370..8376d250650 100644
--- a/vector/src/main/java/im/vector/app/features/html/PillsPostProcessor.kt
+++ b/vector/src/main/java/im/vector/app/features/html/PillsPostProcessor.kt
@@ -59,6 +59,7 @@ class PillsPostProcessor @AssistedInject constructor(@Assisted private val roomI
     }
 
     private fun LinkSpan.createPillSpan(roomId: String?): PillImageSpan? {
+        // TODO handle @room text to create associated chip
         val permalinkData = PermalinkParser.parse(url)
         val matrixItem = when (permalinkData) {
             is PermalinkData.UserLink -> {

From d8e28d7be95da206a0fabae58ab52e828ae38a35 Mon Sep 17 00:00:00 2001
From: Maxime Naturel 
Date: Thu, 10 Feb 2022 17:22:29 +0100
Subject: [PATCH 390/581] Adding autocomplete for @room (missing correct first
 letter of avatar)

---
 .../matrix/android/sdk/api/util/MatrixItem.kt | 43 +++++++++++++------
 .../member/AutocompleteMemberController.kt    | 37 ++++++++++++++--
 .../member/AutocompleteMemberPresenter.kt     | 21 +++++++--
 .../home/room/detail/AutoCompleter.kt         |  5 ++-
 vector/src/main/res/values/strings.xml        |  3 ++
 5 files changed, 87 insertions(+), 22 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt
index 31dcd007a24..9ad1e42513e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt
@@ -27,7 +27,6 @@ import org.matrix.android.sdk.api.session.room.sender.SenderInfo
 import org.matrix.android.sdk.api.session.user.model.User
 import java.util.Locale
 
-// TODO how to represent the notify everyone @room item? EveryoneItem ??
 sealed class MatrixItem(
         open val id: String,
         open val displayName: String?,
@@ -36,7 +35,18 @@ sealed class MatrixItem(
     data class UserItem(override val id: String,
                         override val displayName: String? = null,
                         override val avatarUrl: String? = null) :
-        MatrixItem(id, displayName?.removeSuffix(ircPattern), avatarUrl) {
+            MatrixItem(id, displayName?.removeSuffix(IRC_PATTERN), avatarUrl) {
+        init {
+            if (BuildConfig.DEBUG) checkId()
+        }
+
+        override fun updateAvatar(newAvatar: String?) = copy(avatarUrl = newAvatar)
+    }
+
+    data class EveryoneInRoomItem(override val id: String,
+                                  override val displayName: String? = null,
+                                  override val avatarUrl: String? = null) :
+            MatrixItem(id, displayName, avatarUrl) {
         init {
             if (BuildConfig.DEBUG) checkId()
         }
@@ -47,7 +57,7 @@ sealed class MatrixItem(
     data class EventItem(override val id: String,
                          override val displayName: String? = null,
                          override val avatarUrl: String? = null) :
-        MatrixItem(id, displayName, avatarUrl) {
+            MatrixItem(id, displayName, avatarUrl) {
         init {
             if (BuildConfig.DEBUG) checkId()
         }
@@ -58,7 +68,7 @@ sealed class MatrixItem(
     data class RoomItem(override val id: String,
                         override val displayName: String? = null,
                         override val avatarUrl: String? = null) :
-        MatrixItem(id, displayName, avatarUrl) {
+            MatrixItem(id, displayName, avatarUrl) {
         init {
             if (BuildConfig.DEBUG) checkId()
         }
@@ -69,7 +79,7 @@ sealed class MatrixItem(
     data class SpaceItem(override val id: String,
                          override val displayName: String? = null,
                          override val avatarUrl: String? = null) :
-        MatrixItem(id, displayName, avatarUrl) {
+            MatrixItem(id, displayName, avatarUrl) {
         init {
             if (BuildConfig.DEBUG) checkId()
         }
@@ -80,7 +90,7 @@ sealed class MatrixItem(
     data class RoomAliasItem(override val id: String,
                              override val displayName: String? = null,
                              override val avatarUrl: String? = null) :
-        MatrixItem(id, displayName, avatarUrl) {
+            MatrixItem(id, displayName, avatarUrl) {
         init {
             if (BuildConfig.DEBUG) checkId()
         }
@@ -91,7 +101,7 @@ sealed class MatrixItem(
     data class GroupItem(override val id: String,
                          override val displayName: String? = null,
                          override val avatarUrl: String? = null) :
-        MatrixItem(id, displayName, avatarUrl) {
+            MatrixItem(id, displayName, avatarUrl) {
         init {
             if (BuildConfig.DEBUG) checkId()
         }
@@ -110,16 +120,18 @@ sealed class MatrixItem(
     /**
      * Return the prefix as defined in the matrix spec (and not extracted from the id)
      */
-    fun getIdPrefix() = when (this) {
-        is UserItem      -> '@'
-        is EventItem     -> '$'
+    private fun getIdPrefix() = when (this) {
+        is UserItem           -> '@'
+        is EventItem          -> '$'
         is SpaceItem,
-        is RoomItem      -> '!'
-        is RoomAliasItem -> '#'
-        is GroupItem     -> '+'
+        is RoomItem,
+        is EveryoneInRoomItem -> '!'
+        is RoomAliasItem      -> '#'
+        is GroupItem          -> '+'
     }
 
     fun firstLetterOfDisplayName(): String {
+        // TODO retrieve first letter of room name when EveryoneInRoomItem
         return (displayName?.takeIf { it.isNotBlank() } ?: id)
                 .let { dn ->
                     var startIndex = 0
@@ -152,7 +164,8 @@ sealed class MatrixItem(
     }
 
     companion object {
-        private const val ircPattern = " (IRC)"
+        private const val IRC_PATTERN = " (IRC)"
+        const val NOTIFY_EVERYONE = "@room"
     }
 }
 
@@ -172,6 +185,8 @@ fun RoomSummary.toMatrixItem() = if (roomType == RoomType.SPACE) {
 
 fun RoomSummary.toRoomAliasMatrixItem() = MatrixItem.RoomAliasItem(canonicalAlias ?: roomId, displayName, avatarUrl)
 
+fun RoomSummary.toEveryoneInRoomMatrixItem() = MatrixItem.EveryoneInRoomItem(roomId, MatrixItem.NOTIFY_EVERYONE, avatarUrl)
+
 // If no name is available, use room alias as Riot-Web does
 fun PublicRoom.toMatrixItem() = MatrixItem.RoomItem(roomId, name ?: getPrimaryAlias() ?: "", avatarUrl)
 
diff --git a/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberController.kt b/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberController.kt
index 5bbd4a39964..0684067ba83 100644
--- a/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberController.kt
+++ b/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberController.kt
@@ -16,19 +16,35 @@
 
 package im.vector.app.features.autocomplete.member
 
+import android.content.Context
 import com.airbnb.epoxy.TypedEpoxyController
+import im.vector.app.R
 import im.vector.app.features.autocomplete.AutocompleteClickListener
 import im.vector.app.features.autocomplete.autocompleteMatrixItem
 import im.vector.app.features.home.AvatarRenderer
+import org.matrix.android.sdk.api.util.toEveryoneInRoomMatrixItem
 import org.matrix.android.sdk.api.util.toMatrixItem
 import javax.inject.Inject
 
-class AutocompleteMemberController @Inject constructor() : TypedEpoxyController>() {
+class AutocompleteMemberController @Inject constructor(private val context: Context)
+    : TypedEpoxyController>() {
+
+    ///////////////////////////////////////////////////////////////////////////
+    // FIELDS
+    ///////////////////////////////////////////////////////////////////////////
 
     var listener: AutocompleteClickListener? = null
 
+    ///////////////////////////////////////////////////////////////////////////
+    // DEPENDENCIES
+    ///////////////////////////////////////////////////////////////////////////
+
     @Inject lateinit var avatarRenderer: AvatarRenderer
 
+    ///////////////////////////////////////////////////////////////////////////
+    // SPECIALIZATION
+    ///////////////////////////////////////////////////////////////////////////
+
     override fun buildModels(data: List?) {
         if (data.isNullOrEmpty()) {
             return
@@ -46,17 +62,30 @@ class AutocompleteMemberController @Inject constructor() : TypedEpoxyController<
     ///////////////////////////////////////////////////////////////////////////
 
     private fun buildRoomMemberItem(roomMember: AutocompleteMemberItem.RoomMember) {
+        val host = this
         autocompleteMatrixItem {
             roomMember.roomMemberSummary.let { user ->
                 id(user.userId)
                 matrixItem(user.toMatrixItem())
-                avatarRenderer(this@AutocompleteMemberController.avatarRenderer)
-                clickListener { this@AutocompleteMemberController.listener?.onItemClick(roomMember) }
+                avatarRenderer(host.avatarRenderer)
+                clickListener { host.listener?.onItemClick(roomMember) }
             }
         }
     }
 
     private fun buildEveryoneItem(everyone: AutocompleteMemberItem.Everyone) {
-        // TODO
+        val host = this
+
+        autocompleteMatrixItem {
+            everyone.roomSummary.let { room ->
+                id(room.roomId)
+                matrixItem(room.toEveryoneInRoomMatrixItem())
+                subName(host.context.getString(R.string.room_message_notify_everyone))
+                // TODO fix usage of first letter of room name when avatarUrl is empty
+                // TODO test avatar with a room which has a picture
+                avatarRenderer(host.avatarRenderer)
+                clickListener { host.listener?.onItemClick(everyone) }
+            }
+        }
     }
 }
diff --git a/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberPresenter.kt b/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberPresenter.kt
index bb7d41cc462..5640632ec2f 100644
--- a/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberPresenter.kt
+++ b/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberPresenter.kt
@@ -28,6 +28,7 @@ import org.matrix.android.sdk.api.session.Session
 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.RoomMemberSummary
+import org.matrix.android.sdk.api.util.MatrixItem
 
 class AutocompleteMemberPresenter @AssistedInject constructor(context: Context,
                                                               @Assisted val roomId: String,
@@ -68,14 +69,28 @@ class AutocompleteMemberPresenter @AssistedInject constructor(context: Context,
             memberships = listOf(Membership.JOIN)
             excludeSelf = true
         }
+
         val members = room.getRoomMembers(queryParams)
                 .asSequence()
                 .sortedBy { it.displayName }
                 .disambiguate()
+                .map { AutocompleteMemberItem.RoomMember(it) }
+                .toList()
+
         // TODO check if user can notify everyone => compare user role to room permission setting: PowerLevelsContent
-        // TODO if user can notify everyone, add entry AutocompleteMemberItem.Everyone
-        // TODO add header sections to separate members and notification
-        controller.setData(members.map { AutocompleteMemberItem.RoomMember(it) }.toList())
+        val everyone = room.roomSummary()
+                ?.takeIf { query.isNullOrBlank() || MatrixItem.NOTIFY_EVERYONE.startsWith("@$query") }
+                ?.let {
+            AutocompleteMemberItem.Everyone(it)
+        }
+
+        val items = mutableListOf().apply {
+            // TODO add header sections
+            addAll(members)
+            everyone?.let { add(it) }
+        }
+
+        controller.setData(items)
     }
 }
 
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/AutoCompleter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/AutoCompleter.kt
index 88e49f7062f..4bd38095a9f 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/AutoCompleter.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/AutoCompleter.kt
@@ -44,6 +44,7 @@ import im.vector.app.features.themes.ThemeUtils
 import org.matrix.android.sdk.api.session.group.model.GroupSummary
 import org.matrix.android.sdk.api.session.room.model.RoomSummary
 import org.matrix.android.sdk.api.util.MatrixItem
+import org.matrix.android.sdk.api.util.toEveryoneInRoomMatrixItem
 import org.matrix.android.sdk.api.util.toMatrixItem
 import org.matrix.android.sdk.api.util.toRoomAliasMatrixItem
 
@@ -135,7 +136,8 @@ class AutoCompleter @AssistedInject constructor(
                         when (item) {
                             is AutocompleteMemberItem.RoomMember ->
                                 insertMatrixItem(editText, editable, "@", item.roomMemberSummary.toMatrixItem())
-                            is AutocompleteMemberItem.Everyone   -> Unit // TODO
+                            is AutocompleteMemberItem.Everyone   ->
+                                insertMatrixItem(editText, editable, "@", item.roomSummary.toEveryoneInRoomMatrixItem())
                         }
                         return true
                     }
@@ -253,6 +255,7 @@ class AutoCompleter @AssistedInject constructor(
     }
 
     companion object {
+        // TODO add consts for string trigger for autocomplete
         private const val ELEVATION = 6f
     }
 }
diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml
index c155b6bb755..69a1c53d1ca 100644
--- a/vector/src/main/res/values/strings.xml
+++ b/vector/src/main/res/values/strings.xml
@@ -3782,4 +3782,7 @@
     Show less
     "%1$d more"
 
+    Notify the whole room
+    Users
+    Room notification
 

From 38317e6033966d4ad6a248ae69d9fe3f4091f7d5 Mon Sep 17 00:00:00 2001
From: Maxime Naturel 
Date: Thu, 10 Feb 2022 17:30:28 +0100
Subject: [PATCH 391/581] Using const for char triggers

---
 .../home/room/detail/AutoCompleter.kt         | 37 ++++++++++---------
 1 file changed, 20 insertions(+), 17 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/AutoCompleter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/AutoCompleter.kt
index 4bd38095a9f..550f89dd702 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/AutoCompleter.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/AutoCompleter.kt
@@ -107,7 +107,7 @@ class AutoCompleter @AssistedInject constructor(
         Autocomplete.on(editText)
                 .with(commandAutocompletePolicy)
                 .with(autocompleteCommandPresenter)
-                .with(ELEVATION)
+                .with(ELEVATION_DP)
                 .with(backgroundDrawable)
                 .with(object : AutocompleteCallback {
                     override fun onPopupItemClicked(editable: Editable, item: Command): Boolean {
@@ -127,17 +127,17 @@ class AutoCompleter @AssistedInject constructor(
     private fun setupMembers(backgroundDrawable: ColorDrawable, editText: EditText) {
         autocompleteMemberPresenter = autocompleteMemberPresenterFactory.create(roomId)
         Autocomplete.on(editText)
-                .with(CharPolicy('@', true))
+                .with(CharPolicy(TRIGGER_AUTO_COMPLETE_MEMBERS, true))
                 .with(autocompleteMemberPresenter)
-                .with(ELEVATION)
+                .with(ELEVATION_DP)
                 .with(backgroundDrawable)
                 .with(object : AutocompleteCallback {
                     override fun onPopupItemClicked(editable: Editable, item: AutocompleteMemberItem): Boolean {
                         when (item) {
                             is AutocompleteMemberItem.RoomMember ->
-                                insertMatrixItem(editText, editable, "@", item.roomMemberSummary.toMatrixItem())
+                                insertMatrixItem(editText, editable, TRIGGER_AUTO_COMPLETE_MEMBERS, item.roomMemberSummary.toMatrixItem())
                             is AutocompleteMemberItem.Everyone   ->
-                                insertMatrixItem(editText, editable, "@", item.roomSummary.toEveryoneInRoomMatrixItem())
+                                insertMatrixItem(editText, editable, TRIGGER_AUTO_COMPLETE_MEMBERS, item.roomSummary.toEveryoneInRoomMatrixItem())
                         }
                         return true
                     }
@@ -150,13 +150,13 @@ class AutoCompleter @AssistedInject constructor(
 
     private fun setupRooms(backgroundDrawable: ColorDrawable, editText: EditText) {
         Autocomplete.on(editText)
-                .with(CharPolicy('#', true))
+                .with(CharPolicy(TRIGGER_AUTO_COMPLETE_ROOMS, true))
                 .with(autocompleteRoomPresenter)
-                .with(ELEVATION)
+                .with(ELEVATION_DP)
                 .with(backgroundDrawable)
                 .with(object : AutocompleteCallback {
                     override fun onPopupItemClicked(editable: Editable, item: RoomSummary): Boolean {
-                        insertMatrixItem(editText, editable, "#", item.toRoomAliasMatrixItem())
+                        insertMatrixItem(editText, editable, TRIGGER_AUTO_COMPLETE_ROOMS, item.toRoomAliasMatrixItem())
                         return true
                     }
 
@@ -168,13 +168,13 @@ class AutoCompleter @AssistedInject constructor(
 
     private fun setupGroups(backgroundDrawable: ColorDrawable, editText: EditText) {
         Autocomplete.on(editText)
-                .with(CharPolicy('+', true))
+                .with(CharPolicy(TRIGGER_AUTO_COMPLETE_GROUPS, true))
                 .with(autocompleteGroupPresenter)
-                .with(ELEVATION)
+                .with(ELEVATION_DP)
                 .with(backgroundDrawable)
                 .with(object : AutocompleteCallback {
                     override fun onPopupItemClicked(editable: Editable, item: GroupSummary): Boolean {
-                        insertMatrixItem(editText, editable, "+", item.toMatrixItem())
+                        insertMatrixItem(editText, editable, TRIGGER_AUTO_COMPLETE_GROUPS, item.toMatrixItem())
                         return true
                     }
 
@@ -186,9 +186,9 @@ class AutoCompleter @AssistedInject constructor(
 
     private fun setupEmojis(backgroundDrawable: Drawable, editText: EditText) {
         Autocomplete.on(editText)
-                .with(CharPolicy(':', false))
+                .with(CharPolicy(TRIGGER_AUTO_COMPLETE_EMOJIS, false))
                 .with(autocompleteEmojiPresenter)
-                .with(ELEVATION)
+                .with(ELEVATION_DP)
                 .with(backgroundDrawable)
                 .with(object : AutocompleteCallback {
                     override fun onPopupItemClicked(editable: Editable, item: String): Boolean {
@@ -216,7 +216,7 @@ class AutoCompleter @AssistedInject constructor(
                 .build()
     }
 
-    private fun insertMatrixItem(editText: EditText, editable: Editable, firstChar: String, matrixItem: MatrixItem) {
+    private fun insertMatrixItem(editText: EditText, editable: Editable, firstChar: Char, matrixItem: MatrixItem) {
         // Detect last firstChar and remove it
         var startIndex = editable.lastIndexOf(firstChar)
         if (startIndex == -1) {
@@ -234,7 +234,7 @@ class AutoCompleter @AssistedInject constructor(
 
         // Adding trailing space " " or ": " if the user started mention someone
         val displayNameSuffix =
-                if (firstChar == "@" && startIndex == 0) {
+                if (firstChar == TRIGGER_AUTO_COMPLETE_MEMBERS && startIndex == 0) {
                     ": "
                 } else {
                     " "
@@ -255,7 +255,10 @@ class AutoCompleter @AssistedInject constructor(
     }
 
     companion object {
-        // TODO add consts for string trigger for autocomplete
-        private const val ELEVATION = 6f
+        private const val ELEVATION_DP = 6f
+        private const val TRIGGER_AUTO_COMPLETE_MEMBERS = '@'
+        private const val TRIGGER_AUTO_COMPLETE_ROOMS = '#'
+        private const val TRIGGER_AUTO_COMPLETE_GROUPS = '+'
+        private const val TRIGGER_AUTO_COMPLETE_EMOJIS = ':'
     }
 }

From 82ac302843b01c80723462b8ed63d1c9ab44c985 Mon Sep 17 00:00:00 2001
From: Maxime Naturel 
Date: Thu, 10 Feb 2022 17:50:48 +0100
Subject: [PATCH 392/581] Fixing avatar name when there is no room picture

---
 .../org/matrix/android/sdk/api/util/MatrixItem.kt     | 11 ++++++++---
 .../member/AutocompleteMemberController.kt            |  2 --
 2 files changed, 8 insertions(+), 5 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt
index 9ad1e42513e..1f3a94b2add 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt
@@ -45,7 +45,8 @@ sealed class MatrixItem(
 
     data class EveryoneInRoomItem(override val id: String,
                                   override val displayName: String? = null,
-                                  override val avatarUrl: String? = null) :
+                                  override val avatarUrl: String? = null,
+                                  val roomDisplayName: String? = null) :
             MatrixItem(id, displayName, avatarUrl) {
         init {
             if (BuildConfig.DEBUG) checkId()
@@ -131,7 +132,11 @@ sealed class MatrixItem(
     }
 
     fun firstLetterOfDisplayName(): String {
-        // TODO retrieve first letter of room name when EveryoneInRoomItem
+        val displayName = when (this) {
+            // use the room display name for the notify everyone item
+            is EveryoneInRoomItem -> roomDisplayName
+            else                  -> displayName
+        }
         return (displayName?.takeIf { it.isNotBlank() } ?: id)
                 .let { dn ->
                     var startIndex = 0
@@ -185,7 +190,7 @@ fun RoomSummary.toMatrixItem() = if (roomType == RoomType.SPACE) {
 
 fun RoomSummary.toRoomAliasMatrixItem() = MatrixItem.RoomAliasItem(canonicalAlias ?: roomId, displayName, avatarUrl)
 
-fun RoomSummary.toEveryoneInRoomMatrixItem() = MatrixItem.EveryoneInRoomItem(roomId, MatrixItem.NOTIFY_EVERYONE, avatarUrl)
+fun RoomSummary.toEveryoneInRoomMatrixItem() = MatrixItem.EveryoneInRoomItem(roomId, MatrixItem.NOTIFY_EVERYONE, avatarUrl, displayName)
 
 // If no name is available, use room alias as Riot-Web does
 fun PublicRoom.toMatrixItem() = MatrixItem.RoomItem(roomId, name ?: getPrimaryAlias() ?: "", avatarUrl)
diff --git a/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberController.kt b/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberController.kt
index 0684067ba83..2132cc2f1d0 100644
--- a/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberController.kt
+++ b/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberController.kt
@@ -81,8 +81,6 @@ class AutocompleteMemberController @Inject constructor(private val context: Cont
                 id(room.roomId)
                 matrixItem(room.toEveryoneInRoomMatrixItem())
                 subName(host.context.getString(R.string.room_message_notify_everyone))
-                // TODO fix usage of first letter of room name when avatarUrl is empty
-                // TODO test avatar with a room which has a picture
                 avatarRenderer(host.avatarRenderer)
                 clickListener { host.listener?.onItemClick(everyone) }
             }

From d214ef34dfb43d071f0c869341986a8cae122f73 Mon Sep 17 00:00:00 2001
From: Maxime Naturel 
Date: Thu, 10 Feb 2022 17:59:59 +0100
Subject: [PATCH 393/581] Updating changelog entry name

---
 changelog.d/5123.misc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/changelog.d/5123.misc b/changelog.d/5123.misc
index 790910e493f..cb1a7adf083 100644
--- a/changelog.d/5123.misc
+++ b/changelog.d/5123.misc
@@ -1 +1 @@
-Add completion for @room as per element web
+Add completion for @room to notify everyone in a room

From 2beff8d4cd4a266b8e54009de89deab43feac661 Mon Sep 17 00:00:00 2001
From: Maxime Naturel 
Date: Fri, 11 Feb 2022 11:03:20 +0100
Subject: [PATCH 394/581] adding headers to separate sections

---
 .../autocomplete/AutocompleteHeaderItem.kt    | 39 +++++++++++++++
 .../autocomplete/AutocompleteMatrixItem.kt    |  1 -
 .../member/AutocompleteMemberController.kt    |  9 ++++
 .../member/AutocompleteMemberItem.kt          |  2 +-
 .../member/AutocompleteMemberPresenter.kt     | 49 +++++++++++++++++--
 .../home/room/detail/AutoCompleter.kt         | 12 +++--
 .../layout/item_autocomplete_header_item.xml  | 21 ++++++++
 7 files changed, 122 insertions(+), 11 deletions(-)
 create mode 100644 vector/src/main/java/im/vector/app/features/autocomplete/AutocompleteHeaderItem.kt
 create mode 100644 vector/src/main/res/layout/item_autocomplete_header_item.xml

diff --git a/vector/src/main/java/im/vector/app/features/autocomplete/AutocompleteHeaderItem.kt b/vector/src/main/java/im/vector/app/features/autocomplete/AutocompleteHeaderItem.kt
new file mode 100644
index 00000000000..f287104415b
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/autocomplete/AutocompleteHeaderItem.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.autocomplete
+
+import android.widget.TextView
+import com.airbnb.epoxy.EpoxyAttribute
+import com.airbnb.epoxy.EpoxyModelClass
+import im.vector.app.R
+import im.vector.app.core.epoxy.VectorEpoxyHolder
+import im.vector.app.core.epoxy.VectorEpoxyModel
+
+@EpoxyModelClass(layout = R.layout.item_autocomplete_header_item)
+abstract class AutocompleteHeaderItem : VectorEpoxyModel() {
+
+    @EpoxyAttribute var title: String? = null
+
+    override fun bind(holder: Holder) {
+        super.bind(holder)
+        holder.titleView.text = title
+    }
+
+    class Holder : VectorEpoxyHolder() {
+        val titleView by bind(R.id.headerItemAutocompleteTitle)
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/autocomplete/AutocompleteMatrixItem.kt b/vector/src/main/java/im/vector/app/features/autocomplete/AutocompleteMatrixItem.kt
index d0e2c81b567..dba2661927b 100644
--- a/vector/src/main/java/im/vector/app/features/autocomplete/AutocompleteMatrixItem.kt
+++ b/vector/src/main/java/im/vector/app/features/autocomplete/AutocompleteMatrixItem.kt
@@ -30,7 +30,6 @@ import im.vector.app.features.displayname.getBestName
 import im.vector.app.features.home.AvatarRenderer
 import org.matrix.android.sdk.api.util.MatrixItem
 
-// TODO create a new item for sections: AutocompleteSection
 @EpoxyModelClass(layout = R.layout.item_autocomplete_matrix_item)
 abstract class AutocompleteMatrixItem : VectorEpoxyModel() {
 
diff --git a/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberController.kt b/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberController.kt
index 2132cc2f1d0..ce515ad6ad1 100644
--- a/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberController.kt
+++ b/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberController.kt
@@ -20,6 +20,7 @@ import android.content.Context
 import com.airbnb.epoxy.TypedEpoxyController
 import im.vector.app.R
 import im.vector.app.features.autocomplete.AutocompleteClickListener
+import im.vector.app.features.autocomplete.autocompleteHeaderItem
 import im.vector.app.features.autocomplete.autocompleteMatrixItem
 import im.vector.app.features.home.AvatarRenderer
 import org.matrix.android.sdk.api.util.toEveryoneInRoomMatrixItem
@@ -51,6 +52,7 @@ class AutocompleteMemberController @Inject constructor(private val context: Cont
         }
         data.forEach { item ->
             when (item) {
+                is AutocompleteMemberItem.Header     -> buildHeaderItem(item)
                 is AutocompleteMemberItem.RoomMember -> buildRoomMemberItem(item)
                 is AutocompleteMemberItem.Everyone   -> buildEveryoneItem(item)
             }
@@ -61,6 +63,13 @@ class AutocompleteMemberController @Inject constructor(private val context: Cont
     // HELPER METHODS
     ///////////////////////////////////////////////////////////////////////////
 
+    private fun buildHeaderItem(header: AutocompleteMemberItem.Header) {
+        autocompleteHeaderItem {
+            id(header.id)
+            title(header.title)
+        }
+    }
+
     private fun buildRoomMemberItem(roomMember: AutocompleteMemberItem.RoomMember) {
         val host = this
         autocompleteMatrixItem {
diff --git a/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberItem.kt b/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberItem.kt
index fa9693895f7..77c50699383 100644
--- a/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberItem.kt
+++ b/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberItem.kt
@@ -20,7 +20,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
 import org.matrix.android.sdk.api.session.room.model.RoomSummary
 
 sealed class AutocompleteMemberItem {
-    // TODO add section class
+    data class Header(val id: String, val title: String) : AutocompleteMemberItem()
     data class RoomMember(val roomMemberSummary: RoomMemberSummary) : AutocompleteMemberItem()
     data class Everyone(val roomSummary: RoomSummary) : AutocompleteMemberItem()
 }
diff --git a/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberPresenter.kt b/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberPresenter.kt
index 5640632ec2f..3830742adc5 100644
--- a/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberPresenter.kt
+++ b/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberPresenter.kt
@@ -21,6 +21,7 @@ import androidx.recyclerview.widget.RecyclerView
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
+import im.vector.app.R
 import im.vector.app.features.autocomplete.AutocompleteClickListener
 import im.vector.app.features.autocomplete.RecyclerViewPresenter
 import org.matrix.android.sdk.api.query.QueryStringValue
@@ -36,12 +37,24 @@ class AutocompleteMemberPresenter @AssistedInject constructor(context: Context,
                                                               private val controller: AutocompleteMemberController
 ) : RecyclerViewPresenter(context), AutocompleteClickListener {
 
+    ///////////////////////////////////////////////////////////////////////////
+    // FIELDS
+    ///////////////////////////////////////////////////////////////////////////
+
     private val room by lazy { session.getRoom(roomId)!! }
 
+    ///////////////////////////////////////////////////////////////////////////
+    // INIT
+    ///////////////////////////////////////////////////////////////////////////
+
     init {
         controller.listener = this
     }
 
+    ///////////////////////////////////////////////////////////////////////////
+    // PUBLIC API
+    ///////////////////////////////////////////////////////////////////////////
+
     fun clear() {
         controller.listener = null
     }
@@ -51,6 +64,10 @@ class AutocompleteMemberPresenter @AssistedInject constructor(context: Context,
         fun create(roomId: String): AutocompleteMemberPresenter
     }
 
+    ///////////////////////////////////////////////////////////////////////////
+    // SPECIALIZATION
+    ///////////////////////////////////////////////////////////////////////////
+
     override fun instantiateAdapter(): RecyclerView.Adapter<*> {
         return controller.adapter
     }
@@ -70,6 +87,10 @@ class AutocompleteMemberPresenter @AssistedInject constructor(context: Context,
             excludeSelf = true
         }
 
+        val membersHeader = AutocompleteMemberItem.Header(
+                ID_HEADER_MEMBERS,
+                context.getString(R.string.room_message_autocomplete_users)
+        )
         val members = room.getRoomMembers(queryParams)
                 .asSequence()
                 .sortedBy { it.displayName }
@@ -81,17 +102,35 @@ class AutocompleteMemberPresenter @AssistedInject constructor(context: Context,
         val everyone = room.roomSummary()
                 ?.takeIf { query.isNullOrBlank() || MatrixItem.NOTIFY_EVERYONE.startsWith("@$query") }
                 ?.let {
-            AutocompleteMemberItem.Everyone(it)
-        }
+                    AutocompleteMemberItem.Everyone(it)
+                }
 
         val items = mutableListOf().apply {
-            // TODO add header sections
-            addAll(members)
-            everyone?.let { add(it) }
+            if(members.isNotEmpty()) {
+                add(membersHeader)
+                addAll(members)
+            }
+            everyone?.let {
+                val everyoneHeader = AutocompleteMemberItem.Header(
+                        ID_HEADER_EVERYONE,
+                        context.getString(R.string.room_message_autocomplete_notification)
+                )
+                add(everyoneHeader)
+                add(it)
+            }
         }
 
         controller.setData(items)
     }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // CONST
+    ///////////////////////////////////////////////////////////////////////////
+
+    companion object {
+        private const val ID_HEADER_MEMBERS = "ID_HEADER_MEMBERS"
+        private const val ID_HEADER_EVERYONE = "ID_HEADER_EVERYONE"
+    }
 }
 
 private fun Sequence.disambiguate(): Sequence {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/AutoCompleter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/AutoCompleter.kt
index 550f89dd702..38548377aa0 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/AutoCompleter.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/AutoCompleter.kt
@@ -133,13 +133,17 @@ class AutoCompleter @AssistedInject constructor(
                 .with(backgroundDrawable)
                 .with(object : AutocompleteCallback {
                     override fun onPopupItemClicked(editable: Editable, item: AutocompleteMemberItem): Boolean {
-                        when (item) {
-                            is AutocompleteMemberItem.RoomMember ->
+                        return when (item) {
+                            is AutocompleteMemberItem.Header     -> false // do nothing header is not clickable
+                            is AutocompleteMemberItem.RoomMember -> {
                                 insertMatrixItem(editText, editable, TRIGGER_AUTO_COMPLETE_MEMBERS, item.roomMemberSummary.toMatrixItem())
-                            is AutocompleteMemberItem.Everyone   ->
+                                true
+                            }
+                            is AutocompleteMemberItem.Everyone   -> {
                                 insertMatrixItem(editText, editable, TRIGGER_AUTO_COMPLETE_MEMBERS, item.roomSummary.toEveryoneInRoomMatrixItem())
+                                true
+                            }
                         }
-                        return true
                     }
 
                     override fun onPopupVisibilityChanged(shown: Boolean) {
diff --git a/vector/src/main/res/layout/item_autocomplete_header_item.xml b/vector/src/main/res/layout/item_autocomplete_header_item.xml
new file mode 100644
index 00000000000..f842129e0c5
--- /dev/null
+++ b/vector/src/main/res/layout/item_autocomplete_header_item.xml
@@ -0,0 +1,21 @@
+
+
+
+    
+
+

From fb2401d0b1339da03ee3149544d79e4040c9a3fc Mon Sep 17 00:00:00 2001
From: Maxime Naturel 
Date: Fri, 11 Feb 2022 17:29:33 +0100
Subject: [PATCH 395/581] Fixing parsing of outcoming messages for @room chip
 (missing incoming messages)

---
 .../matrix/android/sdk/api/util/MatrixItem.kt |   5 +-
 .../session/room/send/pills/TextPillsUtils.kt |   3 +
 .../home/room/detail/AutoCompleter.kt         |   2 +-
 .../timeline/factory/MessageItemFactory.kt    |   1 +
 .../app/features/html/PillsPostProcessor.kt   | 109 +++++++++++++-----
 5 files changed, 88 insertions(+), 32 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt
index 1f3a94b2add..b665ef71161 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt
@@ -43,8 +43,9 @@ sealed class MatrixItem(
         override fun updateAvatar(newAvatar: String?) = copy(avatarUrl = newAvatar)
     }
 
+    // TODO is it correct to represent it by a Matrix Item ? => to confirm
     data class EveryoneInRoomItem(override val id: String,
-                                  override val displayName: String? = null,
+                                  override val displayName: String = NOTIFY_EVERYONE,
                                   override val avatarUrl: String? = null,
                                   val roomDisplayName: String? = null) :
             MatrixItem(id, displayName, avatarUrl) {
@@ -190,7 +191,7 @@ fun RoomSummary.toMatrixItem() = if (roomType == RoomType.SPACE) {
 
 fun RoomSummary.toRoomAliasMatrixItem() = MatrixItem.RoomAliasItem(canonicalAlias ?: roomId, displayName, avatarUrl)
 
-fun RoomSummary.toEveryoneInRoomMatrixItem() = MatrixItem.EveryoneInRoomItem(roomId, MatrixItem.NOTIFY_EVERYONE, avatarUrl, displayName)
+fun RoomSummary.toEveryoneInRoomMatrixItem() = MatrixItem.EveryoneInRoomItem(id = roomId, avatarUrl = avatarUrl, roomDisplayName = displayName)
 
 // If no name is available, use room alias as Riot-Web does
 fun PublicRoom.toMatrixItem() = MatrixItem.RoomItem(roomId, name ?: getPrimaryAlias() ?: "", avatarUrl)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/pills/TextPillsUtils.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/pills/TextPillsUtils.kt
index 33cb0db243e..ccbfbfcded2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/pills/TextPillsUtils.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/pills/TextPillsUtils.kt
@@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.session.room.send.pills
 
 import android.text.SpannableString
 import org.matrix.android.sdk.api.session.room.send.MatrixItemSpan
+import org.matrix.android.sdk.api.util.MatrixItem
 import org.matrix.android.sdk.internal.session.displayname.DisplayNameResolver
 import java.util.Collections
 import javax.inject.Inject
@@ -51,6 +52,8 @@ internal class TextPillsUtils @Inject constructor(
         val pills = spannableString
                 ?.getSpans(0, text.length, MatrixItemSpan::class.java)
                 ?.map { MentionLinkSpec(it, spannableString.getSpanStart(it), spannableString.getSpanEnd(it)) }
+                // we use the raw text for @room notification instead of a link
+                ?.filterNot { it.span.matrixItem is MatrixItem.EveryoneInRoomItem }
                 ?.toMutableList()
                 ?.takeIf { it.isNotEmpty() }
                 ?: return null
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/AutoCompleter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/AutoCompleter.kt
index 38548377aa0..be5f9c0bb48 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/AutoCompleter.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/AutoCompleter.kt
@@ -238,7 +238,7 @@ class AutoCompleter @AssistedInject constructor(
 
         // Adding trailing space " " or ": " if the user started mention someone
         val displayNameSuffix =
-                if (firstChar == TRIGGER_AUTO_COMPLETE_MEMBERS && startIndex == 0) {
+                if (matrixItem is MatrixItem.UserItem) {
                     ": "
                 } else {
                     " "
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 0c836748c85..9ab532e5bef 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
@@ -549,6 +549,7 @@ class MessageItemFactory @Inject constructor(
                                      highlight: Boolean,
                                      callback: TimelineEventController.Callback?,
                                      attributes: AbsMessageItem.Attributes): MessageTextItem? {
+        // TODO process body to add pills for @room texts: create a dedicated renderer with PillsPostProcessor ?
         val bindingOptions = spanUtils.getBindingOptions(body)
         val linkifiedBody = body.linkify(callback)
 
diff --git a/vector/src/main/java/im/vector/app/features/html/PillsPostProcessor.kt b/vector/src/main/java/im/vector/app/features/html/PillsPostProcessor.kt
index 8376d250650..b2dc190c6fd 100644
--- a/vector/src/main/java/im/vector/app/features/html/PillsPostProcessor.kt
+++ b/vector/src/main/java/im/vector/app/features/html/PillsPostProcessor.kt
@@ -36,58 +36,109 @@ class PillsPostProcessor @AssistedInject constructor(@Assisted private val roomI
                                                      private val context: Context,
                                                      private val avatarRenderer: AvatarRenderer,
                                                      private val sessionHolder: ActiveSessionHolder) :
-    EventHtmlRenderer.PostProcessor {
+        EventHtmlRenderer.PostProcessor {
 
     @AssistedFactory
     interface Factory {
         fun create(roomId: String?): PillsPostProcessor
     }
 
+    ///////////////////////////////////////////////////////////////////////////
+    // SPECIALIZATION
+    ///////////////////////////////////////////////////////////////////////////
+
     override fun afterRender(renderedText: Spannable) {
         addPillSpans(renderedText, roomId)
     }
 
+    ///////////////////////////////////////////////////////////////////////////
+    // HELPER METHODS
+    ///////////////////////////////////////////////////////////////////////////
+
     private fun addPillSpans(renderedText: Spannable, roomId: String?) {
+        addLinkSpans(renderedText, roomId)
+        roomId?.let { id -> addRawTextSpans(renderedText, id) }
+    }
+
+    private fun addPillSpan(
+            renderedText: Spannable,
+            pillSpan: PillImageSpan,
+            startSpan: Int,
+            endSpan: Int
+    ) {
+        renderedText.setSpan(pillSpan, startSpan, endSpan, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
+    }
+
+    private fun addLinkSpans(renderedText: Spannable, roomId: String?) {
         // We let markdown handle links and then we add PillImageSpan if needed.
         val linkSpans = renderedText.getSpans(0, renderedText.length, LinkSpan::class.java)
         linkSpans.forEach { linkSpan ->
             val pillSpan = linkSpan.createPillSpan(roomId) ?: return@forEach
             val startSpan = renderedText.getSpanStart(linkSpan)
             val endSpan = renderedText.getSpanEnd(linkSpan)
-            renderedText.setSpan(pillSpan, startSpan, endSpan, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
+            addPillSpan(renderedText, pillSpan, startSpan, endSpan)
+        }
+    }
+
+    // TODO move this into another PostProcessor when there is no html
+    private fun addRawTextSpans(renderedText: Spannable, roomId: String) {
+        if (renderedText.contains(MatrixItem.NOTIFY_EVERYONE)) {
+            addNotifyEveryoneSpans(renderedText, roomId)
+        }
+    }
+
+    private fun addNotifyEveryoneSpans(renderedText: Spannable, roomId: String) {
+        val room: RoomSummary? = sessionHolder.getSafeActiveSession()?.getRoomSummary(roomId)
+        val matrixItem = MatrixItem.EveryoneInRoomItem(
+                id = roomId,
+                avatarUrl = room?.avatarUrl,
+                roomDisplayName = room?.displayName
+        )
+        val pillSpan = createPillImageSpan(matrixItem)
+
+        // search for notify everyone text
+        var foundIndex = renderedText.indexOf(MatrixItem.NOTIFY_EVERYONE, 0)
+        while (foundIndex >= 0) {
+            val endSpan = foundIndex + MatrixItem.NOTIFY_EVERYONE.length
+            addPillSpan(renderedText, pillSpan, foundIndex, endSpan)
+            foundIndex = renderedText.indexOf(MatrixItem.NOTIFY_EVERYONE, endSpan)
         }
     }
 
+    private fun createPillImageSpan(matrixItem: MatrixItem) =
+            PillImageSpan(GlideApp.with(context), avatarRenderer, context, matrixItem)
+
     private fun LinkSpan.createPillSpan(roomId: String?): PillImageSpan? {
-        // TODO handle @room text to create associated chip
-        val permalinkData = PermalinkParser.parse(url)
-        val matrixItem = when (permalinkData) {
-            is PermalinkData.UserLink -> {
-                if (roomId == null) {
-                    sessionHolder.getSafeActiveSession()?.getUser(permalinkData.userId)?.toMatrixItem()
-                } else {
-                    sessionHolder.getSafeActiveSession()?.getRoomMember(permalinkData.userId, roomId)?.toMatrixItem()
-                }
+        val matrixItem = when (val permalinkData = PermalinkParser.parse(url)) {
+            is PermalinkData.UserLink  -> permalinkData.toMatrixItem(roomId)
+            is PermalinkData.RoomLink  -> permalinkData.toMatrixItem()
+            is PermalinkData.GroupLink -> permalinkData.toMatrixItem()
+            else                       -> null
+        } ?: return null
+        return createPillImageSpan(matrixItem)
+    }
+
+    private fun PermalinkData.UserLink.toMatrixItem(roomId: String?): MatrixItem? =
+            if (roomId == null) {
+                sessionHolder.getSafeActiveSession()?.getUser(userId)?.toMatrixItem()
+            } else {
+                sessionHolder.getSafeActiveSession()?.getRoomMember(userId, roomId)?.toMatrixItem()
             }
-            is PermalinkData.RoomLink -> {
-                if (permalinkData.eventId == null) {
-                    val room: RoomSummary? = sessionHolder.getSafeActiveSession()?.getRoomSummary(permalinkData.roomIdOrAlias)
-                    if (permalinkData.isRoomAlias) {
-                        MatrixItem.RoomAliasItem(permalinkData.roomIdOrAlias, room?.displayName, room?.avatarUrl)
-                    } else {
-                        MatrixItem.RoomItem(permalinkData.roomIdOrAlias, room?.displayName, room?.avatarUrl)
-                    }
-                } else {
-                    // Exclude event link (used in reply events, we do not want to pill the "in reply to")
-                    null
+
+    private fun PermalinkData.RoomLink.toMatrixItem(): MatrixItem? =
+            if (eventId == null) {
+                val room: RoomSummary? = sessionHolder.getSafeActiveSession()?.getRoomSummary(roomIdOrAlias)
+                when {
+                    isRoomAlias -> MatrixItem.RoomAliasItem(roomIdOrAlias, room?.displayName, room?.avatarUrl)
+                    else        -> MatrixItem.RoomItem(roomIdOrAlias, room?.displayName, room?.avatarUrl)
                 }
+            } else {
+                // Exclude event link (used in reply events, we do not want to pill the "in reply to")
+                null
             }
-            is PermalinkData.GroupLink -> {
-                val group = sessionHolder.getSafeActiveSession()?.getGroupSummary(permalinkData.groupId)
-                MatrixItem.GroupItem(permalinkData.groupId, group?.displayName, group?.avatarUrl)
-            }
-            else                       -> null
-        } ?: return null
-        return PillImageSpan(GlideApp.with(context), avatarRenderer, context, matrixItem)
+
+    private fun PermalinkData.GroupLink.toMatrixItem(): MatrixItem? {
+        val group = sessionHolder.getSafeActiveSession()?.getGroupSummary(groupId)
+        return MatrixItem.GroupItem(groupId, group?.displayName, group?.avatarUrl)
     }
 }

From 07a59e63a6e664183f80645cd0ede0e3561395c0 Mon Sep 17 00:00:00 2001
From: Maxime Naturel 
Date: Mon, 14 Feb 2022 12:28:18 +0100
Subject: [PATCH 396/581] Create chips for incoming messages

---
 .../timeline/factory/MessageItemFactory.kt    | 12 ++-
 .../timeline/render/EventTextRenderer.kt      | 95 +++++++++++++++++++
 .../app/features/html/PillsPostProcessor.kt   | 26 -----
 3 files changed, 104 insertions(+), 29 deletions(-)
 create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/EventTextRenderer.kt

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 9ab532e5bef..aa1758dd6c7 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
@@ -61,6 +61,7 @@ import im.vector.app.features.home.room.detail.timeline.item.RedactedMessageItem
 import im.vector.app.features.home.room.detail.timeline.item.RedactedMessageItem_
 import im.vector.app.features.home.room.detail.timeline.item.VerificationRequestItem
 import im.vector.app.features.home.room.detail.timeline.item.VerificationRequestItem_
+import im.vector.app.features.home.room.detail.timeline.render.EventTextRenderer
 import im.vector.app.features.home.room.detail.timeline.tools.createLinkMovementMethod
 import im.vector.app.features.home.room.detail.timeline.tools.linkify
 import im.vector.app.features.html.EventHtmlRenderer
@@ -112,6 +113,7 @@ class MessageItemFactory @Inject constructor(
         private val timelineMediaSizeProvider: TimelineMediaSizeProvider,
         private val htmlRenderer: Lazy,
         private val htmlCompressor: VectorHtmlCompressor,
+        private val textRendererFactory: EventTextRenderer.Factory,
         private val stringProvider: StringProvider,
         private val imageContentRenderer: ImageContentRenderer,
         private val messageInformationDataFactory: MessageInformationDataFactory,
@@ -138,6 +140,10 @@ class MessageItemFactory @Inject constructor(
         pillsPostProcessorFactory.create(roomId)
     }
 
+    private val textRenderer by lazy {
+        textRendererFactory.create(roomId)
+    }
+
     fun create(params: TimelineItemFactoryParams): VectorEpoxyModel<*>? {
         val event = params.event
         val highlight = params.isHighlighted
@@ -549,9 +555,9 @@ class MessageItemFactory @Inject constructor(
                                      highlight: Boolean,
                                      callback: TimelineEventController.Callback?,
                                      attributes: AbsMessageItem.Attributes): MessageTextItem? {
-        // TODO process body to add pills for @room texts: create a dedicated renderer with PillsPostProcessor ?
-        val bindingOptions = spanUtils.getBindingOptions(body)
-        val linkifiedBody = body.linkify(callback)
+        val renderedBody = textRenderer.render(body)
+        val bindingOptions = spanUtils.getBindingOptions(renderedBody)
+        val linkifiedBody = renderedBody.linkify(callback)
 
         return MessageTextItem_()
                 .message(
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/EventTextRenderer.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/EventTextRenderer.kt
new file mode 100644
index 00000000000..29761e0d680
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/EventTextRenderer.kt
@@ -0,0 +1,95 @@
+/*
+ * 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.render
+
+import android.content.Context
+import android.text.Spannable
+import android.text.SpannableStringBuilder
+import android.text.Spanned
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import im.vector.app.core.di.ActiveSessionHolder
+import im.vector.app.core.glide.GlideApp
+import im.vector.app.features.home.AvatarRenderer
+import im.vector.app.features.html.PillImageSpan
+import org.matrix.android.sdk.api.session.room.model.RoomSummary
+import org.matrix.android.sdk.api.util.MatrixItem
+import timber.log.Timber
+
+class EventTextRenderer @AssistedInject constructor(@Assisted private val roomId: String?,
+                                                    private val context: Context,
+                                                    private val avatarRenderer: AvatarRenderer,
+                                                    private val sessionHolder: ActiveSessionHolder) {
+
+    ///////////////////////////////////////////////////////////////////////////
+    // PUBLIC API
+    ///////////////////////////////////////////////////////////////////////////
+
+    @AssistedFactory
+    interface Factory {
+        fun create(roomId: String?): EventTextRenderer
+    }
+
+    /**
+     * @param text the text you want to render
+     */
+    fun render(text: CharSequence): CharSequence {
+        return if (roomId != null && text.contains(MatrixItem.NOTIFY_EVERYONE)) {
+            SpannableStringBuilder(text).apply {
+                addNotifyEveryoneSpans(this, roomId)
+            }
+        } else {
+            text
+        }
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // HELPER METHODS
+    ///////////////////////////////////////////////////////////////////////////
+
+    private fun addNotifyEveryoneSpans(text: Spannable, roomId: String) {
+        val room: RoomSummary? = sessionHolder.getSafeActiveSession()?.getRoomSummary(roomId)
+        val matrixItem = MatrixItem.EveryoneInRoomItem(
+                id = roomId,
+                avatarUrl = room?.avatarUrl,
+                roomDisplayName = room?.displayName
+        )
+
+        // search for notify everyone text
+        var foundIndex = text.indexOf(MatrixItem.NOTIFY_EVERYONE, 0)
+        while (foundIndex >= 0) {
+            val endSpan = foundIndex + MatrixItem.NOTIFY_EVERYONE.length
+            //text.setSpan(ForegroundColorSpan(Color.RED), foundIndex, endSpan, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
+            addPillSpan(text, createPillImageSpan(matrixItem), foundIndex, endSpan)
+            Timber.e("set span for text $text from index $foundIndex to $endSpan")
+            foundIndex = text.indexOf(MatrixItem.NOTIFY_EVERYONE, endSpan)
+        }
+    }
+
+    private fun createPillImageSpan(matrixItem: MatrixItem) =
+            PillImageSpan(GlideApp.with(context), avatarRenderer, context, matrixItem)
+
+    private fun addPillSpan(
+            renderedText: Spannable,
+            pillSpan: PillImageSpan,
+            startSpan: Int,
+            endSpan: Int
+    ) {
+        renderedText.setSpan(pillSpan, startSpan, endSpan, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/html/PillsPostProcessor.kt b/vector/src/main/java/im/vector/app/features/html/PillsPostProcessor.kt
index b2dc190c6fd..2c5115f481a 100644
--- a/vector/src/main/java/im/vector/app/features/html/PillsPostProcessor.kt
+++ b/vector/src/main/java/im/vector/app/features/html/PillsPostProcessor.kt
@@ -57,7 +57,6 @@ class PillsPostProcessor @AssistedInject constructor(@Assisted private val roomI
 
     private fun addPillSpans(renderedText: Spannable, roomId: String?) {
         addLinkSpans(renderedText, roomId)
-        roomId?.let { id -> addRawTextSpans(renderedText, id) }
     }
 
     private fun addPillSpan(
@@ -80,31 +79,6 @@ class PillsPostProcessor @AssistedInject constructor(@Assisted private val roomI
         }
     }
 
-    // TODO move this into another PostProcessor when there is no html
-    private fun addRawTextSpans(renderedText: Spannable, roomId: String) {
-        if (renderedText.contains(MatrixItem.NOTIFY_EVERYONE)) {
-            addNotifyEveryoneSpans(renderedText, roomId)
-        }
-    }
-
-    private fun addNotifyEveryoneSpans(renderedText: Spannable, roomId: String) {
-        val room: RoomSummary? = sessionHolder.getSafeActiveSession()?.getRoomSummary(roomId)
-        val matrixItem = MatrixItem.EveryoneInRoomItem(
-                id = roomId,
-                avatarUrl = room?.avatarUrl,
-                roomDisplayName = room?.displayName
-        )
-        val pillSpan = createPillImageSpan(matrixItem)
-
-        // search for notify everyone text
-        var foundIndex = renderedText.indexOf(MatrixItem.NOTIFY_EVERYONE, 0)
-        while (foundIndex >= 0) {
-            val endSpan = foundIndex + MatrixItem.NOTIFY_EVERYONE.length
-            addPillSpan(renderedText, pillSpan, foundIndex, endSpan)
-            foundIndex = renderedText.indexOf(MatrixItem.NOTIFY_EVERYONE, endSpan)
-        }
-    }
-
     private fun createPillImageSpan(matrixItem: MatrixItem) =
             PillImageSpan(GlideApp.with(context), avatarRenderer, context, matrixItem)
 

From 24a92d5a1eb6ae3f6497a05382bd8fa54391d246 Mon Sep 17 00:00:00 2001
From: Maxime Naturel 
Date: Mon, 14 Feb 2022 14:10:41 +0100
Subject: [PATCH 397/581] Setting background color for chips

---
 .../home/room/detail/timeline/render/EventTextRenderer.kt    | 3 ---
 .../main/java/im/vector/app/features/html/PillImageSpan.kt   | 5 +++++
 2 files changed, 5 insertions(+), 3 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/EventTextRenderer.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/EventTextRenderer.kt
index 29761e0d680..dab3f415703 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/EventTextRenderer.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/EventTextRenderer.kt
@@ -29,7 +29,6 @@ import im.vector.app.features.home.AvatarRenderer
 import im.vector.app.features.html.PillImageSpan
 import org.matrix.android.sdk.api.session.room.model.RoomSummary
 import org.matrix.android.sdk.api.util.MatrixItem
-import timber.log.Timber
 
 class EventTextRenderer @AssistedInject constructor(@Assisted private val roomId: String?,
                                                     private val context: Context,
@@ -74,9 +73,7 @@ class EventTextRenderer @AssistedInject constructor(@Assisted private val roomId
         var foundIndex = text.indexOf(MatrixItem.NOTIFY_EVERYONE, 0)
         while (foundIndex >= 0) {
             val endSpan = foundIndex + MatrixItem.NOTIFY_EVERYONE.length
-            //text.setSpan(ForegroundColorSpan(Color.RED), foundIndex, endSpan, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
             addPillSpan(text, createPillImageSpan(matrixItem), foundIndex, endSpan)
-            Timber.e("set span for text $text from index $foundIndex to $endSpan")
             foundIndex = text.indexOf(MatrixItem.NOTIFY_EVERYONE, endSpan)
         }
     }
diff --git a/vector/src/main/java/im/vector/app/features/html/PillImageSpan.kt b/vector/src/main/java/im/vector/app/features/html/PillImageSpan.kt
index ff2e2a9cdb0..3a388cfc697 100644
--- a/vector/src/main/java/im/vector/app/features/html/PillImageSpan.kt
+++ b/vector/src/main/java/im/vector/app/features/html/PillImageSpan.kt
@@ -19,6 +19,7 @@
 package im.vector.app.features.html
 
 import android.content.Context
+import android.content.res.ColorStateList
 import android.graphics.Canvas
 import android.graphics.Paint
 import android.graphics.drawable.Drawable
@@ -32,6 +33,7 @@ import im.vector.app.R
 import im.vector.app.core.glide.GlideRequests
 import im.vector.app.features.displayname.getBestName
 import im.vector.app.features.home.AvatarRenderer
+import im.vector.app.features.themes.ThemeUtils
 import org.matrix.android.sdk.api.session.room.send.MatrixItemSpan
 import org.matrix.android.sdk.api.util.MatrixItem
 import java.lang.ref.WeakReference
@@ -117,6 +119,9 @@ class PillImageSpan(private val glideRequests: GlideRequests,
             setChipMinHeightResource(R.dimen.pill_min_height)
             setChipIconSizeResource(R.dimen.pill_avatar_size)
             chipIcon = icon
+            if (matrixItem is MatrixItem.EveryoneInRoomItem) {
+                chipBackgroundColor = ColorStateList.valueOf(ThemeUtils.getColor(context, R.attr.colorError))
+            }
             setBounds(0, 0, intrinsicWidth, intrinsicHeight)
         }
     }

From df35da5571f9ccce6419306be5fad3970efd0b60 Mon Sep 17 00:00:00 2001
From: Maxime Naturel 
Date: Mon, 14 Feb 2022 14:43:24 +0100
Subject: [PATCH 398/581] Fixing code format issues

---
 .../member/AutocompleteMemberController.kt    | 28 ++++++++--------
 .../member/AutocompleteMemberPresenter.kt     | 32 +++++++++----------
 .../timeline/render/EventTextRenderer.kt      | 12 +++----
 .../app/features/html/PillsPostProcessor.kt   | 16 ++++++----
 4 files changed, 46 insertions(+), 42 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberController.kt b/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberController.kt
index ce515ad6ad1..04bcbd7f018 100644
--- a/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberController.kt
+++ b/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberController.kt
@@ -27,24 +27,24 @@ import org.matrix.android.sdk.api.util.toEveryoneInRoomMatrixItem
 import org.matrix.android.sdk.api.util.toMatrixItem
 import javax.inject.Inject
 
-class AutocompleteMemberController @Inject constructor(private val context: Context)
-    : TypedEpoxyController>() {
+class AutocompleteMemberController @Inject constructor(private val context: Context) :
+        TypedEpoxyController>() {
 
-    ///////////////////////////////////////////////////////////////////////////
-    // FIELDS
-    ///////////////////////////////////////////////////////////////////////////
+    /* ==========================================================================================
+     * Fields
+     * ========================================================================================== */
 
     var listener: AutocompleteClickListener? = null
 
-    ///////////////////////////////////////////////////////////////////////////
-    // DEPENDENCIES
-    ///////////////////////////////////////////////////////////////////////////
+    /* ==========================================================================================
+     * Dependencies
+     * ========================================================================================== */
 
     @Inject lateinit var avatarRenderer: AvatarRenderer
 
-    ///////////////////////////////////////////////////////////////////////////
-    // SPECIALIZATION
-    ///////////////////////////////////////////////////////////////////////////
+    /* ==========================================================================================
+     * Specialization
+     * ========================================================================================== */
 
     override fun buildModels(data: List?) {
         if (data.isNullOrEmpty()) {
@@ -59,9 +59,9 @@ class AutocompleteMemberController @Inject constructor(private val context: Cont
         }
     }
 
-    ///////////////////////////////////////////////////////////////////////////
-    // HELPER METHODS
-    ///////////////////////////////////////////////////////////////////////////
+    /* ==========================================================================================
+     * Helper methods
+     * ========================================================================================== */
 
     private fun buildHeaderItem(header: AutocompleteMemberItem.Header) {
         autocompleteHeaderItem {
diff --git a/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberPresenter.kt b/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberPresenter.kt
index 3830742adc5..73e5bfe7b9e 100644
--- a/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberPresenter.kt
+++ b/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberPresenter.kt
@@ -37,23 +37,23 @@ class AutocompleteMemberPresenter @AssistedInject constructor(context: Context,
                                                               private val controller: AutocompleteMemberController
 ) : RecyclerViewPresenter(context), AutocompleteClickListener {
 
-    ///////////////////////////////////////////////////////////////////////////
-    // FIELDS
-    ///////////////////////////////////////////////////////////////////////////
+    /* ==========================================================================================
+     * Fields
+     * ========================================================================================== */
 
     private val room by lazy { session.getRoom(roomId)!! }
 
-    ///////////////////////////////////////////////////////////////////////////
-    // INIT
-    ///////////////////////////////////////////////////////////////////////////
+    /* ==========================================================================================
+     * Init
+     * ========================================================================================== */
 
     init {
         controller.listener = this
     }
 
-    ///////////////////////////////////////////////////////////////////////////
-    // PUBLIC API
-    ///////////////////////////////////////////////////////////////////////////
+    /* ==========================================================================================
+     * Public api
+     * ========================================================================================== */
 
     fun clear() {
         controller.listener = null
@@ -64,9 +64,9 @@ class AutocompleteMemberPresenter @AssistedInject constructor(context: Context,
         fun create(roomId: String): AutocompleteMemberPresenter
     }
 
-    ///////////////////////////////////////////////////////////////////////////
-    // SPECIALIZATION
-    ///////////////////////////////////////////////////////////////////////////
+    /* ==========================================================================================
+     * Specialization
+     * ========================================================================================== */
 
     override fun instantiateAdapter(): RecyclerView.Adapter<*> {
         return controller.adapter
@@ -106,7 +106,7 @@ class AutocompleteMemberPresenter @AssistedInject constructor(context: Context,
                 }
 
         val items = mutableListOf().apply {
-            if(members.isNotEmpty()) {
+            if (members.isNotEmpty()) {
                 add(membersHeader)
                 addAll(members)
             }
@@ -123,9 +123,9 @@ class AutocompleteMemberPresenter @AssistedInject constructor(context: Context,
         controller.setData(items)
     }
 
-    ///////////////////////////////////////////////////////////////////////////
-    // CONST
-    ///////////////////////////////////////////////////////////////////////////
+    /* ==========================================================================================
+     * Const
+     * ========================================================================================== */
 
     companion object {
         private const val ID_HEADER_MEMBERS = "ID_HEADER_MEMBERS"
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/EventTextRenderer.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/EventTextRenderer.kt
index dab3f415703..d50a6fb297d 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/EventTextRenderer.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/render/EventTextRenderer.kt
@@ -35,9 +35,9 @@ class EventTextRenderer @AssistedInject constructor(@Assisted private val roomId
                                                     private val avatarRenderer: AvatarRenderer,
                                                     private val sessionHolder: ActiveSessionHolder) {
 
-    ///////////////////////////////////////////////////////////////////////////
-    // PUBLIC API
-    ///////////////////////////////////////////////////////////////////////////
+    /* ==========================================================================================
+     * Public api
+     * ========================================================================================== */
 
     @AssistedFactory
     interface Factory {
@@ -57,9 +57,9 @@ class EventTextRenderer @AssistedInject constructor(@Assisted private val roomId
         }
     }
 
-    ///////////////////////////////////////////////////////////////////////////
-    // HELPER METHODS
-    ///////////////////////////////////////////////////////////////////////////
+    /* ==========================================================================================
+     * Helper methods
+     * ========================================================================================== */
 
     private fun addNotifyEveryoneSpans(text: Spannable, roomId: String) {
         val room: RoomSummary? = sessionHolder.getSafeActiveSession()?.getRoomSummary(roomId)
diff --git a/vector/src/main/java/im/vector/app/features/html/PillsPostProcessor.kt b/vector/src/main/java/im/vector/app/features/html/PillsPostProcessor.kt
index 2c5115f481a..506f5e773c7 100644
--- a/vector/src/main/java/im/vector/app/features/html/PillsPostProcessor.kt
+++ b/vector/src/main/java/im/vector/app/features/html/PillsPostProcessor.kt
@@ -38,22 +38,26 @@ class PillsPostProcessor @AssistedInject constructor(@Assisted private val roomI
                                                      private val sessionHolder: ActiveSessionHolder) :
         EventHtmlRenderer.PostProcessor {
 
+    /* ==========================================================================================
+     * Public api
+     * ========================================================================================== */
+
     @AssistedFactory
     interface Factory {
         fun create(roomId: String?): PillsPostProcessor
     }
 
-    ///////////////////////////////////////////////////////////////////////////
-    // SPECIALIZATION
-    ///////////////////////////////////////////////////////////////////////////
+    /* ==========================================================================================
+     * Specialization
+     * ========================================================================================== */
 
     override fun afterRender(renderedText: Spannable) {
         addPillSpans(renderedText, roomId)
     }
 
-    ///////////////////////////////////////////////////////////////////////////
-    // HELPER METHODS
-    ///////////////////////////////////////////////////////////////////////////
+    /* ==========================================================================================
+     * Helper methods
+     * ========================================================================================== */
 
     private fun addPillSpans(renderedText: Spannable, roomId: String?) {
         addLinkSpans(renderedText, roomId)

From 49596dcea3edc9b52cffca078b231308bc75d82e Mon Sep 17 00:00:00 2001
From: Maxime Naturel 
Date: Mon, 14 Feb 2022 15:57:23 +0100
Subject: [PATCH 399/581] Mocking check of permission to notify room

---
 .../session/room/model/PowerLevelsContent.kt  |  1 -
 .../member/AutocompleteMemberController.kt    |  1 -
 .../member/AutocompleteMemberPresenter.kt     | 86 ++++++++++++-------
 3 files changed, 55 insertions(+), 33 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContent.kt
index 371fb922e6a..5c46db7166e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContent.kt
@@ -100,7 +100,6 @@ data class PowerLevelsContent(
         }
     }
 
-    // TODO use this to check if user can notify everyone => compare user role to room permission setting
     companion object {
         /**
          * Key to use for content.notifications and get the level required to trigger an @room notification. Defaults to 50 if unspecified.
diff --git a/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberController.kt b/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberController.kt
index 04bcbd7f018..2034cee90af 100644
--- a/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberController.kt
+++ b/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberController.kt
@@ -84,7 +84,6 @@ class AutocompleteMemberController @Inject constructor(private val context: Cont
 
     private fun buildEveryoneItem(everyone: AutocompleteMemberItem.Everyone) {
         val host = this
-
         autocompleteMatrixItem {
             everyone.roomSummary.let { room ->
                 id(room.roomId)
diff --git a/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberPresenter.kt b/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberPresenter.kt
index 73e5bfe7b9e..476c7d850c4 100644
--- a/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberPresenter.kt
+++ b/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberPresenter.kt
@@ -26,6 +26,7 @@ import im.vector.app.features.autocomplete.AutocompleteClickListener
 import im.vector.app.features.autocomplete.RecyclerViewPresenter
 import org.matrix.android.sdk.api.query.QueryStringValue
 import org.matrix.android.sdk.api.session.Session
+import org.matrix.android.sdk.api.session.room.members.RoomMemberQueryParams
 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.RoomMemberSummary
@@ -77,33 +78,10 @@ class AutocompleteMemberPresenter @AssistedInject constructor(context: Context,
     }
 
     override fun onQuery(query: CharSequence?) {
-        val queryParams = roomMemberQueryParams {
-            displayName = if (query.isNullOrBlank()) {
-                QueryStringValue.IsNotEmpty
-            } else {
-                QueryStringValue.Contains(query.toString(), QueryStringValue.Case.INSENSITIVE)
-            }
-            memberships = listOf(Membership.JOIN)
-            excludeSelf = true
-        }
-
-        val membersHeader = AutocompleteMemberItem.Header(
-                ID_HEADER_MEMBERS,
-                context.getString(R.string.room_message_autocomplete_users)
-        )
-        val members = room.getRoomMembers(queryParams)
-                .asSequence()
-                .sortedBy { it.displayName }
-                .disambiguate()
-                .map { AutocompleteMemberItem.RoomMember(it) }
-                .toList()
-
-        // TODO check if user can notify everyone => compare user role to room permission setting: PowerLevelsContent
-        val everyone = room.roomSummary()
-                ?.takeIf { query.isNullOrBlank() || MatrixItem.NOTIFY_EVERYONE.startsWith("@$query") }
-                ?.let {
-                    AutocompleteMemberItem.Everyone(it)
-                }
+        val queryParams = createQueryParams(query)
+        val membersHeader = createMembersHeader()
+        val members = createMemberItems(queryParams)
+        val everyone = createEveryoneItem(query)
 
         val items = mutableListOf().apply {
             if (members.isNotEmpty()) {
@@ -111,10 +89,7 @@ class AutocompleteMemberPresenter @AssistedInject constructor(context: Context,
                 addAll(members)
             }
             everyone?.let {
-                val everyoneHeader = AutocompleteMemberItem.Header(
-                        ID_HEADER_EVERYONE,
-                        context.getString(R.string.room_message_autocomplete_notification)
-                )
+                val everyoneHeader = createEveryoneHeader()
                 add(everyoneHeader)
                 add(it)
             }
@@ -123,6 +98,55 @@ class AutocompleteMemberPresenter @AssistedInject constructor(context: Context,
         controller.setData(items)
     }
 
+    /* ==========================================================================================
+     * Helper methods
+     * ========================================================================================== */
+
+    private fun createQueryParams(query: CharSequence?) = roomMemberQueryParams {
+        displayName = if (query.isNullOrBlank()) {
+            QueryStringValue.IsNotEmpty
+        } else {
+            QueryStringValue.Contains(query.toString(), QueryStringValue.Case.INSENSITIVE)
+        }
+        memberships = listOf(Membership.JOIN)
+        excludeSelf = true
+    }
+
+    private fun createMembersHeader() =
+            AutocompleteMemberItem.Header(
+                    ID_HEADER_MEMBERS,
+                    context.getString(R.string.room_message_autocomplete_users)
+            )
+
+    private fun createMemberItems(queryParams: RoomMemberQueryParams) =
+            room.getRoomMembers(queryParams)
+                    .asSequence()
+                    .sortedBy { it.displayName }
+                    .disambiguate()
+                    .map { AutocompleteMemberItem.RoomMember(it) }
+                    .toList()
+
+    private fun createEveryoneHeader() =
+            AutocompleteMemberItem.Header(
+                    ID_HEADER_EVERYONE,
+                    context.getString(R.string.room_message_autocomplete_notification)
+            )
+
+    private fun createEveryoneItem(query: CharSequence?) =
+            room.roomSummary()
+                    ?.takeIf { canNotifyEveryone() }
+                    ?.takeIf { query.isNullOrBlank() || MatrixItem.NOTIFY_EVERYONE.startsWith("@$query") }
+                    ?.let {
+                        AutocompleteMemberItem.Everyone(it)
+                    }
+
+    private fun canNotifyEveryone() = true
+    // TODO use session object to check ?
+    /*conditionResolver.resolveSenderNotificationPermissionCondition(
+            Event(roomId = roomId),
+            SenderNotificationPermissionCondition(PowerLevelsContent.NOTIFICATIONS_ROOM_KEY)
+    )*/
+
     /* ==========================================================================================
      * Const
      * ========================================================================================== */

From 10d196596c6e7a28a63d85f23319c2b9e5e4fd9b Mon Sep 17 00:00:00 2001
From: Maxime Naturel 
Date: Tue, 15 Feb 2022 11:19:16 +0100
Subject: [PATCH 400/581] Unmocking check of permission to notify room

---
 .../android/sdk/api/pushrules/PushRuleService.kt |  3 +++
 .../notification/DefaultPushRuleService.kt       |  7 +++++++
 .../member/AutocompleteMemberPresenter.kt        | 16 ++++++++++------
 3 files changed, 20 insertions(+), 6 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushRuleService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushRuleService.kt
index 88268f0f86e..76885d85454 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushRuleService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushRuleService.kt
@@ -50,6 +50,9 @@ interface PushRuleService {
 
 //    fun fulfilledBingRule(event: Event, rules: List): PushRule?
 
+    fun resolveSenderNotificationPermissionCondition(event: Event,
+                                                     condition: SenderNotificationPermissionCondition): Boolean
+
     interface PushRuleListener {
         fun onEvents(pushEvents: PushEvents)
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt
index 3e821b8956f..cdc7350f8bc 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt
@@ -19,11 +19,13 @@ import androidx.lifecycle.LiveData
 import androidx.lifecycle.Transformations
 import com.zhuinden.monarchy.Monarchy
 import org.matrix.android.sdk.api.pushrules.Action
+import org.matrix.android.sdk.api.pushrules.ConditionResolver
 import org.matrix.android.sdk.api.pushrules.PushEvents
 import org.matrix.android.sdk.api.pushrules.PushRuleService
 import org.matrix.android.sdk.api.pushrules.RuleKind
 import org.matrix.android.sdk.api.pushrules.RuleScope
 import org.matrix.android.sdk.api.pushrules.RuleSetKey
+import org.matrix.android.sdk.api.pushrules.SenderNotificationPermissionCondition
 import org.matrix.android.sdk.api.pushrules.getActions
 import org.matrix.android.sdk.api.pushrules.rest.PushRule
 import org.matrix.android.sdk.api.pushrules.rest.RuleSet
@@ -53,6 +55,7 @@ internal class DefaultPushRuleService @Inject constructor(
         private val removePushRuleTask: RemovePushRuleTask,
         private val pushRuleFinder: PushRuleFinder,
         private val taskExecutor: TaskExecutor,
+        private val conditionResolver: ConditionResolver,
         @SessionDatabase private val monarchy: Monarchy
 ) : PushRuleService {
 
@@ -143,6 +146,10 @@ internal class DefaultPushRuleService @Inject constructor(
         return pushRuleFinder.fulfilledBingRule(event, rules)?.getActions().orEmpty()
     }
 
+    override fun resolveSenderNotificationPermissionCondition(event: Event, condition: SenderNotificationPermissionCondition): Boolean {
+        return conditionResolver.resolveSenderNotificationPermissionCondition(event, condition)
+    }
+
     override fun getKeywords(): LiveData> {
         // Keywords are all content rules that don't start with '.'
         val liveData = monarchy.findAllMappedWithChanges(
diff --git a/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberPresenter.kt b/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberPresenter.kt
index 476c7d850c4..ab4b598153e 100644
--- a/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberPresenter.kt
+++ b/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberPresenter.kt
@@ -24,17 +24,20 @@ import dagger.assisted.AssistedInject
 import im.vector.app.R
 import im.vector.app.features.autocomplete.AutocompleteClickListener
 import im.vector.app.features.autocomplete.RecyclerViewPresenter
+import org.matrix.android.sdk.api.pushrules.SenderNotificationPermissionCondition
 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.room.members.RoomMemberQueryParams
 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.PowerLevelsContent
 import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
 import org.matrix.android.sdk.api.util.MatrixItem
 
 class AutocompleteMemberPresenter @AssistedInject constructor(context: Context,
                                                               @Assisted val roomId: String,
-                                                              session: Session,
+                                                              private val session: Session,
                                                               private val controller: AutocompleteMemberController
 ) : RecyclerViewPresenter(context), AutocompleteClickListener {
 
@@ -140,12 +143,13 @@ class AutocompleteMemberPresenter @AssistedInject constructor(context: Context,
                         AutocompleteMemberItem.Everyone(it)
                     }
 
-    private fun canNotifyEveryone() = true
-    // TODO use session object to check ?
-    /*conditionResolver.resolveSenderNotificationPermissionCondition(
-            Event(roomId = roomId),
+    private fun canNotifyEveryone() = session.resolveSenderNotificationPermissionCondition(
+            Event(
+                    senderId = session.myUserId,
+                    roomId = roomId
+            ),
             SenderNotificationPermissionCondition(PowerLevelsContent.NOTIFICATIONS_ROOM_KEY)
-    )*/
+    )
 
     /* ==========================================================================================
      * Const

From 6736bebc9cd75dce1d629e6778cd3563628aa2cb Mon Sep 17 00:00:00 2001
From: Maxime Naturel 
Date: Tue, 15 Feb 2022 11:28:06 +0100
Subject: [PATCH 401/581] Changing changelog entry type to feature

---
 changelog.d/{5123.misc => 5123.feature} | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename changelog.d/{5123.misc => 5123.feature} (100%)

diff --git a/changelog.d/5123.misc b/changelog.d/5123.feature
similarity index 100%
rename from changelog.d/5123.misc
rename to changelog.d/5123.feature

From 37a990368c6e03591a01cb2cebd0ab39d5a9f5c9 Mon Sep 17 00:00:00 2001
From: Maxime Naturel 
Date: Tue, 15 Feb 2022 15:20:06 +0100
Subject: [PATCH 402/581] Fixing text color of pills in light theme

---
 library/ui-styles/src/main/res/values/text_appearances.xml  | 6 +++++-
 .../main/java/im/vector/app/features/html/PillImageSpan.kt  | 2 ++
 2 files changed, 7 insertions(+), 1 deletion(-)

diff --git a/library/ui-styles/src/main/res/values/text_appearances.xml b/library/ui-styles/src/main/res/values/text_appearances.xml
index 4ad3fd493e1..8e30dd00d69 100644
--- a/library/ui-styles/src/main/res/values/text_appearances.xml
+++ b/library/ui-styles/src/main/res/values/text_appearances.xml
@@ -59,6 +59,10 @@
         sans-serif-medium
     
 
+    
+
     
 
-
\ No newline at end of file
+
diff --git a/vector/src/main/java/im/vector/app/features/html/PillImageSpan.kt b/vector/src/main/java/im/vector/app/features/html/PillImageSpan.kt
index 3a388cfc697..ae285b074cf 100644
--- a/vector/src/main/java/im/vector/app/features/html/PillImageSpan.kt
+++ b/vector/src/main/java/im/vector/app/features/html/PillImageSpan.kt
@@ -121,6 +121,8 @@ class PillImageSpan(private val glideRequests: GlideRequests,
             chipIcon = icon
             if (matrixItem is MatrixItem.EveryoneInRoomItem) {
                 chipBackgroundColor = ColorStateList.valueOf(ThemeUtils.getColor(context, R.attr.colorError))
+                // setTextColor API does not exist right now for ChipDrawable, use textAppearance
+                setTextAppearanceResource(R.style.TextAppearance_Vector_Body_OnError)
             }
             setBounds(0, 0, intrinsicWidth, intrinsicHeight)
         }

From 96ed30ccc45ab47326a5058b73906be085540a98 Mon Sep 17 00:00:00 2001
From: Maxime Naturel 
Date: Thu, 17 Feb 2022 11:38:28 +0100
Subject: [PATCH 403/581] Adding header section only when necessary

---
 .../member/AutocompleteMemberPresenter.kt            | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberPresenter.kt b/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberPresenter.kt
index ab4b598153e..b646e22f173 100644
--- a/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberPresenter.kt
+++ b/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberPresenter.kt
@@ -88,12 +88,18 @@ class AutocompleteMemberPresenter @AssistedInject constructor(context: Context,
 
         val items = mutableListOf().apply {
             if (members.isNotEmpty()) {
-                add(membersHeader)
+                if (everyone != null) {
+                    // add header only when there is everyone tag as well
+                    add(membersHeader)
+                }
                 addAll(members)
             }
             everyone?.let {
-                val everyoneHeader = createEveryoneHeader()
-                add(everyoneHeader)
+                if (members.isNotEmpty()) {
+                    // add header only when there are members as well
+                    val everyoneHeader = createEveryoneHeader()
+                    add(everyoneHeader)
+                }
                 add(it)
             }
         }

From d86d6526863cccb0a2cb656c24a2ecbc23225431 Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Thu, 17 Feb 2022 11:10:18 +0000
Subject: [PATCH 404/581] Translated using Weblate (Japanese)

Currently translated at 72.2% (2012 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 745 +++++++++++++---------
 1 file changed, 447 insertions(+), 298 deletions(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index a491d76b861..fbad27f1dc7 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -82,7 +82,7 @@
     招待
     ステッカーを送信しました。
     画像を送信しました。
-    部屋
+    ルーム
     設定
     履歴
     OK
@@ -95,7 +95,7 @@
     共有
     削除
     端末情報
-    暗号化された部屋での多人数通話はサポートされていません
+    暗号化されたルームでのグループ通話はサポートされていません
     招待
     全ての発言を既読にする
     履歴
@@ -106,11 +106,11 @@
     警告
     お気に入り
     会話
-    部屋
-    ルーム名で絞り込み
-    お気に入りで絞り込み
-    会話で絞り込み
-    ルームで絞り込み
+    ルーム
+    ルーム名で絞り込む
+    お気に入りで絞り込む
+    ユーザーで絞り込む
+    ルーム名で絞り込む
     招待中
     低優先度
     会話
@@ -129,7 +129,7 @@
     前回アプリケーションは正常に停止しませんでした。クラッシュ報告の画面を開きますか?
     不具合を報告しました
     不具合の報告の送信に失敗しました (%s)
-    部屋へ参加
+    ルームに参加
     通話が開始できません
     音声通話
     ビデオ通話
@@ -141,14 +141,14 @@
     ログイン画面へ戻る
     電話番号
     電話番号 (任意で)
-    ユーザ名かパスワードが正しくありません
-    ユーザ名は半角英数字、ドット、ハイフン、アンダスコアのみで記して下さい
+    ユーザー名かパスワードが正しくありません
+    ユーザー名は半角英数字、ドット、ハイフン、アンダスコアのみで記して下さい
     パスワードが短すぎます(最小6文字)
     正しくない電子メールアドレスのようです
     正しくない電話番号のようです
     すでに登録されている電子メールアドレスです。
     パスワードが一致しません
-    パスワードを忘れた?
+    パスワードを忘れましたか?
     登録を続行するには電子メールを確認して下さい
     URLはhttp://か、https://で始めて下さい
     ログインできません:通信エラー
@@ -156,7 +156,7 @@
     登録できません:通信エラー
     登録できません
     正しいURLを入力して下さい
-    ユーザ名かパスワードが正しくありません
+    ユーザー名かパスワードが正しくありません
     原寸
     大き目
     中程度
@@ -167,21 +167,21 @@
     %1$d分 %2$d秒
     昨日
     今日
-    部屋名
+    ルーム名
     通話終了
     呼び出し中です…
     はい
     いいえ
     続ける
     最新の未読へ移動
-    部屋を退室
-    本当にこの部屋を退室しますか?
+    ルームを退出
+    このルームを退出してよろしいですか?
     作成
     接続中
     切断中
     待機中
     招待
-    この部屋を退室する
+    このルームを退出する
     検索
     %sさんが文字入力中…
     %1$sさんと %2$sさんが文字入力中…
@@ -202,10 +202,10 @@
     確認
     無効にする
     送信中 (%s%%)
-    このユーザ名はすでに使用されています
+    このユーザー名はすでに使用されています
     削除
     参加
-    あなたは %s さんに呼ばれてこの部屋へ参加しました
+    あなたは %s さんに、このルームへ招待されています
     新しい会話
     参加者を追加
     1名
@@ -214,19 +214,19 @@
     権限を司会者へ変更
     権限を管理者へ変更
     ここに送信文を入力 (暗号なし)…
-    サーバとの接続が失われました.
+    サーバーとの接続が失われました.
     全て再送信
     未送信の文を再送信
     未送信の文を削除
     ファイルが見つかりません
     あなたはこのルームで発言する権限がありません。
-    部屋の詳細
+    ルームの詳細
     参加者
     ファイル
     設定
     お気に入り
     低優先度
-    会話
+    対話
     忘れる
     自分のアイコン画像
     表示名
@@ -238,8 +238,8 @@
     このアカウントで通知を許可
     この端末で通知を許可
     会話で発言されたとき
-    部屋で発言されたとき
-    部屋へ招待されたとき
+    グループチャットでのメッセージ
+    ルームへ招待されたとき
     通話の呼び出しがあったとき
     自動発言プログラム(Bot)が発言した時
     端末起動時に開始
@@ -260,7 +260,7 @@
     端末の電話帳
     端末の電話帳の使用を許可
     電話帳の国番号
-    端末
+    セッション一覧
     全てのメッセージにタイムスタンプを表示
     タイムスタンプを12時間形式で表示
     端末詳細
@@ -272,7 +272,7 @@
     認証
     この操作には追加の認証が必要です。
 \n続行するには、パスワードを入力してください。
-    受諾
+    送信
     ログイン中のアカウント
     言語を選択
     言語
@@ -293,48 +293,48 @@
     1週間
     1ヵ月
     永久に
-    部屋の画像アイコン
-    部屋名
-    部屋の説明
-    部屋の属性
-    部屋の属性:
+    ルームの画像アイコン
+    ルーム名
+    ルームの説明
+    ルームの属性
+    ルームの属性:
     お気に入り
     低優先度
     なし
     参加と可視範囲
-    部屋一覧へ公開する
-    部屋への参加
-    部屋の発言履歴の可視範囲
-    部屋の発言履歴を読める人は?
-    部屋へ参加できる人は?
+    ルームの一覧へ公開する
+    ルームへの参加
+    ルームの履歴の可視範囲
+    ルームの履歴を読める人は\?
+    ルームへ参加できる人は\?
     誰でも
     参加者のみ (この設定を選択した時点から)
     参加者のみ (招待を送った時点から)
     参加者のみ (参加した時点から)
-    この部屋に招待された人のみ参加可能
-    部屋のリンクを知る人なら誰でも(ゲストユーザを除く)
-    部屋のリンクを知る人なら誰でも(ゲストユーザも含む)
+    このルームに招待された人だけ
+    ルームのリンクを知る人なら誰でも(ゲストユーザーを除く)
+    ルームのリンクを知る人なら誰でも(ゲストユーザーも含む)
     再入室禁止された参加者
     拡張設定
-    この部屋のサーバ内識別ID
+    このルームのサーバー内識別ID
     実験的
-    これらは予期せぬ不具合が生じるかもしれない実験的機能です. 慎重に使用してください.
+    これらは予期せぬ不具合が生じるかもしれない実験的機能です。慎重に使用してください。
     エンドツーエンド暗号化
     エンドツーエンド暗号化を使用中
     暗号を有効にするためにはログアウトする必要があります.
     認証された端末のみで暗号化
-    この部屋では, この端末から認証されていない端末への暗号送信をしません.
+    このセッションでは、このルームの未検証のセッションに対して暗号化されたメッセージを送信しない。
     新しいアドレス (記入例 #foo:matrix.org)
-    この部屋はサーバ内住所表記がありません
+    このルームにはローカルアドレスがありません
     住所表記
     住所表記が正しくありません
     \'%s\' は正しくない形式の住所表記です
-    この部屋の本住所表記が設定されていません.
-    本住所表記の警告
+    このルームのメインアドレスが設定されていません。
+    メインアドレスの警告
     メインアドレスとして設定
     メインアドレスとして設定を解除
-    部屋固有IDをコピー
-    部屋の住所表記をコピー
+    ルーム固有IDをコピー
+    ルームのアドレスをコピー
     セッションID
     文字の大きさ
     とても小さい
@@ -356,7 +356,7 @@
     写真撮影やビデオ通話には, ${app_name}アプリに端末のカメラの使用を許可する必要があります.
     通話を開始できませんでした。後ほどお試しください
     権限が無いため、一部の機能を利用できない可能性があります…
-    この部屋で会議を開始するためには招待権限が必要です
+    このルームで会議を開始するためには招待の権限が必要です
     とにかく送る
     ログアウト
     ホーム
@@ -373,28 +373,28 @@
     アカウントを作成
     送信
     飛ばす
-    ユーザ名または電子メール
+    ユーザー名または電子メール
     パスワード
     新しいパスワード
-    ユーザ名
+    ユーザー名
     電子メールアドレス
     電子メールアドレス (任意で)
     パスワード再確認
     新しいパスワードを再確認
     パスワードが違います
-    電子メールアドレスが違います
-    電話番号が違います
-    電子メールアドレスまたは電話番号が違います
-    接続先サーバを指定する(追加設定)
-    指定が正しくありません
+    メールアドレスがありません
+    電話番号が入力されていません
+    電子メールアドレスまたは電話番号が入力されていません
+    接続先サーバーを指定する(追加設定)
+    トークンが正しくありません
     メールと電話番号の同時登録はまだシステムが対応できませんが、電話番号だけの登録は可能です。
 \n
 \n設定からプロフィールにメールアドレスを追加できます。
-    このホームサーバはあなたがロボットではない認証を求めます
-    ユーザ名はすでに使用されています
-    ホームサーバ:
-    認証サーバー:
-    自分用電子メールアドレスで認証をします
+    このホームサーバーはあなたがロボットではない認証を求めます
+    ユーザー名はすでに使用されています
+    ホームサーバー:
+    IDサーバー:
+    メールアドレスを確認しました
     パスワードを初期化するには, アカウントに登録されている電子メールアドレスを入力してください:
     あなたのアカウントに登録された電子メールアドレスの入力が必要です.
     新しいパスワードの入力が必要です.
@@ -402,15 +402,15 @@
     電子メールアドレスの確認に失敗しました: 電子メールのリンクをクリックしたことを確認してください
     パスワードがリセットされました。
 \n
-\nすべてのセッションからログアウトしたため、プッシュ通知を受け取れなくなりました。通知を再び有効にするには、各デバイスで再ログインをお願いします。
+\n全てのセッションからログアウトしたため、プッシュ通知を受け取れなくなりました。通知を再び有効にするには、各デバイスで再ログインをお願いします。
     登録ができません : 電子メールがあなた個人のものであるか確認できません
     指定されたアクセストークンが認識されませんでした
     不正な形式のJSON
     有効なJSONを含んでいませんでした
-    ログイン要求が多すぎてサーバが対応できません
+    ログイン要求が多すぎてサーバーが対応できません
     まだクリックされていないeメールのリンク
     以下の容量で画像を送信
-    部屋の説明
+    ルームの説明
     通話が接続されました
     通話を接続中…
     通話着信中
@@ -423,7 +423,7 @@
     写真または動画の撮影
     動画を記録できません
     保存
-    試写
+    プレビュー
     拒否
     管理者権限操作
     通話
@@ -433,11 +433,11 @@
     この参加者の発言を全て非表示にする
     この参加者の発言を全て表示する
     指名して呼掛け
-    ユーザID, 表示名, 電子メールアドレス
+    ユーザーID, 表示名, 電子メールアドレス
     接続端末一覧を表示
-    %sさんをこの部屋へ招待して本当によろしいですか?
-    ユーザIDで招待
-    電子メールまたはMatrixユーザID
+    %sさんをこのルームに招待してよろしいですか?
+    ユーザーIDで招待
+    電子メールまたはMatrixユーザーID
     全て中止
     ログアウト
     無視
@@ -447,8 +447,8 @@
     結果なし
     参加者
     ファイル
-    部屋
-    部屋一覧を見る
+    ルーム
+    ルーム一覧を見る
     退室
     会話
     設定
@@ -461,8 +461,8 @@
     著作権
     個人情報保護方針
     パスワード:
-    ホームサーバ
-    認証サーバー
+    ホームサーバー
+    IDサーバー
     メールアドレスが見つかりません。
     この電話番号は既に使用されています。
     パスワード変更
@@ -470,28 +470,28 @@
     新しいパスワード
     新しいパスワードの確認
     パスワードの更新に失敗しました
-    %sのすべてのメッセージを表示しますか?
+    %sの全てのメッセージを表示しますか?
 \n
 \nこの操作はアプリを再起動するため時間がかかる場合があります。
     電話番号の認証時にエラーが発生しました
-    この部屋へのリンクを作成するには、部屋の住所表記が必要です。
-    この部屋は暗号化されています。
-    この部屋は暗号化されていません。
+    このルームへのリンクを作成するには、アドレスが必要です。
+    このルームは暗号化されています。
+    このルームは暗号化されていません。
     暗号化を有効にします
 \n(警告: 有効後にこれを無効にすることはできません!)
-    部屋一覧
+    ルーム一覧
     外観
     エンドツーエンド暗号化についての情報
     公開端末名
-    部屋のEnd-to-end暗号鍵を出力
+    ルームのEnd-to-end暗号鍵を出力
     認証
     履歴を検索
-    あなたはこの部屋に参加していません。
-    あなたはこの部屋で権限がありません。
-    部屋 %s は、見ることができません。
-    ユーザ名
-    ホームサーバ URL
-    認証サーバ ーURL
+    あなたはこのルームに参加していません。
+    あなたはこのルームで権限がありません。
+    ルーム %s は、見ることができません。
+    ユーザー名
+    ホームサーバー URL
+    IDサーバーのURL
     ログイン
     Matrixアプリを追加
     権限の数値は正の整数で入力してください。
@@ -512,49 +512,47 @@
     ${app_name}はビデオ通話を行うためにカメラとマイクにアクセスする許可を必要としています。
 \n
 \n次のポップアップでアクセスを許可して通話ができるようにしてください。
-    Matrixユーザが電子メールアドレスや電話番号を元に他のユーザを検索するためには、${app_name}アプリがあなたの端末内電話帳へアクセスする許可が必要です。
-
-${app_name}からあなた個人の電話帳への検索要求を許可する場合は、次のポップアップでアクセスを許可してください。
-    ${app_name}はあなたの連絡先のメールアドレスや電話番号をもとに他のユーザーを見つけることができます。
+    ${app_name}アプリでは、あなたの端末の電話帳のメールアドレスや電話番号をもとに、他のユーザーを検索することができます。${app_name}による電話帳の検索を許可する場合は、次のポップアップでアクセスを許可してください。
+    ${app_name}はあなたの電話帳のメールアドレスや電話番号をもとに他のユーザーを見つけることができます。
 \n
-\nこのアプリがあなたの連絡先へアクセスすることを許可しますか?
+\nこのアプリがあなたの電話帳へアクセスすることを許可しますか?
     申し訳ありません。権限がないために操作が実行されませんでした
     発言を通報する
     既読
     写真を撮影
     動画を撮影
-    要求を無視する
+    要求を無視
     検証せずに共有する
-    検証を開始する
+    検証を開始
     リクエストに user_id がありません。
     リクエストに room_id がありません。
     リクエストの送信に失敗しました。
     ウィジェットを作成できません。
-    この部屋のウィジェットを管理する権限が必要です
+    このルームのウィジェットを管理する権限が必要です
     ウィジェットの作成に失敗しました
-    ウィジェットをこの部屋から削除してもよろしいですか?
+    ウィジェットをこのルームから削除してもよろしいですか?
     サーバーが利用できないか、オーバーロードしている可能性があります
     このルームは検証されていない不明なセッションが含まれています。
 \nこれは、そのセッションが主張するユーザーのものであるという保証がないことを意味します。
 \n続行する前に、各セッションの検証プロセスを進めることをおすすめしますが、検証せずにメッセージを再送信することもできます。
 \n
 \n不明なセッション:
-    部屋に不明なデバイスが含まれています
+    ルームに不明なデバイスが含まれています
     キーが一致していることを確認
     一致する場合は、下の確認ボタンを押します。そうでない場合は、他の誰かがこのデバイスを盗聴しているので、代わりにブロックボタンを押すことをおすすめします。将来この検証プロセスはより洗練されたものになるでしょう。
     デバイスの検証
     不明なデバイス
-    このデバイスから未認証のデバイスに暗号化されたメッセージを送信しない。
+    このセッションでは、未検証のセッションに対して暗号化されたメッセージを送信しない
     認証済みデバイスに対してのみ暗号化
     インポート
     ローカルファイルからキーをインポート
     ルームキーをインポート
-    部屋のエンドツーエンド暗号鍵をインポート
+    ルームのエンドツーエンド暗号鍵をインポート
     パスフレーズを確認
     パスフレーズを入力
     エクスポート
     暗号鍵をローカルファイルにエクスポートする
-    部屋の暗号鍵をエクスポート
+    ルームの暗号鍵をエクスポート
     検証
     通話
     通知あり(音量大)
@@ -565,27 +563,27 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場
     この招待はこのアカウントに関連付けられていない %s に送信されました。
 \n別のアカウントでログインするか、このメールを自分のアカウントに追加してください。
     あなたは%sにアクセスしようとしています。話し合いに参加しますか?
-    部屋
-    これは部屋のプレビューです。部屋でのやりとりはできません。
+    ルーム
+    これはルームのプレビューです。ルームでのやり取りは無効化されています。
     このユーザーにあなたと同じ権限を与えますが、この変更は取り消せません。
-\n本当によろしいですか?
+\nよろしいですか?
     端末の連絡先 (%d)
-    ユーザディレクトリ (%s)
-    Matrixユーザのみ
-    ユーザIDで招待する
+    ユーザーディレクトリ (%s)
+    Matrixユーザーのみ
+    ユーザーIDで招待する
     1つまたは複数のメールアドレスか、Matrix IDを入力してください
     信用する
     信用しない
     フィンガープリント (%s):
-    リモートサーバのアイデンティティを確認できませんでした。
-    誰かが不当にあなたの通信を傍受しているか、リモートサーバの証明書をあなたの電話が信用していない可能性があります。
-    サーバの管理者が、これは想定されていることであると言っているのであれば、以下のフィンガープリントが管理者によるフィンガープリントと一致していることを確認してください。
+    リモートサーバーのIDを確認できませんでした。
+    誰かが不当にあなたの通信を傍受しているか、リモートサーバーの証明書をあなたの電話が信用していない可能性があります。
+    サーバーの管理者が、これは想定されていることであると言っているのであれば、以下のフィンガープリントが管理者によるフィンガープリントと一致していることを確認してください。
     証明書はあなたの電話に信頼されていたものから変更されています。これはきわめて異常な事態です。この新しい証明書は承認しないことを強く推奨します。
-    証明書は以前信頼されていたものから信頼されていないものへと変更されています。サーバがその証明書を更新した可能性があります。予測されるフィンガープリントを取得するために、サーバの管理者に連絡してください。
-    サーバの管理者が上のフィンガープリントと一致するものを発行した場合に限り、証明書を承認してください。
+    証明書は以前信頼されていたものから信頼されていないものへと変更されています。サーバーがその証明書を更新した可能性があります。予測されるフィンガープリントを取得するために、サーバーの管理者に連絡してください。
+    サーバーの管理者が上のフィンガープリントと一致するものを発行した場合に限り、証明書を承認してください。
     不正な形式のIDです。メールアドレスまたは\'@localpart:domain\'のようなMatrix IDを入力してください
     このコンテンツを報告する理由
-    このユーザによるすべてのメッセージを非表示にしますか?
+    このユーザーによる全てのメッセージを非表示にしますか?
 \n
 \nこの操作はアプリを再起動するため時間がかかる場合があります。
     アップロードをキャンセルする
@@ -597,22 +595,22 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場
     このアプリのシステムの情報を見る。
     アプリの情報
     自分の表示名を含むメッセージ
-    自分のユーザネームを含むメッセージ
+    自分のユーザー名を含むメッセージ
     バージョン
     olmのバージョン
     サードパーティの通知
     ホーム画面
-    逃した通知がある部屋を固定する
-    未読のメッセージがある部屋を固定する
+    逃した通知があるルームをピン止めする
+    未読のあるルームをピン止めする
     分析
     データ節約モード
     メールアドレスを認証できません。メールを確認して、そこに記載してあるリンクをクリックしてください。その後、「続ける」をクリックしてください。
     この通知の対象を削除してよろしいですか?
     %1$s %2$sを削除してよろしいですか?
     コード
-    %s はこの部屋のタイムラインのある箇所を読み込もうとしましたが、見つかりませんでした。
+    %s はこのルームのタイムラインのある箇所を読み込もうとしましたが、見つかりませんでした。
     イベント情報
-    ユーザID
+    ユーザーID
     Curve25519 固有鍵
     アルゴリズム
     セッションID
@@ -629,11 +627,11 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場
     ブラックリスト
     ブラックリストから除外する
     このデバイスが信頼できることを確認するために、その所有者に何らかの他の方法(直接会って、または、電話で)連絡し、以下のキーが、そのデバイスの「設定」で確認できるキーと一致するかお尋ねください:
-    部屋のディレクトリを選択
-    公開の部屋を表示するホームサーバを入力してください
+    ルームのディレクトリを選択
+    公開のルームを表示するホームサーバーを入力してください
     サーバー名
-    %sサーバ上のすべての部屋
-    すべてのローカルの %s 部屋
+    %sサーバー上の全てのルーム
+    全てのローカルの %s ルーム
     メッセージが未送信です。今 %1$s または %2$s しますか?
     不明なデバイスが存在しているため、メッセージを送ることができませんでした。今 %1$s または %2$s しますか?
     要求されたフィンガープリントキー Ed25519
@@ -647,50 +645,50 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場
     音量大
     暗号化されたメッセージ
     退出
-    コミュニティの詳細
+    コミュニティーの詳細
     読み込み中…
-    コミュニティ
-    コミュニティで絞り込み
+    コミュニティー
+    コミュニティー名で絞り込む
     招待
-    コミュニティ
+    コミュニティー
     グループがありません
-    %sと新しい会話を開始して本当によろしいですか?
-    音声通話を開始して本当によろしいですか?
-    本当にビデオ通話を開始しますか?
+    %sと新しい会話を開始してよろしいですか?
+    音声通話を開始してよろしいですか?
+    ビデオ通話を開始してよろしいですか?
     グループリスト
-    ユーザーを禁止するとこの部屋から追い出され、二度と参加できなくなります。
-    すべてのメッセージ (大音量)
-    すべてのメッセージ
+    ユーザーをブロックすると、ユーザーはこのルームから削除され、二度と参加できなくなります。
+    全てのメッセージ (大音量)
+    全てのメッセージ
     ミュート
     ホーム画面にショートカットを作成
     インラインURLプレビュー
     通知
-    この部屋はコミュニティの特色を表示していません
-    所属するコミュニティ
-    新しいコミュニティID (記入例 +foo:matrix.org)
-    無効なコミュニティID
-    \'%s\' は有効なコミュニティIDではありません
+    このルームはコミュニティーの特色を表示していません
+    所属するコミュニティー
+    新しいコミュニティーID (記入例 +foo:matrix.org)
+    無効なコミュニティーID
+    \'%s\' は有効なコミュニティーIDではありません
     ルームのエンドツーエンド暗号鍵は \'%s\' に保存されました。
 \n 
 \n警告: このファイルは、アプリケーションをアンインストールすると削除されることがあります。
     暗号鍵を要求している新しい端末 \'%s\' を追加しました。
     未認証の端末 \'%s\' が暗号鍵を要求しています。
     作成
-    コミュニティを作成
-    コミュニティの名前
+    コミュニティーを作成
+    コミュニティーの名前
     
-    コミュニティID
+    コミュニティーID
     
     ホーム
     参加者
-    部屋
-    ユーザがいません
-    部屋
+    ルーム
+    ユーザーがいません
+    ルーム
     参加済
     招待済
     グループのメンバーをフィルタリングする
-    グループの部屋を絞り込み
-    管理者はこのコミュニティの詳細を規定していません。
+    グループのルームを絞り込み
+    管理者はこのコミュニティーの詳細を規定していません。
     あなたは%2$sによって%1$sから除外されました
     あなたは%2$sによって%1$sへの参加を禁止されました
     理由: %1$s
@@ -712,12 +710,12 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場
         %d件の新しいメッセージ
     
     
-        %d部屋
+        %dルーム
     
     
-        %2$s に %1$s 部屋見つかりました
+        %2$s に %1$s ルーム見つかりました
     
-    部屋の履歴を消す
+    ルームの履歴を消す
     アバター
     名前があがったときのみ
     通知のプライバシー
@@ -768,10 +766,10 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場
         %d個選択済
     
     低プライバシー
-    • 通知中のメッセージの内容は Matrixのホームサーバから直接安全に入手しています
+    • 通知中のメッセージの内容は Matrixのホームサーバーから直接安全に入手しています
     ・通知は メタデータとメッセージのデータ を含みます
     • 通知は メッセージの内容を表示しません
-    ユーザの名前をあげるときバイブレーションで通知
+    ユーザーの名前をあげるときバイブレーションで通知
     送信の前にメディアをプレビュー
     アカウントを停止
     自分のアカウントを停止
@@ -792,7 +790,7 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場
         %d件の通知された未読メッセージ
     
     
-        %d部屋
+        %dルーム
     
     %2$s件 中 %1$s件
     
@@ -803,15 +801,15 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場
     メッセージを送信するには、キーボードのエンターキーを使用してください
     ボイスメッセージを送信
     動作を表示
-    指定したIDのユーザをブロック
-    指定したIDのユーザのブロックを解除
-    ユーザの権限レベルを決める
-    指定したIDのユーザの管理者権限を取り消す
-    指定したユーザを現在の部屋に招待
-    指定したエイリアスの部屋に参加
-    部屋を退室
-    部屋のテーマを設定
-    指定したIDのユーザとの接続を切断
+    指定したIDのユーザーをブロック
+    指定したIDのユーザーのブロックを解除
+    ユーザーの権限レベルを決める
+    指定したIDのユーザーの管理者権限を取り消す
+    指定したユーザーを現在のルームに招待
+    指定されたアドレスのルームに参加します
+    ルームを退室
+    ルームのテーマを設定
+    指定したIDのユーザーとの接続を切断
     表示するニックネームを変更
     Markdown書式の入/切
     Matrixアプリの管理を修正するには
@@ -819,39 +817,39 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場
         %d名の参加者
     
     
-        %d部屋
+        %dルーム
     
     アバターを読み込み
-    %1$s ホームサーバを使用し続けるには、利用規約を読み、同意する必要があります。
+    %1$s ホームサーバーを使用し続けるには、利用規約を読み、同意する必要があります。
     エラー
     アバターに通知を表示
     今すぐ見る
     アカウントを停止
-    この操作によりあなたのアカウントは永久に使えなくなります。あなたはログインできなくなり、誰も同じユーザIDを再登録できなくなります。アカウントが参加しているすべてのルームを退出し、IDサーバからアカウントの詳細を削除することになります。 この操作は取り消しできません 
+    この操作により、あなたのアカウントは永久に使えなくなります。あなたはログインできなくなり、誰も同じユーザーIDを再登録できなくなります。アカウントが参加している全てのルームを退出し、IDサーバーからアカウントの詳細は削除されます。 この操作は取り消せません。
 \n
-\nあなたのアカウントを停止することによって デフォルトではあなたが送信したメッセージの削除はされません。メッセージを削除を望む場合は、以下のボックスにチェックを入れてください。 
+\nアカウントを停止しても、 デフォルトではあなたが送信したメッセージは忘却されません。メッセージの忘却を望む場合は、以下のボックスにチェックを入れてください。
 \n
-\nMatrixでのメッセージの可視性は電子メールと同様のものです。メッセージを削除すると、あなたが送信したメッセージが新規または未登録のユーザーと共有されないことを意味しますが、すでにこれらのメッセージにアクセスをしている登録ユーザーはそのコピーにアクセスできます。
-    アカウントを停止したとき自分の送信した全てのメッセージを削除 (警告: この操作により、将来的なユーザが会話を不完全な形で見ることになります)
+\nMatrixのメッセージの可視性は、電子メールと同様のものです。メッセージを忘却すると、あなたが送信したメッセージは、新規または未登録のユーザーには共有されることはありませんが、すでにメッセージを取得している登録ユーザーは、今後もそのコピーにアクセスできます。
+    アカウントを停止したとき自分の送信した全てのメッセージを削除 (警告: この操作により、将来的なユーザーが会話を不完全な形で見ることになります)
     続けるには、パスワードを入力してください:
     アカウントを停止
     パスワードを入力してください。
     このルームは置き換えられており、アクティブではありません。
     ここで会話が続いています
-    この部屋は別の会話の続きです
+    このルームは別の会話の続きです
     より古いメッセージを見るには、ここをクリックしてください
     リソース制限の超過
     管理者に連絡
     あなたのサービス管理者に連絡
-    このホームサーバはリソース制限の1つを超過しているため、 ユーザがログインできなくなることがあります
-    このホームサーバはリソース制限の1つを超過しています。
-     このホームサーバは月間アクティブユーザ上限に達しているため、 ユーザがログインできなくなることがあります
-    このホームサーバは月間アクティブユーザ上限に達しています。
+    このホームサーバーはリソース制限の1つを超過しているため、 ユーザーがログインできなくなることがあります
+    このホームサーバーはリソース制限の1つを超過しています。
+     このホームサーバーは月間アクティブユーザー上限に達しているため、 ユーザーがログインできなくなることがあります
+    このホームサーバーは月間アクティブユーザー上限に達しています。
     この上限を上げるには %s してください。
     このサービスを使い続けるには %s してください。
-    最初に部屋のメンバーのみを読み込むことによりパフォーマンスを向上。
-    あなたのホームサーバは部屋のメンバーの簡易読み込みをサポートしていません。後で試してください。
-    部屋のメンバーの簡易読み込み
+    最初にルームのメンバーのみを読み込むことによりパフォーマンスを向上。
+    あなたのホームサーバーはルームのメンバーの簡易読み込みをサポートしていません。後で試してください。
+    ルームのメンバーの簡易読み込み
     申し訳ありません、エラーが発生しました
     Version %s
     エクスポートされた鍵を暗号化するパスフレーズを作成してください。 キーをインポートするには、同じパスフレーズを入力する必要があります。
@@ -945,19 +943,19 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場
     削除済みのメッセージを表示
     削除されたメッセージの代わりに削除されたという通知を表示します。
     ユーザーによって削除されたイベント
-    新しい部屋を作成
+    新しいルームを作成
     変更
     ネットワークを変更
-    全てのコミュニティ
-    部屋
+    全てのコミュニティー
+    ルーム
     ダイレクトメッセージ
-    新しい部屋
+    新しいルーム
     作成
     名前
-    公開する
-    部屋が公開され、誰でもこの部屋に参加できるようになります
-    部屋一覧
-    部屋一覧にこの部屋が公開されます
+    公開
+    誰でもこのルームに参加できるようになります
+    ルーム一覧
+    ルームの一覧へ公開する
     一般
     セキュリティとプライバシー
     ヘルプと概要
@@ -973,7 +971,7 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場
     設定
     お気に入りに追加
     お気に入りから削除
-    部屋から退出
+    ルームから退出
     長押しすると、追加のオプションが表示されます
     開発者モード
     設定
@@ -985,14 +983,14 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場
     詳細
     その他の設定
     管理者としての操作
-    部屋の設定
+    ルームの設定
     通知
     
         %1$d 人の参加者
     
     アップロード
-    部屋を退室
-    部屋から退室中…
+    ルームを退出
+    ルームから退室中…
     管理者
     モデレーター
     カスタム
@@ -1030,9 +1028,9 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場
     アカウントデータ
     削除…
     削除の確認
-    このイベントを削除してよろしいですか?部屋名やトピックの変更を削除すると、変更が元に戻る点にご注意ください。
+    このイベントを削除してよろしいですか?ルーム名やトピックの変更を削除すると、変更が元に戻る点にご注意ください。
     暗号化は有効です
-    この部屋内でのメッセージはエンドツーエンド暗号化されます。詳細の確認や検証はユーザーのプロフィールをご確認ください。
+    このルーム内でのメッセージはエンドツーエンド暗号化されます。詳細の確認や検証はユーザーのプロフィールをご確認ください。
     暗号化が有効化されていません
     通知設定
     切断
@@ -1112,7 +1110,7 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場
     セキュアバックアップを設定
     管理
     セキュアバックアップ
-    部屋を作成中…
+    ルームを作成中…
     招待されています
     %s からの招待
     このセッションは正常に検証されました。
@@ -1146,7 +1144,7 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場
     リアルタイム接続を確立できませんでした。
 \nホームサーバーの管理者に、通話が正常に動作するためにTURNを設定するようご連絡ください。
     再び表示しない
-    アイデンティティサーバーが設定されていません。
+    IDサーバーが設定されていません。
     終了
     拒否
     承諾
@@ -1162,12 +1160,12 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場
     なし
     トピック
     ルーム名
-    この部屋内のメッセージはエンドツーエンド暗号化されていません。
+    このルーム内のメッセージはエンドツーエンド暗号化されていません。
     ここでのメッセージはエンドツーエンド暗号化されていません。
     設定
-    あなたにはこの部屋の暗号化を有効にする権限がありません。
+    あなたにはこのルームの暗号化を有効にする権限がありません。
     未読メッセージ
-    タイムラインでスワイプして返信を有効にする
+    タイムラインでのスワイプによる返信を有効にする
     タイムラインで非表示のイベントを表示する
     QR コードをスキャン
     QR コード画像
@@ -1206,54 +1204,54 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場
     %sを削除しますか?
     認証が必要です
     あなたのパスワードを確認する
-    暗号化された部屋での検索はまだサポートされていません。
-    禁止されたユーザーをフィルタリングする
+    暗号化されたルームでの検索はまだサポートされていません。
+    ブロックされたユーザーを絞り込む
     トピックを変更する
     ルームをアップグレード
-    m.room.server_acl eventsを送信します
-    権限を変更する
-    部屋名を変更する
-    履歴を見えるように変更する
-    部屋の暗号化を有効にする
-    部屋のメインアドレスを変更する
-    部屋のアバターを変更する
-    ウィジェットを変更する
-    全員に通知する
-    他の人から送信されたメッセージを削除する
-    禁止ユーザー
-    キックユーザー
+    m.room.server_acl eventsを送信
+    権限を変更
+    ルーム名の変更
+    履歴の見え方の変更
+    ルームの暗号化の有効化
+    ルームのメインアドレスの変更
+    ルームのアバターの変更
+    ウィジェットの変更
+    全員への通知
+    他の人から送信されたメッセージの削除
+    ユーザーのブロック
+    ユーザーのキック
     設定を変更する
     招待されたユーザー
     メッセージを送る
     デフォルトルール
-    部屋のさまざまな部分を変更するための必要な役割を更新する権限がありません
-    部屋のさまざまな部分を変更するために必要な役割を選択します
-    部屋の権限
+    ルームに関する変更を行うために必要な役割を更新する権限がありません
+    ルームに関する変更を行うために必要な役割を選択
+    ルームの権限
     権限
-    部屋のさまざまな部分を変更するために必要な役割を表示し更新します。
-    ユーザーの禁止を解除すると、ユーザーは再び部屋に参加できるようになります。
+    ルームに関する変更を行うために必要な役割を表示し更新します。
+    ブロックを解除すると、ユーザーは再びルームに参加できるようになります。
     禁止されたユーザー
     禁止の理由
     ユーザーの禁止を解除する
-    キックするユーザーは、この部屋から削除されます。
+    キックするユーザーは、このルームから削除されます。
 \n
-\n再び参加するのを防ぐためには永久追放する必要があります。
+\n再参加を防ぐためには、キックの代わりにブロックする必要があります。
     キックする理由
     ユーザーをキックする
     このユーザーの招待をキャンセルしてよろしいですか?
     招待をキャンセル
-    このユーザーを解除すると、そのユーザーからのすべてのメッセージが再び表示されます。
+    このユーザーを解除すると、そのユーザーからの全てのメッセージが再び表示されます。
     ユーザーを無視しない
-    このユーザーを無視すると、あなたが共有している部屋からそのユーザーのメッセージが削除されます。
+    このユーザーを無視すると、あなたが共有しているルームからそのユーザーのメッセージが削除されます。
 \n
-\nこの動作は設定からいつでも元に戻すことができます。
+\nこの動作は、設定からいつでも元に戻すことができます。
     ユーザーを無視する
     広角
-    あなたは自分自身を降格させているので、この変更を元に戻すことはできません。あなたが部屋の中で最後の特権ユーザーである場合、特権を取り戻すことはできません。
+    あなたは自分自身を降格させようとしているため、今後、この変更を元に戻すことはできなくなります。あなたがルームの中で最後の特権ユーザーである場合、特権を再取得することはできません。
     降格しますか?
     招待をキャンセル
-    この部屋は公開されていません。 招待なしで再参加することはできません。
-    このアクションを実行するには、設定にIDサーバーを追加します。
+    このルームは公開されていません。 招待がなければ再び参加することはできません。
+    このアクションを実行するには、設定にIDサーバーを追加してください。
     連絡先へのアクセスを許可します。
     QRコードをスキャンするには、カメラへのアクセスを許可する必要があります。
     通話をかけました
@@ -1262,7 +1260,7 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場
     コールし直す
     通話をやり直す
     アクティブな通話(%s)
-    あなたのホームサーバがアシスト機能を提供しない場合、代わりに%sを使用します(IPアドレスは通話中に共有されます)
+    あなたのホームサーバーがアシスト機能を提供しない場合、代わりに%sを使用します(IPアドレスは通話中に共有されます)
     ビデオ通話が行われています…
     フォールバックコールアシストサーバーを許可する
     有効な認証情報ではありません
@@ -1270,8 +1268,8 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場
     通話が確実に機能させるためには、ホームサーバー(%1$s)の管理者にTURNサーバーの設定を依頼してください。
 \n
 \n代わりに、%2$sのパブリックサーバーを使用することもできますが、信頼性は低く、あなたのIPアドレスがそのサーバーと共有されてしまいます。これは設定から管理することができます。
-    ルームディレクトリのすべての部屋を詳しい説明を含めて表示します。
-    部屋の詳しい説明を表示する
+    ルームのディレクトリ内の全てのルームを表示(露骨なコンテンツのあるルームを含む)する。
+    露骨なコンテンツのあるルームを表示
     ルームディレクトリ
     新着情報
     戻る
@@ -1283,74 +1281,74 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場
     会話を始める
     %1$sがエンドツーエンド暗号化をオンにしました。
     エンドツーエンド暗号化をオンにしました。
-    ゲストが部屋に入るのを防いでいます。
-    %1$sはゲストが部屋に参加するのを妨げました。
-    ゲストが部屋に参加するのを妨ぎました。
-    %1$sはゲストが部屋に参加するのを妨ぎました。
+    ゲストがルームに入るのを拒否しています。
+    %1$sはゲストがルームに参加するのを拒否しています。
+    ゲストがルームに入るのを拒否しています。
+    %1$sはゲストがルームに参加するのを拒否しました。
     ここへのゲストの入室を許可しました。
     %1$sはここにゲストが参加することを許可しました。
-    この部屋へのゲストの入室を許可しました。
-    %1$s がゲストの部屋への参加を許可しました。
+    ゲストにルームへの参加を許可しました。
+    %1$s がゲストにルームへの参加を許可しました。
     システムデフォルト
-    この部屋のメインおよび代替のアドレスを変更しました。
-    この部屋の代替アドレスを変更しました。
+    このルームのメインおよび代替のアドレスを変更しました。
+    このルームの代替アドレスを変更しました。
     
-        この部屋の代替アドレス%1$sを削除しました。
+        このルームの代替アドレス%1$sを削除しました。
     
     
-        この部屋の代替アドレス%1$sを追加しました。
+        このルームの代替アドレス%1$sを追加しました。
     
-    この部屋のメインアドレスを削除しました。
-    この部屋のメイン・アドレスを%1$sに設定しました。
-    この部屋のアドレスとして、%1$sを追加し%2$sを削除しました。
+    このルームのメインアドレスを削除しました。
+    このルームのメインアドレスを%1$sに設定しました。
+    このルームのアドレスとして、%1$sを追加し%2$sを削除しました。
     
-        この部屋のアドレスの%1$sを削除しました。
+        このルームのアドレスの%1$sを削除しました。
     
     %1$sの招待を取り消しました。理由:%2$s
     %1$sの招待を受諾しました%2$。理由:%2$s
-    %1$sの部屋への招待を取り消しました。理由:%2$s
+    %1$sのルームへの招待を取り消しました。理由:%2$s
     %1$sにルームへの招待状を送りました。理由:%2$s
     VoIPカンファレンスをリクエストしました
-    このルームのサーバのACLを変更しました。
-    このルームのサーバACLを設定しました。
+    このルームのサーバーのアクセス制御リストを変更しました。
+    このルームのサーバーアクセス制御リストを設定しました。
     ここをアップグレードしました。
     あなたはこのルームをアップグレードしました。
     通話を終えました。
-    通話に応えました。
+    通話に応答しました。
     通話を設定するためのデータを送信しました。
-    この部屋のアドレスを変更しました。
-    %1$s はこの部屋のメインおよび代替アドレスを変更しました。
-    %1$s がこの部屋のアドレスを変更しました。
-    %1$sはこの部屋の代替アドレスを変更しました。
+    このルームのアドレスを変更しました。
+    %1$s はこのルームのメインおよび代替アドレスを変更しました。
+    %1$s がこのルームのアドレスを変更しました。
+    %1$sはこのルームの代替アドレスを変更しました。
     
-        %1$s がこの部屋の代替アドレス%2$sを削除しました。
+        %1$s がこのルームの代替アドレス%2$sを削除しました。
     
     
-        %1$s はこの部屋の代替アドレス%2$sを追加しました。
+        %1$s はこのルームの代替アドレス%2$sを追加しました。
     
-    %1$sがこの部屋のメインアドレスを削除しました。
-    %1$sがこの部屋のメインアドレスを%2$sに設定しました。
-    %1$sはこの部屋のアドレスとして %2$sを追加し%3$sを削除しました。
+    %1$sがこのルームのメインアドレスを削除しました。
+    %1$sがこのルームのメインアドレスを%2$sに設定しました。
+    %1$sはこのルームのアドレスとして %2$sを追加し%3$sを削除しました。
     
-        %1$s はこの部屋のアドレスの%2$sを削除しました。
+        %1$s はこのルームのアドレスの%2$sを削除しました。
     
     
-        この部屋のアドレスとして%1$sが追加されました。
+        このルームのアドレスとして%1$sが追加されました。
     
     %1$sが%2$s にルームへの招待を送りました。理由:%3$s
     %1$sが%2$sのルームへの招待を取り消しました。理由:%3$s
     %1$sが %2$sの招待を承諾しました。理由:%3$s
     %1$sは%2$sの招待を取り下げました。理由:%3$s
     
-        %1$sはこの部屋のアドレスとして%2$sを追加しました。
+        %1$sはこのルームのアドレスとして%2$sを追加しました。
     
     あなたのプロフィールが更新されました %1$s
-    %sがこのルームのサーバのACLを変更しました。
+    %sがこのルームのサーバーのアクセス制御リストを変更しました。
     IPリテラルに一致するサーバーは禁止されています。
     ・IPリテラルに一致するサーバーを許可します。
     ・%sに一致するサーバーは許可されています。
     ・%sに一致するサーバーは禁止されています。
-    %sがこのルームのサーバACLを設定しました。
+    %sがこのルームのサーバーアクセス制御リストを設定しました。
     %sがここをアップグレードしました。
     %s がこのルームをアップグレードしました。
     エンドツーエンド暗号化をオンにしました (%1$s)
@@ -1394,7 +1392,7 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場
     初期同期:
 \nアカウントデータをインポートしています
     初期同期:
-\nコミュニティをインポートしています
+\nコミュニティーをインポートしています
     初期同期:
 \nルームをインポートしています
     初期同期:
@@ -1466,7 +1464,7 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場
     入室と退室イベントの表示
     Use /confettiコマンドを使用するか、❄️または🎉を含むメッセージを送信します
     チャットでエフェクトを表示する
-    部屋のメンバーのイベントを表示する
+    ルームのメンバーのイベントを表示する
     ホームサーバーがこの機能をサポートしている場合は、チャット内のリンクをプレビューします。
     ボット、ブリッジ、ウィジェット、ステッカーパックの管理をします。
 \nユーザーに代わり、構成データを受信しウィジェットを変更、ルーム招待の送信、権限の設定などができます。
@@ -1512,7 +1510,7 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場
 \n%1$s
     FCMトークンのホームサーバーへの登録が成功しました。
     トークンの登録
-    アカウントを追加する
+    アカウントを追加
     [%1$s]
 \nこのエラーは、${app_name}の管理外です。スマホにはGoogleアカウントがありません。アカウントマネージャーを開いて、Googleアカウントを追加してください。
     %1$s
@@ -1521,7 +1519,7 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場
 \n%1$s
     FCMトークンの取得に失敗しました:
 \n%1$s
-    🎉すべてのサーバの参加を禁止されています!このルームは使用できなくなりました。
+    🎉全てのサーバーの参加を禁止されています!このルームは使用できなくなりました。
     変化がありません。
     • サーバーにマッチするIPリテラルが禁止されています。
     • サーバーにマッチするIPリテラルが許可されるようになりました。
@@ -1551,7 +1549,7 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場
     続けるには利用規約に同意する必要があります。
     全てブロック
     許可
-    部屋ID
+    ルームID
     ウィジェットID
     あなたのテーマ
     あなたのユーザーID
@@ -1562,7 +1560,7 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場
     アクティブなウィジェット
     自分
     新しいメッセージ
-    部屋
+    ルーム
     新しいイベント
     不明なIP
     
@@ -1574,7 +1572,7 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場
     選択
     詳細情報: %s
     ローカルアドレスを追加する
-    この部屋はローカルアドレスがありません
+    このルームにはローカルアドレスがありません
     アドレス \"%1$s\" を削除しますか?
     メインアドレス
     これがメインアドレスです
@@ -1584,10 +1582,10 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場
     Eメールで招待
     詳細
     人を招待する
-    とにかく参加する
-    部屋を追加する
+    とにかく参加
+    ルームを追加
     %s はあなたを招待しています
-    この部屋で電話会議をする権利がありません
+    このルームでグループ通話をする権利がありません
     オーディオミーティングを開始する
     安全バックアップを設定
     暗号鍵バックアップで管理
@@ -1640,7 +1638,7 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場
     リカバリーキーを入力
     リカバリーキーを使用
     ログアウトしたりこの端末を失くしたりすればメッセージへのアクセスを失う可能性があります。
-    本当によろしいですか?
+    続行しますか?
     暗号鍵が現在バックグラウンドでホームサーバーへバックアップされています。初期バックアップは数分かかることがあります。
     バックアップが開始されました
     予期せぬエラー
@@ -1651,7 +1649,7 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場
     中止
     上書き
     違うセッションにより設定されたキーのバックアップが存在してます。上書きしますか?
-    ホームサーバにバックアップが存在しています
+    ホームサーバーにバックアップが存在しています
     リカバリーキーが保存されました。
     リカバリーキーが%sに保存されました。
 \n
@@ -1664,11 +1662,11 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場
     暗号鍵がバックアップ中です。
     (高度)リカバリーキーを使用して設定
     または、リカバリーキーでバックアップを保護し、安全な場所に保存してください。
-    暗号鍵のコピーを暗号化してホームサーバに保存します。バックアップを保護するためにパスフレーズを設定してください。
+    暗号鍵のコピーを暗号化してホームサーバーに保存します。バックアップを保護するためにパスフレーズを設定してください。
 \n
 \n最高度のセキュリティのために、アカウントのパスワードと異なることが大切です。
     パスフレーズを使用してバックアップを保護します。
-    暗号化が有効な部屋で送信されたメッセージはエンドツーエンド暗号化によって保護されます。メッセージを読むための暗号鍵を持っているのは送受信者のみです。
+    暗号化が有効なルームで送信されたメッセージはエンドツーエンド暗号化によって保護されます。メッセージを読むための暗号鍵を持っているのは送受信者のみです。
 \n
 \n暗号鍵を失わないように保護されたバックアップをしてください。
     (高度)
@@ -1705,7 +1703,7 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場
     ウィジェットを再読込
     クッキーを設定し%sとデータを交換する可能性があります:
     ウィジェットの追加者:
-    **送信に失敗 - 部屋を開いてください
+    **送信に失敗 - ルームを開いてください
     新しい招待
     %1$sと%2$s
     %1$s に %2$s と %3$s
@@ -1719,42 +1717,42 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場
         %d件の招待
     
     既にリストに載っているサーバーです
-    サーバーまたはその部屋一覧が見つかりません
+    サーバーまたはそのルーム一覧が見つかりません
     検索される新しいサーバーの名前を入力します。
     新しいサーバーを追加
-    あなたのサーバ
+    あなたのサーバー
     暗号化されたメッセージの復元
     セッションの公開名は会話中の相手に閲覧できます
-    部屋のバージョン
+    ルームのバージョン
     
         banされたユーザー%d人
     
-    この部屋が含まれているスペースの参加者誰でも発見し参加できます。部屋をスペースに追加できるのは部屋の管理者だけです。
+    このルームのあるスペースの参加者は誰でも発見し参加できます。ルームをスペースに追加できるのは、ルームの管理者だけです。
     スペースのメンバーのみ
-    誰でも部屋を発見し参加できます
+    誰でもルームを発見し参加できます
     公開
-    参加された人のみが部屋を発見し参加できます
+    招待された人だけが発見し参加できます
     プライベート
     不明のアクセス設定(%s)
     誰でもノックができ、メンバーがその参加を承認または拒否できます
-    現在の部屋一覧可視性状態を取得できません(%1$s)。
-    この部屋を%1$sの部屋一覧に公開しますか?
+    現在のルーム一覧の見え方を取得できません(%1$s)。
+    このルームを%1$sのルーム一覧に公開しますか?
     アドレスを非公開にする
     アドレスを公開
-    同じホームサーバー(%1$s)の他のユーザーがこの部屋を見つけられるようにアドレスを設定できます。
+    アドレスを設定すれば、他のユーザーがあなたのホームサーバー (%1$s) を通じてこのルームを見つけられるようになります。
     ローカルアドレス
     新しい公開アドレス(例: #alias:server)
     他の公開アドレスはまだありません。以下から追加できます。
     他の公開アドレスはまだありません。
-    部屋を%1$sの部屋一覧に公開しますか?
+    このルームを%1$sのルーム一覧に公開しますか?
     \"%1$s\"を非公開にしますか?
     公開
     手動で新しいアドレスを公開
     他の公開アドレス:
-    公開されたアドレスを通して、どのサーバーのどのユーザーでもこの部屋に参加できます。アドレスを公開するには、まずローカルアドレスとして設定する必要があります。
+    公開されたアドレスを通して、どのサーバーのどのユーザーでもこのルームに参加できます。アドレスを公開するには、まずローカルアドレスとして設定する必要があります。
     公開アドレス
-    部屋のアドレス
-    部屋のアドレス及び部屋一覧における可視性を管理できます。
+    ルームのアドレス
+    ルームのアドレス及びルーム一覧における可視性を管理できます。
     スペースのアドレスを管理できます。
     スペースのアドレス
     ルームのアドレス
@@ -1766,7 +1764,7 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場
     招待
     おすすめのルーム
     スペース
-    ホームサーバAPIのURL
+    ホームサーバーAPIのURL
     復旧用のメールアドレスを設定します。後からオプションでメールアドレスや電話番号を使用して知人に見つけてもらえるようにできます。
     電話番号を設定して、後からオプションで知人に見つけてもらえるようにできます。
     %s を使用してみてください
@@ -1843,25 +1841,24 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場
     プッシュルール
     エキスパート
     クイックリアクション
-    あなたはすでにこの部屋を見ています!
+    あなたはすでにこのルームを見ています!
     その他のサードパーティーの通知
     Matrix SDK バージョン
     ファイル\"%1$s\"からe2eキーをインポートします。
     キーのバックアップデータの取得中にエラーが発生しました
     信頼情報の取得中にエラーが発生しました
-    ルーム作成されましたが、一部の招待が送信されていません
-\n理由:
+    ルームが作成されましたが、一部の招待が送信されていません。理由:
 \n
 \n%s
-    ルーム設定
+    ルームの設定
     トピック
     ルームトピック(オプション)
     ルーム名
-    この部屋はプレビューできません。参加しますか?
-    現在、この部屋にはアクセスできません。
+    このルームはプレビューできません。参加しますか?
+    現在、このルームにはアクセスできません。
 \n後でもう一度やり直すか、ルームの管理者にアクセス権があるかどうかを確認するよう依頼してください。
     ${app_name}では、誰でも読めるルームのプレビューがまだサポートされていません
-    この部屋はプレビューできません
+    このルームはプレビューできません
     お待ち下さい…
     ネットワークがありません。インターネット接続を確認してください。
     不正な形式のイベント、表示できません
@@ -1908,8 +1905,8 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場
     リクエストはキャンセルされました
     キーの検証
     レガシー検証を使います。
-    何も表示されませんか?すべてのクライアントがインタラクティブ検証をまだサポートしているわけではありません。その場合はレガシー検証を使用してください。
-    取得
+    何も表示されませんか?全てのクライアントがインタラクティブ検証をまだサポートしているわけではありません。その場合はレガシー検証を使用してください。
+    了解
     このユーザーとのメッセージはエンドツーエンドで暗号化され第三者が読むことはできません。
     完了しました!
     相手が確認するのを待っています…
@@ -1925,7 +1922,7 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場
     短い文字列を比較して検証します。
     認証が無効または期限切れのため、ログアウトされました。
     構成を使用する
-    ${app_name}がuserIdドメイン\"%1$s \"のカスタムサーバ構成を検出しました。
+    ${app_name}がuserIdドメイン\"%1$s \"のカスタムサーバー構成を検出しました。
 \n%2$s
     サーバーオプションをおまかせする
     無効なホームサーバーディスカバリーレスポンス
@@ -1943,13 +1940,13 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場
     タップしスペースを編集する
     スペースを選択する
     アクセス可能なスペース
-    スペースメンバーが検索してアクセスすることを許可します。
-    スペースメンバー %sは検索、プレビュー、参加することができます。
+    スペースのメンバーに発見とアクセスを許可します。
+    スペース%sのメンバーが検索、プレビュー、参加できます。
     プライベート(招待のみ)
     デフォルトのメディアソース
     デフォルトの圧縮
-    ルームアップグレード
-    botによるメッセージ
+    ルームのアップグレード
+    ボットによるメッセージ
     ルーム招待
     \@roomを含むメッセージ
     ルームがアップグレードされたとき
@@ -1957,8 +1954,8 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場
     グループメッセージ
     暗号化されたダイレクトメッセージ
     ダイレクトメッセージ
-    私のユーザーネームを含むメッセージ
-    自分のディスプレーネームを含むメッセージ
+    自分のユーザー名
+    自分のディスプレーネーム
     グループチャットでのメッセージの暗号化
     個別チャットでのメッセージの暗号化
     通知してください
@@ -1975,7 +1972,7 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場
     キー共有リクエストの送信履歴
     結果がありません
     自分で電話をかけることはできません。参加者が招待を受け入れるのを待ちます
-    ミーティングはJitsiのセキュリティとパーミッションポリシーを使用します。会議中は、現在ルームにいるすべての人に招待状が表示されます。
+    ミーティングはJitsiのセキュリティとパーミッションポリシーを使用します。会議中は、現在ルームにいる全ての人に招待状が表示されます。
     権限がありません
     音声メッセージを送信するには、マイクの権限を許可してください。
     この動作を行うには、システム設定からカメラの権限を許可してください。
@@ -2006,10 +2003,10 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場
     IDサーバー
     ボット、ブリッジ、ウィジェット、ステッカーパックを使う
     他の人が見つけられるように
-    規約を見直す
+    規約を確認する
     利用規約
     編集履歴を表示
-    部屋に参加しています…
+    ルームに参加しています…
     提案
     連絡先
     最近の
@@ -2062,4 +2059,156 @@ ${app_name}からあなた個人の電話帳への検索要求を許可する場
     %s のURLにあるホームサーバーに接続できません。リンクをチェックするか、手動でホームサーバーを選択してください。
     後で
     スペース
+    スレッドから
+    参加している全スレッドを表示
+    現在のルームのスレッドを全て表示
+    ユーザーをブロックすると、ユーザーはこのスペースから削除され、二度と参加できなくなります。
+    PINコードを有効にする
+    これを招待者のみ参加可能に設定しました。
+    ルームの設定
+    コンテンツが報告されました
+    ヘルプとサポート
+    ヘルプ
+    ${app_name} ポリシー
+    ここ
+    キーワードを追加
+    自分のスレッド
+    全てのスレッド
+    ユーザーを自動的に招待
+    ユーザー
+    このユーザーの禁止を解除すると、そのユーザーはスペースに再び参加できるようになります。
+    このルームを招待者のみ参加可能に設定しました。
+    低優先度から削除
+    低優先度に追加
+    スパムとして報告済
+    このルームにファイルはありません
+    このルームにメディアはありません
+    公開ルームをアップグレード
+    プライベートスペース
+    公開スペース
+    送信済
+    送信中
+    種類
+    確認済
+    選択済
+    ビデオ
+    画像
+    スクリーンショット
+    接続
+    投票
+    投票
+    コード
+    役割
+    送信
+    招待
+    暗号化されていません
+    終了
+    再読み込み
+    セッション一覧
+    警告
+    無視を解除
+    あなた
+    動画。
+    絵文字で検証
+    待機中…
+    ステッカー
+    ファイル
+    音声
+    画像。
+    イライラシェイク
+    警告
+    警告
+    成功しました!
+    続ける
+    警告!
+    ロケーション
+    メディア
+    投票終了
+    投票を終了しますか?
+    投票を削除
+    スペースを作成
+    スペースに参加
+    スペースを退出
+    スペースにようこそ!
+    ルームを管理
+    このスペースにはルームがありません
+    音声メッセージを再生できません
+    音声メッセージを録音できません
+    グループ通話が開始しました
+    ルームのアップグレードには権限が必要です
+    アップグレード
+    アップグレードが必要です
+    プライベートルームをアップグレード
+    音声メッセージを一時停止
+    音声メッセージを有効にする
+    このメールアドレスをアカウントにリンク
+    投票を編集
+    投票を編集
+    地図
+    投票を終了
+    投票を終了
+    選択肢 %1$d
+    選択肢を作成
+    ロケーション
+    ロケーションを共有
+    ロケーションの共有を有効にする
+    地図を読み込めませんでした
+    スレッドで返信
+    ロケーションを共有
+    ロケーションを共有
+    カメラを開く
+    画像と動画を送信
+    ファイルをアップロード
+    連絡先を開く
+    ステッカーを送信
+    投票を作成
+    ロケーションを共有
+    スペースに関する変更を行うために必要な役割を更新する権限がありません
+    スペースに関する変更を行うために必要な役割を選択
+    スペースに関する変更を行うために必要な役割を表示し更新します。
+    フィルター
+    スレッド
+    スレッド
+    スペースをアップグレード
+    スペース名の変更
+    スペースの権限
+    応答がありません
+    応答がありません
+    スレッドへのリンクをコピー
+    有効にする
+    もっと知る
+    あなたのIDサーバーのポリシー
+    新しいルームを作成
+    認証コードが正しくありません。
+    IDサーバーのURLを入力してください
+    名前、ID、メールアドレスで検索
+    システムの設定
+    バージョン
+    現在のルームのアバターを変更
+    ルーム名を設定
+    アカウントの設定
+    キーワード
+    ルームから退出しました!
+    吹き出しでメッセージを表示
+    電子メールによる通知
+    セッションからサインアウトしました!
+    なし
+    メンションとキーワードのみ
+    ルームのスレッドを絞り込む
+    退出
+    モバイルでは、暗号化されたルームでのメンションとキーワードの通知は受信できません。
+    ルームの暗号化の有効化
+    スペースのメインアドレスの変更
+    スペースのアバターの変更
+    投票を作成
+    投票を作成
+    暗号化が正しく設定されていないためメッセージを送ることができません。クリックして設定を開いてください。
+    暗号化が正しく設定されていないためメッセージを送ることができません。管理者に連絡して、暗号化を正しい状態に復元してください。
+    %2$dの%1$d
+    あなたはすでにこのスレッドを見ています!
+    ルームに表示
+    ルームに表示
+    スレッドを表示
+    ポリシー
+    このルームへの参加は許可されていません
 
\ No newline at end of file

From b4596682380519af17cf2d6a51642706848fcf53 Mon Sep 17 00:00:00 2001
From: oksya8and8 
Date: Thu, 17 Feb 2022 10:31:43 +0000
Subject: [PATCH 405/581] Translated using Weblate (Japanese)

Currently translated at 72.2% (2012 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index fbad27f1dc7..1c14024c3db 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -1313,7 +1313,7 @@
     このルームのサーバーアクセス制御リストを設定しました。
     ここをアップグレードしました。
     あなたはこのルームをアップグレードしました。
-    通話を終えました。
+    通話を終了しました。
     通話に応答しました。
     通話を設定するためのデータを送信しました。
     このルームのアドレスを変更しました。
@@ -2211,4 +2211,8 @@
     スレッドを表示
     ポリシー
     このルームへの参加は許可されていません
+    "トピック: "
+    トラブルシューティング
+    添付ファイルを取得中にエラーが発生しました。
+    キーバックアップのバナーを閉じる
 
\ No newline at end of file

From 11dacbab81bb970af65c6bef78f4abd15fed1cdc Mon Sep 17 00:00:00 2001
From: Maxime Naturel 
Date: Thu, 17 Feb 2022 13:59:30 +0100
Subject: [PATCH 406/581] Revert "Adding header section only when necessary"

This reverts commit 96ed30ccc45ab47326a5058b73906be085540a98.
---
 .../member/AutocompleteMemberPresenter.kt            | 12 +++---------
 1 file changed, 3 insertions(+), 9 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberPresenter.kt b/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberPresenter.kt
index b646e22f173..ab4b598153e 100644
--- a/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberPresenter.kt
+++ b/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberPresenter.kt
@@ -88,18 +88,12 @@ class AutocompleteMemberPresenter @AssistedInject constructor(context: Context,
 
         val items = mutableListOf().apply {
             if (members.isNotEmpty()) {
-                if (everyone != null) {
-                    // add header only when there is everyone tag as well
-                    add(membersHeader)
-                }
+                add(membersHeader)
                 addAll(members)
             }
             everyone?.let {
-                if (members.isNotEmpty()) {
-                    // add header only when there are members as well
-                    val everyoneHeader = createEveryoneHeader()
-                    add(everyoneHeader)
-                }
+                val everyoneHeader = createEveryoneHeader()
+                add(everyoneHeader)
                 add(it)
             }
         }

From df3fc38054e2645befe9876c138dcfeb4c9011e7 Mon Sep 17 00:00:00 2001
From: Maxime Naturel 
Date: Thu, 17 Feb 2022 14:14:44 +0100
Subject: [PATCH 407/581] Adding header section only when "@room" is available

---
 .../autocomplete/member/AutocompleteMemberPresenter.kt      | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberPresenter.kt b/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberPresenter.kt
index ab4b598153e..ce3b9c6a7e8 100644
--- a/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberPresenter.kt
+++ b/vector/src/main/java/im/vector/app/features/autocomplete/member/AutocompleteMemberPresenter.kt
@@ -85,10 +85,14 @@ class AutocompleteMemberPresenter @AssistedInject constructor(context: Context,
         val membersHeader = createMembersHeader()
         val members = createMemberItems(queryParams)
         val everyone = createEveryoneItem(query)
+        // add headers only when user can notify everyone
+        val canAddHeaders = canNotifyEveryone()
 
         val items = mutableListOf().apply {
             if (members.isNotEmpty()) {
-                add(membersHeader)
+                if (canAddHeaders) {
+                    add(membersHeader)
+                }
                 addAll(members)
             }
             everyone?.let {

From 0a87486f65d2e9c67a924522cff60848f9667a34 Mon Sep 17 00:00:00 2001
From: Maxime Naturel 
Date: Thu, 17 Feb 2022 14:19:17 +0100
Subject: [PATCH 408/581] Removing TODO

---
 .../src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt  | 1 -
 1 file changed, 1 deletion(-)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt
index b665ef71161..302f7387fad 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt
@@ -43,7 +43,6 @@ sealed class MatrixItem(
         override fun updateAvatar(newAvatar: String?) = copy(avatarUrl = newAvatar)
     }
 
-    // TODO is it correct to represent it by a Matrix Item ? => to confirm
     data class EveryoneInRoomItem(override val id: String,
                                   override val displayName: String = NOTIFY_EVERYONE,
                                   override val avatarUrl: String? = null,

From 41bd516ca9144ee7dfb6cf86dd15b829fca868cf Mon Sep 17 00:00:00 2001
From: Maxime Naturel 
Date: Thu, 17 Feb 2022 14:50:12 +0100
Subject: [PATCH 409/581] Updating changelog entry

---
 changelog.d/2782.misc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/changelog.d/2782.misc b/changelog.d/2782.misc
index 6c340219d53..dc200503692 100644
--- a/changelog.d/2782.misc
+++ b/changelog.d/2782.misc
@@ -1 +1 @@
-Collapse ACL events
+Collapse successive ACLs events in room timeline

From 6e013e1737efcdef900727a3630fedb1bd7e38b3 Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Thu, 17 Feb 2022 13:32:48 +0000
Subject: [PATCH 410/581] Translated using Weblate (Japanese)

Currently translated at 74.2% (2067 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 114 ++++++++++++++++------
 1 file changed, 83 insertions(+), 31 deletions(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 1c14024c3db..bfb07181f19 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -30,7 +30,7 @@
     
         %1$sと他%2$d名
     
-    %1$sは、今後のルーム履歴を%2$sに表示させました
+    %1$sは、今後のルーム履歴を%2$sに見えるように設定しました
     ルームのメンバー全員、招待された時点から。
     ルームのメンバー全員、参加した時点から。
     ルームのメンバー全員。
@@ -539,7 +539,7 @@
 \n不明なセッション:
     ルームに不明なデバイスが含まれています
     キーが一致していることを確認
-    一致する場合は、下の確認ボタンを押します。そうでない場合は、他の誰かがこのデバイスを盗聴しているので、代わりにブロックボタンを押すことをおすすめします。将来この検証プロセスはより洗練されたものになるでしょう。
+    一致する場合は、下の確認ボタンを押します。そうでない場合は、他の誰かがこのデバイスを盗聴しているので、代わりにブロックボタンを押すことをおすすめします。今後この検証プロセスはより洗練されたものになるでしょう。
     デバイスの検証
     不明なデバイス
     このセッションでは、未検証のセッションに対して暗号化されたメッセージを送信しない
@@ -562,7 +562,7 @@
     ダウンロードファイルに保存しますか?
     この招待はこのアカウントに関連付けられていない %s に送信されました。
 \n別のアカウントでログインするか、このメールを自分のアカウントに追加してください。
-    あなたは%sにアクセスしようとしています。話し合いに参加しますか?
+    あなたは%sにアクセスしようとしています。ディスカッションに参加しますか?
     ルーム
     これはルームのプレビューです。ルームでのやり取りは無効化されています。
     このユーザーにあなたと同じ権限を与えますが、この変更は取り消せません。
@@ -830,7 +830,7 @@
 \nアカウントを停止しても、 デフォルトではあなたが送信したメッセージは忘却されません。メッセージの忘却を望む場合は、以下のボックスにチェックを入れてください。
 \n
 \nMatrixのメッセージの可視性は、電子メールと同様のものです。メッセージを忘却すると、あなたが送信したメッセージは、新規または未登録のユーザーには共有されることはありませんが、すでにメッセージを取得している登録ユーザーは、今後もそのコピーにアクセスできます。
-    アカウントを停止したとき自分の送信した全てのメッセージを削除 (警告: この操作により、将来的なユーザーが会話を不完全な形で見ることになります)
+    アカウントを停止したとき自分の送信した全てのメッセージを削除(警告: この操作により、今後のユーザーは会話を不完全な形で見ることになります)
     続けるには、パスワードを入力してください:
     アカウントを停止
     パスワードを入力してください。
@@ -957,7 +957,7 @@
     ルーム一覧
     ルームの一覧へ公開する
     一般
-    セキュリティとプライバシー
+    セキュリティーとプライバシー
     ヘルプと概要
     ダイレクトメッセージ
     (編集済み)
@@ -979,7 +979,7 @@
     その他のセッション
     暗号化を有効にする
     暗号化設定は有効化後変更できません。
-    セキュリティ
+    セキュリティー
     詳細
     その他の設定
     管理者としての操作
@@ -1067,7 +1067,7 @@
     このコンテンツを報告する理由
     報告する
     ユーザーを無視する
-    ユーザーを無視する
+    ユーザーを無視
     警告:
     
         元の大きさのまま画像を送信する
@@ -1122,7 +1122,7 @@
     検証リクエスト
     通話の開始前に確認する
     意図しない通話を阻止する
-    お使いの端末は脆弱性のある古いTLSセキュリティプロトコルを使用しています、このセキュリティでは接続できません
+    お使いの端末は脆弱性のある古いTLSセキュリティープロトコルを使用しています、このセキュリティーでは接続できません
     SSLエラー。
     SSLエラー:相手のアイデンティティが認証されていません。
     このURLからホームサーバーに接続できませんでした、ご確認ください
@@ -1219,7 +1219,7 @@
     全員への通知
     他の人から送信されたメッセージの削除
     ユーザーのブロック
-    ユーザーのキック
+    ユーザーの除去
     設定を変更する
     招待されたユーザー
     メッセージを送る
@@ -1233,11 +1233,11 @@
     禁止されたユーザー
     禁止の理由
     ユーザーの禁止を解除する
-    キックするユーザーは、このルームから削除されます。
+    このユーザーはルームから除去されます。
 \n
-\n再参加を防ぐためには、キックの代わりにブロックする必要があります。
-    キックする理由
-    ユーザーをキックする
+\n再参加を防ぐためには、除去する代わりにブロックする必要があります。
+    除去の理由
+    ユーザーを除去
     このユーザーの招待をキャンセルしてよろしいですか?
     招待をキャンセル
     このユーザーを解除すると、そのユーザーからの全てのメッセージが再び表示されます。
@@ -1352,9 +1352,9 @@
     %sがここをアップグレードしました。
     %s がこのルームをアップグレードしました。
     エンドツーエンド暗号化をオンにしました (%1$s)
-    あなたは今後のメッセージを%1$sに見えるようにしました
-    今後のルーム履歴を%1$sに表示させました
-    %1$s は将来のメッセージを %2$sに見えるようにしました
+    あなたは今後のメッセージを%1$sに見えるように設定しました
+    今後のルーム履歴を%1$sに見えるように設定しました
+    %1$s は今後のメッセージを %2$sに見えるように設定しました
     %sが通話を設定するためのデータを送信しました。
     通話をしました。
     ビデオ通話をしました。
@@ -1362,8 +1362,8 @@
     %1$sが%2$sを永久追放しました。理由: %3$s
     あなたが%1$sの禁止を解除しました。理由: %2$s
     %1$sが%2$sの禁止を解除しました。理由: %3$s
-    あなたは%1$sをキックしました。理由: %2$s
-    %1$sが%2$sをキックしました。理由: %3$s
+    あなたは%1$sを除去しました。理由: %2$s
+    %1$sが%2$sを除去しました。理由: %3$s
     あなたは招待を拒否しました。理由: %1$s
     %1$s は招待を拒否しました。理由: %2$s
     退出しました。理由: %1$s
@@ -1453,14 +1453,14 @@
     ディスカバリー設定を管理します。
     ディスカバリー(発見)
     これにより、現在のキーまたはフレーズが置き換えられます。
-    新しいセキュリティキーを生成するか、既存のバックアップに新しいセキュリティフレーズを設定します。
+    新しいセキュリティーキーを生成するか、既存のバックアップに新しいセキュリティーフレーズを設定します。
     サーバー上の暗号化キーをバックアップすることにより、暗号化されたメッセージとデータへのアクセスが失われるのを防ぎます。
     メッセージ作成画面に絵文字キーボードを開くボタンを追加する
     絵文字キーボードを表示する
     アバターと表示名の変更が含まれます。
     アカウントイベントを表示する
-    招待、キック、禁止は影響を受けません。
-    招待/入室/退室/キック/禁止イベントや、アバター/表示名の変更など。
+    招待、削除、禁止は影響を受けません。
+    招待/参加/退出/削除/禁止イベントや、アバター/表示名の変更などを含む。
     入室と退室イベントの表示
     Use /confettiコマンドを使用するか、❄️または🎉を含むメッセージを送信します
     チャットでエフェクトを表示する
@@ -1664,7 +1664,7 @@
     または、リカバリーキーでバックアップを保護し、安全な場所に保存してください。
     暗号鍵のコピーを暗号化してホームサーバーに保存します。バックアップを保護するためにパスフレーズを設定してください。
 \n
-\n最高度のセキュリティのために、アカウントのパスワードと異なることが大切です。
+\n最高度のセキュリティーのために、アカウントのパスワードと異なることが大切です。
     パスフレーズを使用してバックアップを保護します。
     暗号化が有効なルームで送信されたメッセージはエンドツーエンド暗号化によって保護されます。メッセージを読むための暗号鍵を持っているのは送受信者のみです。
 \n
@@ -1732,7 +1732,7 @@
     誰でもルームを発見し参加できます
     公開
     招待された人だけが発見し参加できます
-    プライベート
+    非公開
     不明のアクセス設定(%s)
     誰でもノックができ、メンバーがその参加を承認または拒否できます
     現在のルーム一覧の見え方を取得できません(%1$s)。
@@ -1918,7 +1918,7 @@
     このセッションを検証して、信頼済みとしてマークします。やり取りの前に検証することで、より安全にメッセージすることができます。
     入力された検証要求
     検証を開始します
-    最大限のセキュリティを確保するために、これを行うか、別の信頼できる通信手段を用いることをお勧めします。
+    最大限のセキュリティーを確保するために、これを行うか、別の信頼できる通信手段を用いることをお勧めします。
     短い文字列を比較して検証します。
     認証が無効または期限切れのため、ログアウトされました。
     構成を使用する
@@ -1942,7 +1942,7 @@
     アクセス可能なスペース
     スペースのメンバーに発見とアクセスを許可します。
     スペース%sのメンバーが検索、プレビュー、参加できます。
-    プライベート(招待のみ)
+    非公開(招待のみ)
     デフォルトのメディアソース
     デフォルトの圧縮
     ルームのアップグレード
@@ -1972,7 +1972,7 @@
     キー共有リクエストの送信履歴
     結果がありません
     自分で電話をかけることはできません。参加者が招待を受け入れるのを待ちます
-    ミーティングはJitsiのセキュリティとパーミッションポリシーを使用します。会議中は、現在ルームにいる全ての人に招待状が表示されます。
+    ミーティングはJitsiのセキュリティーとパーミッションポリシーを使用します。会議中は、現在ルームにいる全ての人に招待状が表示されます。
     権限がありません
     音声メッセージを送信するには、マイクの権限を許可してください。
     この動作を行うには、システム設定からカメラの権限を許可してください。
@@ -2037,9 +2037,9 @@
     アドレス
     継続する
     ファイル
-    ユーザーをキックすると、このスペースから削除されます。
+    このユーザーはスペースから除去されます。
 \n
-\n再参加を防ぐには、代わりにブロックしてください。
+\n再参加を防ぐためには、除去する代わりにブロックする必要があります。
     通話を終了しています…
     通知を待機しています
     \@room
@@ -2084,7 +2084,7 @@
     このルームにファイルはありません
     このルームにメディアはありません
     公開ルームをアップグレード
-    プライベートスペース
+    非公開スペース
     公開スペース
     送信済
     送信中
@@ -2138,7 +2138,7 @@
     ルームのアップグレードには権限が必要です
     アップグレード
     アップグレードが必要です
-    プライベートルームをアップグレード
+    非公開ルームをアップグレード
     音声メッセージを一時停止
     音声メッセージを有効にする
     このメールアドレスをアカウントにリンク
@@ -2213,6 +2213,58 @@
     このルームへの参加は許可されていません
     "トピック: "
     トラブルシューティング
-    添付ファイルを取得中にエラーが発生しました。
+    添付ファイルの取得中にエラーが発生しました。
     キーバックアップのバナーを閉じる
+    キーワードに「%s」を含めることはできません
+    %sへのメール通知を有効にする
+    ヒント:メッセージを長押しして「%s」を使う。
+    スレッドを使うと、会話のテーマを保ったり、会話を追跡したりするのが容易になります。
+    あなたの非公開スペース
+    あなたの公開スペース
+    自分のみ
+    スレッドでディスカッションを整理して管理
+    %sを待機しています…
+    このデバイスでスキャン
+    検証を送信済
+    手動で検証
+    このセッションを検証
+    音声
+    ルームのアドレスを入力してください
+    このアドレスはすでに使用されています
+    スペースのアドレス
+    ルームのアドレス
+    一致しません
+    一致しています
+    自分のセッション一覧を表示
+    サインイン
+    サインアウトしました
+    データを消去
+    データを消去
+    全てのデータを消去
+    Matrix ID
+    サインアウトしました
+    サインイン
+    個人データを消去
+    履歴を消去
+    %1$sに接続
+    シングルサインオン
+    アカウントを作成
+    サーバーに接続
+    コミュニティー
+    チーム
+    この機能はベータ版です
+    新しいスペースを作成
+    キーワードは「.」から始めることができません
+    電子メールによる通知を受け取るには、メールアドレスをMatrixのアカウントと連携してください
+    不適切として報告済
+    ネットワークの接続がありません
+    添付ファイルを送信
+    詳細なログを有効にする。
+    メールアドレスか電話番号でアカウントを見つけてもらえるようにするには、IDサーバー(%s)の利用規約への同意が必要です。
+    ${app_name}はロケーションにアクセスできませんでした
+    ${app_name}はロケーションにアクセスできませんでした。後でもう一度やり直してください。
+    音声メッセージ(%1$s)
+    推奨のルームバージョンへとアップグレード
+    音声メッセージを録音
+    あなたのホームサーバーのポリシー
 
\ No newline at end of file

From 5f1cd97865631147ab39148f48587eb9f165a387 Mon Sep 17 00:00:00 2001
From: oksya8and8 
Date: Thu, 17 Feb 2022 13:53:02 +0000
Subject: [PATCH 411/581] Translated using Weblate (Japanese)

Currently translated at 74.2% (2067 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index bfb07181f19..8701820a3af 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2267,4 +2267,5 @@
     推奨のルームバージョンへとアップグレード
     音声メッセージを録音
     あなたのホームサーバーのポリシー
+    いちばん下へジャンプ
 
\ No newline at end of file

From 3c821fa9043de89620352c6db8a5c47d5dba1554 Mon Sep 17 00:00:00 2001
From: oksya8and8 
Date: Thu, 17 Feb 2022 17:00:12 +0000
Subject: [PATCH 412/581] Translated using Weblate (Japanese)

Currently translated at 76.1% (2122 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 8701820a3af..83cd387a7b9 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2268,4 +2268,7 @@
     音声メッセージを録音
     あなたのホームサーバーのポリシー
     いちばん下へジャンプ
+    %sを入力する
+    %sが読みました
+    %1$sと%2$sが読みました
 
\ No newline at end of file

From 551e1e7d184ebc6039e9fb666eecca3ea40f4b00 Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Thu, 17 Feb 2022 16:59:30 +0000
Subject: [PATCH 413/581] Translated using Weblate (Japanese)

Currently translated at 76.1% (2122 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 60 +++++++++++++++++++++--
 1 file changed, 57 insertions(+), 3 deletions(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 83cd387a7b9..4add3097d21 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -263,7 +263,7 @@
     セッション一覧
     全てのメッセージにタイムスタンプを表示
     タイムスタンプを12時間形式で表示
-    端末詳細
+    セッションの詳細
     ID(端末固有番号)
     公開端末名
     公開端末名の更新
@@ -1182,7 +1182,7 @@
     インテグレーションが無効になっています
     インテグレーションマネージャー
     インテグレーションを許可
-    データセーブモードでは、特定のフィルターを適用することで、プレゼンスの更新やタイピングの通知がフィルタリングされます。
+    データ節約モードでは、特定のフィルターを適用することで、プレゼンスの更新やタイピングの通知がフィルタリングされます。
     FCMトークンが正常に取得されました:
 \n%1$s
     Firebaseトークン
@@ -2267,8 +2267,62 @@
     推奨のルームバージョンへとアップグレード
     音声メッセージを録音
     あなたのホームサーバーのポリシー
-    いちばん下へジャンプ
+    一番下に移動
     %sを入力する
     %sが読みました
     %1$sと%2$sが読みました
+    ファイルを使用
+    アカウントのパスワード
+    暗号化を有効にしますか?
+    他のルーム
+    キャンセルしました
+    matrix.orgを選択
+    このスペースを公開
+    ルームを追加
+    既存のスペースを追加
+    既存のルームを追加
+    全てのルームとスペースから退出
+    セットアップを終了
+    ランダム
+    スペースを作成しています…
+    非公開
+    公開
+    ルームを新しいバージョンにアップグレード
+    スペースを作成
+    イベントを送信しました!
+    送信に失敗した全てのメッセージを削除
+    失敗しました
+    公開スペース
+    公開されたルーム
+    アバターを削除
+    アバターを変更
+    かけ直す
+    ダイヤルパッド
+    Matrixのリンク
+    現在のPINコードを変更
+    PINコードを変更
+    PINコードと生体認証でアクセスを保護できます。
+    アクセスを保護
+    PINコードをリセットするには、再ログインして新しいコードを作成してください。
+    新しいPINコード
+    PINコードをリセット
+    PINコードを忘れましたか?
+    PINコードを入力
+    PINコードを確認
+    ユーザーのロケーションをタイムラインに表示
+    一度有効にすると、ロケーションはどのルームにも送れるようになります
+    サードパーティー製ライブラリー
+    いつでも設定から無効にできます
+    私たちは情報を第三者と共有することはありません
+    私たちはアカウントのデータを記録したり分析したりすることはありません
+    Elementの改善を手伝う
+    リンクを知っている人を対象に、このルームを公開しました。
+    どのユーザーも無視していません
+    キーワードを入力するとリアクションを検索できます。
+    変更を加えませんでした
+    %1$sは変更を加えませんでした
+    ファイル「%1$s」(%2$s)は大きすぎてアップロードできません。制限は%3$sです。
+    
+        %d人のユーザーが読みました
+    
 
\ No newline at end of file

From 6e68f9a224c5b0fa5a2e5569f460af59d980b86c Mon Sep 17 00:00:00 2001
From: Benoit Marty 
Date: Fri, 4 Feb 2022 17:38:49 +0100
Subject: [PATCH 414/581] Remove piwik configuration, it was not used.

---
 .idea/dictionaries/bmarty.xml         | 1 +
 vector/src/main/res/values/config.xml | 3 ++-
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/.idea/dictionaries/bmarty.xml b/.idea/dictionaries/bmarty.xml
index f99842f0671..ed572b573f4 100644
--- a/.idea/dictionaries/bmarty.xml
+++ b/.idea/dictionaries/bmarty.xml
@@ -28,6 +28,7 @@
       previewable
       previewables
       pstn
+      rageshake
       riotx
       signin
       signout
diff --git a/vector/src/main/res/values/config.xml b/vector/src/main/res/values/config.xml
index 8f23c2e1bef..3799d5a28df 100755
--- a/vector/src/main/res/values/config.xml
+++ b/vector/src/main/res/values/config.xml
@@ -5,7 +5,8 @@
 
     
     https://matrix.org
-    https://piwik.riot.im
+
+    
     https://riot.im/bugreports/submit
     riot-android
     element-auto-uisi

From e44ff1e303845c60cb2c437ec05e5fa4ca9c8bb4 Mon Sep 17 00:00:00 2001
From: Benoit Marty 
Date: Fri, 4 Feb 2022 21:03:31 +0100
Subject: [PATCH 415/581] Update preferred_jitsi_domain value from
 "jitsi.riot.im" to "meet.element.io"

---
 vector/src/main/res/values/config.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/vector/src/main/res/values/config.xml b/vector/src/main/res/values/config.xml
index 3799d5a28df..78b92cbfa4f 100755
--- a/vector/src/main/res/values/config.xml
+++ b/vector/src/main/res/values/config.xml
@@ -22,7 +22,7 @@
     im.vector.app.android
 
     
-    jitsi.riot.im
+    meet.element.io
 
     
         matrix.org

From f57ece5ca80f4e498adb77cc189e9119799a6177 Mon Sep 17 00:00:00 2001
From: Benoit Marty 
Date: Fri, 4 Feb 2022 21:15:51 +0100
Subject: [PATCH 416/581] Add icon to the project!

---
 .idea/icon.png | Bin 0 -> 14783 bytes
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 .idea/icon.png

diff --git a/.idea/icon.png b/.idea/icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..6f7872211b0f603c5340b3d0d2eae6c878e89233
GIT binary patch
literal 14783
zcmeIZ^oXy600fZxD#)=D!ySC_R%EXe9A|4pjwJVmQ}$;$RY^4Gw%0%)
zIm2g<9~lOaY|kRznXF{3?TTwS>fxC68CC6O9?j+t#t!}pwbMvWbAKS6xH56ysHZPh
z_0`dh_&=`NeSeb8vK)f#%p}?A{xWni{
zD&$l?Akcp*xnv*)5>hTuC=~+^1bQ5*LJ8s}C5M1OjJ#M9&>PVIKlJ~j7FeQF866!P
zGe;s@`(4s9f^tR)SH%T(4yUa(<-JWG&=?&gLE(T%k}FQiReB@pI7h_2zPL+0Ll(_<
zap-vOuXSJ%|Ha(coV&a~zSiVJuE|7$Ks$xr|9cp{3jSVpRSSPW7uiwozISwSX-Ld!
zU1)PV(;c!FJ|}rx9-~6(RcrGjsCj#=j!dlF#Nf`dg+;?%qYHYWKwdF&$U7FJm2tG0
z8_X#X)_X{O>^9v{BMXj9P@&xWx|m-60bMS78};|rR)q!6SGVbUj?i2NoXVIg?S4m@
zxk;&qs}tW$*nObsQ+hi4c6Rf)g_h7l@cvYtNH1A1BTmJ5q+;>xHDJh0k
zB+el(m{iCww~5NfCf>)6T`AFq5*4RH+4V++$MViZ-2g^|;vEku7i)pp;bC-MS)k5n
zQ);hs=mZ1qJL{;?y2gm92y7;k{;w??_iyGf`rM6EJlddwY?2onCNadd#G3U-h1<%J
z%*OSff+mPOon687dg)kL6(2Cb*=63ARUYO0f4%i?4$*<13@f~U(Xq+_D)|#2_B@If
zt@{fWU&^WmL9_$!lS@V_Wk7cM1uoQlFH0&b$Hdx>^AFGsZdY$fSUFQ`Kx}%s$|t}4
z%2%}DVm{KFDJE9^vzy<)f6Kh~Th+lNb+*yHm5(RoPQcM^|6ZPIO+#b>irn*
znLlli`yf)T-)~R{cT&Zrm!TFML|4;+L77}0sZJ6$_(g*32b*~p>z2PkYQaKuZ=`xj
z+SPK&*bt}dCCOL*oK^(6;em#{ydT_w%h0jeSvQqQKn1;&t9r`mqyw_c+1Z))h>N4D;FB(N
zty9e`ED$#~;=YuZ_lDj8cG0x#!#J!JqJwlz3#Z7suq>V2{pLxS6
zCqLB_Yxva3A)%L@>|50P;&<{euVo%bo8dvHC4Yx$;r;pJpYf)+M=R0N<#cc8v|ocL
ziZqTwF466pWJ;e_<$T+QZ^ul0U8c90*l>1no2)MCd46uh93gHs@p5~6_lr{Q2%z`A
zSCys7pJ2#;KW*4R{3W1~P38eAj#cKR7y&o^t8y=5}MK&Nt?=O|bTmNLhe%%p$nE
zc<6pY@H|DI#y|S4xg%^+Q7=yhb5bcAT%o5e^vVfP>zI)$6?XU%&Z)5Ph+=wmN*6$iRpTJ)rZr)(|d;{>!aluWEryeS_;eJsd~VyZeX`
zOZfH3A%Dtl{9PXpKM_lfF{+pk>aAdg^hbc*80Z?R`BZ>zwI8!1$G0pdMwt38D^Z=B
zACR-y)KE1V---{8(TWTV6urDK^9RbCUj`;(%hC1I9u_VkMgr-(T-(YiE1**UX~99r?TrU4@M@EF0#H`3?-A
zcX}i4T#t~MNikkTxw<$zi!hs;Il4^SieE0WFjv15^3BqA5D`&TjY_mBJB3Y*q)`O^
z-v1RNTpTM=s9}XzpE97#EKr38FfGrYi(mCz9)+AISe-*zxY$>3$Q#9_|E>oDgoqDn
zL^>WS44zv*yafl7{RyMy`hk6LT3uvPXcQ=dSmM7m-G*O#vfZ&S&OP&%~06-F;x`!Tp9-RNj}X}SQVT2pLK
zoWSG9E^CS2NAP#fj#Qh~Y#(XD=vmsTwx|&sZ@24RKVNC|N1U4N`X0FiBoc*RwpSfc
zlePSZ)fY5UMEyov*EEgvyYt_&h=MJ&KC8T!zaH@h9G0kxQ`@a4RA>`>?C1pIGt-xS
ze7~4X;jzv){xXIoqRK_kF@C!B0#;U8%);9x{|KiZ1GX&V70hj>YfpL{nk!oULSr>6W(%`5{)N8Nc~%reMMkS>+a
zvTA|{SaCvw5)D{F?MmAb-r)WFlXJ}Eapn0z>Af5VQwCR
z3o{l<7BX#g*1pHFK#|Xnm^&(8_lsd6ho~1mNJ7pj9MSC;7OiMr2E3n^oOq&tGrc?K
z*-f=qe?eAw*7~{o9=4`hiA_V_n}q2^Ath%evBy0E_07&dN601|2J$qZc{s1`9fsAD
z=K(J0^R0n?-PDQe`(b4oA$^na{;lEx9=VQ7bgZPdN^mauknxn+$;m*$(!O>=ej=n{LP6jqiICrBTbNV4AX;Xa{gXUr&@dZ)gg%jt=>4T~m{B0Evr+5A7f
z+oW6<8+7~HMReX#ho8nOoZghPi=64PB-I+(j;C>?AbnuMg4AJ&!LygGbw%>&#i-`G
z!!R{I`uQQsikB;e!Jl$sbdCn~0XaMcuPe1N?1_8NrT4iY^yg6Oog$o~MD#>N6c-H3bil`x->ZqeN;CgUbzv
z(V7>Lr~*}SHY0jILWarFpX=TSMPXt{Lbf>jEl8|NZfZOxBgT0JCA|)l)AqpWT4dx1
zWvM_JO})e!MeAaY9~FSwU3;FRyl9;rpfQ2^%kS>Cc1zEwuPtmkZ_>A}4I9NTJB-Y=
zbxeH_7vqlwhrw>-iPe=@ZKcZ8@Vw;592uy@XrzT(3VY}(Rw*KFczUQUSNN`ax*MkHT#T!<
z0kGLh&MfXskMBqEJ8h-f-L8$_ifWqKFD8)zoRBo#N|o5IxJCU)@I-Y_Iy(p5!hcwP
zmc{31{S7nDKwy=zx!u$RsSv4cxx_|Pd{!zQ$hPtTGD44W%L`GpvvBmet
zkBw~#n>a-q*W^TXG`9kk3a?gTU4{q^UoNuwL=16^2Jc)N6`3OCy7GqykF>0GtnjG5
z@~Xw+keEv=Dw;5Ei3ESTYqp6Z0UJI8=F$xVT?rfxF2?9Zcj9*j-23PD+)95ZZb@roS&
zQneH#ed5-0OOuVAj8<(>z^FEL?l=X_dd`pKzp3hrarIm793HIuqCN=6NqMA=f3HS>
zs1Dvl>Sb+G8~)@B+^yaypo|j<7KbY%0(4y_v&;yWj5x+!SeA>@8Q_~a-m!?AgugmG
zD@6~4+&%Yn@>2)`MCoZ&tWpQa*R#h53S~xjfAvUoz6ZB}q7VI`CXM+DR0mW6
zbMg*lT%+~wvsO{Y>t=0pn;STh?j1&4m;rJ9XR-RehUT`4bXiABsgM=;YkFwft;AT{
z+f)bNrSI6{548uL%g5#mwZ0jjOPQx$qQCa*tqM4E{0wtw`-f4Q>4_1%uEPlYb-LbR
zcowF=wrty4Qqt{*Kd<{COR8v-ov~1x^wdN?w$QWHeU!aCxU5hli21!na50z0jIMo%?M1^b@u0`r)Rg#g-niyX5fi3Vi|ife*c@?0=(#)_OlJ
zd$YYO=2<-)U>4WJ4^1|X|K$kd7DOj67=9k6qVzC)60-iY>tJe5MDV+~Cby+YrMX)m
zDrei_z(wFz6M`I`+alBPdX*El{%F6~+DRj0Mg0t0#yfuFaxy2wqrxbSV$J@b9D8$V
zK0xiAf>?t=7C$*WYeSQe2f&YtJX?G&OOoL45t|14-Z7s-m%8x&X=+Y_+ZELTDA3Bi
z1Ok1^G?ZfZ%2~;vbQ56@$sqk==K!^Rw7Om_txN2)vmu9vGRp*awhU78>+Vn)6;Tsk
z!m7Rgc489gBSN`56R*o_}FhoZNJ(=vIfif&`ZYk9@xA#}2(*#g`twypcICl2)
zmVp`wS7;Ehi@OuF-E(s96ve@K_OsJXYsn*Na&ov$I_KOMbzl0I~BR}><)ki?<^rf6F{mX&rXUw0bJO0%-0td6i|+xF2zOwlVAKeAIK1xt_J|)Sc_Fl
z;fi0SHr+oNx^}!#)Lg&k01jaFI^AnqJ}lWn8J~{sHf=IV^_o+o)!CXwgOUx^|Hjh2
zcBB0ada&l!Xf>e>1{@2>qj$TaqJo0gV(!y)9d&o)x33I15}Qg%23e!kInt~R1qE_A
z=+(f{OYr#RLjC`%>ZC)1E$nGTmUd966hMe
zbXzkGXc`$COJ|Yy-@8j$UAwqZ-j~tM%6Y4BmxB0epi)+RYASYG+2odiwRMOijq2G+
z`imkXvSKs7$8c>#`m9*%*&3Mrr&=!McY~<8s+tbeldD;p4EA9cy8&&b1Bx>_!I9A~
z4OK-^Rkc*n-L2kYFlv@Cd}c#yo%2@ty>IPRTd~V7>tA_ZNd-Wy<5*-%+PgD6%v?BR
zU$+XM?*$lYQ|)_J#o#U7+_D1o6YiTxUyj_I9{yurD}3`bw`CjQvb{iQF{{Wxa*Mm%
zTeQJrs&G@oLhO1fiuk5;}HNICL>?=BRq>HR${i
zcyxVV%>B<_8F83E=Z!z7Um8$*&LQ8*%rL_eDj(yF-?qp&yU+YCdxr&5t3DvHsB<2U
zxu;1G42L0L<=%8oQ$tOo3^Jr#9iQx@Dv`hPg;MPKAI{UiT$$?+7XCYSyYF*7tJ43v
zQxWe|G-iMNkXETHHTku#MNaoZ^_a@XFe_%dXz$^Fln34W&zrHdEMQVDqlT}$!p2fY
z>i}^;_uGIFe{H`1mF#?2_8z2?JaO-~LkUw1>rcgFA0*qw5gA}+`k3aO~;v>=0lQh0uw}==Nl`(Z8F;-_s7S^
z*xucuOWFnoaiLITii;J&7p;uotH
zdpx71AyAm^UbegM;+S;2v7>DzQBwHjYkn+Qq2gBsHKctt^}97Ci;@DBH4%t9f9-vf
zcQyXp%A|^VaBo=sOQ&MpnNtVk$jO6WJBqLuZ<4X0$oe@LtkxbnbdTd=@q8
zw|071A267Iz`5m_(F*ayYYBWW`wzX^R)5(F`?{RCP#W(|SM~nEX0p&uoJ!JT`y?%E
z6vTJDp$+f>%C%p$ue_Z`YwGl5fU1&zrO^Uu?W!Tr#I4vqA1?=d|(?EX3e
z(;3#0sVJUP_{rpbwbcIU<
z3`Wp|nwmL_iwpTMkF;1DZFr}>sT-}1bcQmp=)ti+>1qoVb=
zKgFC_*CeTsZo;JBiYOW|pfb`53Nz+Z?mcMqAoaE6k-PRz3y6-U0d5auEofrG_
zCaIzhwL4u*8F}FE+06@m?=nnAO%pkrEVAnhHyQQLeu#^|!M(C)gxU~YZ%YxON0$lq
zng!XCo|7w@ld{k!;9C&tr80?1u@h{G2H4sje`=uXs*8S@AlK&<-m%~qt7bbjJ#FR7
zDsH+H=x;&7nhsN=Sq)J-b#4)llGFBl$QhRSGr%PtV0F@h^?tT%yxIKocjBlsW~j+m
z*ftTMtOes6oWJGe@mcwmXb_=!70Mh)QOgO_sgtv*V)NiTT-!JvKt!pe2}|9Gd{c6!
zJ#q2gm$iPEuYmx?crO^$)oUC@gc4FU#jKW{T^|JH^>RfG>Hn7nJ_>gyp5Obrx#65l
z@O5(BL_74Sr_5=ic8?1Fe62P*b1)LMl00PgTh?p0XxjFttW?6GuF;&zIf*PN5R01D(w#x?o
zChi4>>Td$MLXqrd?Hz~7k>!4QW{z@)U97&k;P!|qvh%M!#7)@P=7
zr3by%90qLL5C#Tz5FV}*_U(rzNcz5SWj%NAYHpcfKLmB}ot2Gs40|sx->WO9`+;qn
z5b!vO==*7zIC6=o9zVj@prijlDq6xgJV{+Y!glM^W7Gz6kGk+%hXGXWafr
zd06;~BT_E^QE?+O%W7xKQ{p`R5ED%Y@2De2W(KeS?R<--X82;I}%yn
zxnT4TrPu0q%`on=86bFYw!4}vGKuijNBA5{$>NDVg3K+zx`Tjm^C-S4%(vV+_19`X
z7ZLsm@Zqb20DCI766litcduYoHtNJNsYu(x%g;y&d^Z~+>Q=AD3M5Uq1o<)dqxWf3
zLD325pD~>ahGhW?ca7yzVRtRxeBjwmEl)<)q7Ua{@HG|Y2RnMd)=1P7Rlq9JfHDCX
zBJ9=z##gDiO!~N3i^OEiEn1nv2SC!_b$ZgkeWK4I}AU=m@Htr{d4%s
zR3gN?SX-kF1=Oc<(x9jzX}$^(_En7ZOXxmr?XQ2uGrPPpk0(LgBgjL;qaiXCAigf~
zKN);(QRg&iCHpn7(B4tIF3xK9$JuWQKPQ&670qG3CuXh!S;tu~j4_E%ld6$_01$$o
z(Z6QcgS?u3BK0dE8&gd3uuAr~G{fp=Y)2VtZ!BKNqx8Ym<1oaebi02hfR1Jxud``~
zu#0Yw8zIWvnuk{(4l12m!@N^EzxYwhmL^2Onw^81my=lR;Rfc6tx`xX-&NWO5Ar7O
zo3EIrt;|0&T4_U)@pW=KBfKp8L(O-WZGC(=w*gjb;QY{{!p=G3OG`
z9vrj<{Ay012%1>5(elR5`C=iT`HISC?WlcY?M0+BkDZH$hd&~+V)0bEykJ^)e6MP4
zJ2C$s^fO*I<9fo;cMwjYaJUv)F!0|&l8V3k6%C>`nNvC(+?R)pJ(FHrF%mNDkHe7wiorm1M%j~
z_Ew^LZ83_nkxB9KsvZ2p;vyUAY%{-rZkd&vXSu@_rQdQDbh0pdeYNwKLGk1?@xtX4
zQBL7&HQ?g-RnB`g&EfFy(5de7chY5#>Nrpzla`itTC)Bu_?vKiIX{te)qUkkob_*S
z-RRadQH)fV2K@c+9uHRPjNaPSRS{HBGp=mx|j1y5$aXxP{a@jiTA+m(*#vQabKBeAc
za}kr$Eyeu9dyB0{<6CKQYg6B@FZIWz8zk0LijE=*lrIj3k9aP(bcc&<2rsVtMp+7i
zpLgUjT*bV+hg|t1ePn&LG3{gz`&E6aqGKBDe%iLT_`R~V{;U}KFoK(kRFPo{ZJdUH
zbmdQCHhzYL`1@i?P;fXJ(f+?;5qVYQDZlcmK7%4H**eGK)kRmvQ0wcY;T`8yt(tY=
zHk<60))Tq2?E-O*pZDEQm(W_bgG{<
z(RR++bK`qmT+^u2wyp_aQyj;(SsKmZ<8mj~GFQ2%wH4oKGr`dpoq#8GY6!+gzKz$ixU-f
z7`IS@&7l`faw#MvuxAw|{rfE@=pPJ>y4xy$Hni#({*2B$j@il?AR!7v6i3vI@031~
zZC+o)PELNlL|*uw*46Mg{lzDt_L5~RH#XoSn2?}3So1ognkUtPUgJ6M#D^e;8Pi#C
zI6~Hg+Asa{5xmu!QJ$Upps6V-2F^wbFKkgvD~DXIc83)W0p^
zG#RyX&A&6DY}O+9re-~+`EU6Ca79Fp(uLLnnRa#I6|>QFOvpmP5FK&ec8g8Y^qZ+X=_~5L{T6tJuP(r%$DILcx_}@7l)B!Qm
zw}81SzH~t}k1&X`Q>e|8W99Pj&nkzYS6LmFBUF+?v%e_bTg3SIYKu1pl||{;R_cu>
zxaD#6bnJX<-kkZSEy^s?2vzBwhGgKKZoAeIv{J|C1Ng5Wwf^XX^Tb1l;+F-=8W}x4
zm+Y^10>)3;pw`v0S4Xeaj!OaBaC_;6+TiP^vi=f_x(cA2BIE8{#ae1U{{GT(QOHCM&D8bq9XV8egl*7W`QXIPV4V6<9|}h3n2S5SnNBoj(NXL^KeBQ>bzBi@0QOcR^1xpExGW~ei665RU=l&H_@?D#i*0R_6DF{80j(sU533^
z-%`wL3E3Pc2p#l(ik!bao}=+KM&Se
z%`vH&>FH^@Ndv;~FY;nuzWA0m!ioCQO
zi5Seh@93r>37E9?;6Bo;cf2pT!92T0);*+;L6lU{52}@Ib0v0Svujw?o*a3Cxjhp1
zxOAJI=R5B(aMAIQx^~a8?>pY{r~XdtbKS6o_jB!^P>_~#FNdQSrgqFyY}E$d#8%Ci5kHbSi^!6nDWNWT^r|pHvAFBzw#I=tIV^Uq$1APT9pJu
z+`H0Rvf{Nba%-?ZCRdkzsFZsLXXhn+S{pXh2YY=$gn+}xgz{FAx>RbQ0@fi>zi<+vDX#J9uSZo6R7+~vj(C>k7uelY
zXg(0kW`({dI@rE{Q0a3`kWd8oH((?BP4}jH6xXy`0>|GN
z$=WZV3cr2x^8PIG$x&@;8_r~Yph$x!9_G=GJSbW|D}Q!Jxjs(gVKIe3DYm!ti+^!B
zwep5{&2X%ndi7&?UniZ@JMfWBL9Wy6S2L9}kHU02Z{m&6+`aVWKJH&@^AhfVK#5Zd!)yesr?i;5xo2(5+eN
zM#_~eSXt=ZmE
zCemMS_8j~MZWa91BV_M~nO(Pap!?}84VS@Yi$_de3$V3~?+99i=1kbHu`tx1Y2_2&
zPk$w`dq+3@y0a-onWnh+jI+GQ1J?(cO#C1}mjHW+y=U8E&1f3-#S@R)9|~mk85uUT
zp$>h-hVUbIS*P3Lze3!A@
zi`8x7y>#uiqD|}4v2Gjn>Z!0nef}AXjNn(GutYWg;+(HRVS#x&*+Fw9L-zgopGKhh
z4F+fE3a3A8ekWCHj9aDbcnI`bSmMa^vlLjy78KDWiCj%+du0cXVPDNg5>4utC&VXy
z-U}A{x$91dA+V|IjLMl5yyq=^3nBYp-KEMd2E}Ey1#%5
zG+z_UXwCgS-LbKB>z*uf%6rA4`NSli?`QtBvWD`=4eFdf&Kivu?tVM;2_vv|wQYBK?75yc
zRq4_gdhLQAJBU8O9E=Cee&&`>_%M1reAb*@Z_PDfmz<>cjmK_DL}$wV+X`?$Ekrgt
zrWqr_O3FnB+4(aqv)=bPs$WCA{6dkZP&VnXVoHF=ESVxmYoU@moNJadne+PWL9xns
zJ*($KQm%-his!yGx{^!<&+9z!K||R{LW)n0VzlS{&RD^^2D!R=GG~cQj_p!%jB7Yo
z3_n&v5D1H@DMwRh(Pwkuxti-eu(PTq0m@r=YHusYh{1C5X3hXN@gIpy#5}vF+yt#|
z0uFYhs|)GD$e&7O7>n)A;Cd*B*p{
zr)i-8nj29po2%uc+kr;UYSeM_khYGL{s|k(cM^k;!_D+l$gmIokAGSM?k~_A$%S_4
zRY1d3#BZX!KxBbypRMZ8x}Za691OQfTSc5%vR?^^SgRP(+o)t(fZ2iULmQFdH)#eu
zJsSn6BhP!&W2UNKX{I+cH^)H
zQLy(+Qb|hqG@*NbbQ14=?cHY|AJ-dOQhkl;x!sX>{6K+n??hVtEqiXX*pb6UiehM%Z38&=JOgE_n!e|p$c3`%+^a6kMP$0$1y)x
z08Hkj*BAftEpjo_i2^iwFh_%M9j~j{DnA?aj^ukfC7$DZ_9Rj+R8>n$DPkjeAa_?1
zH@O>5t*D}fB>USLbJjFW58Q{P>E?zz7K_TsU*Gqh6sPX7v`EpcV!UhUR&aVFafRBrNReluY2k&*u2G7&{(E
zVy0c#=L&TP{_(>_4yGr(k(w=)k_IQVGc^nMIwmA~TR~MZCoxvDW0R-e)5N9Jtyu?k
z;(_A5Ksee3Qb9Zy;tBN(9rq3Pxe&@zdZkDXPwC`bJ^hZ@j2W1;$jCl>TJ(S!<(cTD
zG+Qi_^MjI?`p0Gygz8X&A6tHJ^?u0OI&f->pUL@-L}iPIknef
z`}iAJ(W6OvJ933aMCKH``EBn~HM{VTyj}9df0GD$`F6YxED+>P(omZ~8kWt^rT;n5
z&cLUk*huMMZ_0#oj1UG^ei=Vr$J10)aapfiyKBj#_#Zi3hiSFuzm@^ZHIKMUY&kY&
zzVd1;-E2+bPuzH&vQw!*PG02c4kJ!Z?+LeET5mQY+iN$^WbooAH)0>3!+xK+r4sqv
zwoot0Ek#nC98Ry(a&#`((c;~^ziwvfG3S0_25+HarGt&P9lBe|ut%%sI%qj)2;}%z
zcRqD^+zQ_=hMK~=OLv-%bh{@LlfE`SbRmaFzu@L9umxv-x(zNtLX(V`gO+@`=(R_O
z6~MIdDg(a{%$Mq{}P
z)`W^x4H{IBL!-ehm1I(wt>k&P!hIx&{FJqGW)Q|W^y_ZIAbE7exNn+J_arXp>LDpI
z{>8Q<`r*#ToWAufGx7_VS@tiz0IL}7^8C}SPADVJj=>v>ctVWxFn2t<*zcvFjUM+s
zKA0>YusNp>Q_@IQQk2}d`z`hzDlL{l`HQ|)OXX6oO35?4Kh=T$(^N#;&G~Joi$L-3
zF0@3#oWpmU04r$t-NqzdEY!9uL0g}YK`d(R!fu6xX;4G}j}DgZ=?qXjbY#Hs7v9N!
z>s@SS7U}tA&65YK5XraGlt`1j3}?jgGo0!t%
zk%Z>nIa?P9J_~`-pU|$?oj-CGwz!SA1F!nSI$!5p|GN2I1AhlCu~eg1y(o$l*M2>TJ_|irDvQ
zO}~jZD^Y1>{SK`6Z?shQ7SNJ8P#r{=I`d?r*amdJoln|I1z4rV9SzxWpK0a-$qs#j
zXT<(#WcmC}L_DkLKb1v9r_>z#s{gPR#6CHX%!R=S-Tt!R`a5YXp&~m?Vo7Oevpd)X
z$u<7G71ZZ>x7uCFSnGalO!np0H#r)+&dsLBU1qi26B;}?RG*nM58Fk{Mlv|_KV`tf
z!Oti8gn?!8k5M!}r-USU7MLu@<^y-LWP$Jo`-S3rotJXs&6v^nXO^7>c=uodt_WB`fe1?*paZ*Y=
zY9HW3U;Wkv2EFD5<_`7cZB79r}uHAjV|M-Dxv0)g7
zfeoY*lBi+qp~iFF0;S^ch8C1-&1kq@blnH#4f<}QiXPIui8BO5Jh}!eO8pO;q(pf6
zujid=<&Y*LPGSy42KxQzyAk{#tkQdp_B{!R)}-{`HyCkrm=+XjOH-?n1i{vMRO0i|)>
zGk0LztuWaAt`<^Ib?zOao^enC9q{MCE|FY**THK|Lf#ZTAW(Hazbl_q_Y)S}Ru&oP
zfwWkB>j9253(%@EMs*;xWGoF*;Z}7_j1$6cI!2R$mLK)MPCc0$O1O(P`mC!&FrWbX
z?`A*f_w}}v3d79Ffp~S(IZIxDg*~~6%T%NUEo--$!=5ny@3UdVn&kRYGMbx)69L!;
m^)TQraD%6$?{oGQ*iN4;K3w;V0r
Date: Thu, 17 Feb 2022 18:20:20 +0100
Subject: [PATCH 417/581] Changelog

---
 changelog.d/5254.misc | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 changelog.d/5254.misc

diff --git a/changelog.d/5254.misc b/changelog.d/5254.misc
new file mode 100644
index 00000000000..2ae642e9b74
--- /dev/null
+++ b/changelog.d/5254.misc
@@ -0,0 +1 @@
+Change preferred jitsi domain from `jitsi.riot.im` to `meet.element.io`
\ No newline at end of file

From 8f42167fcead16148feb66358771a1ce73cf2d41 Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Thu, 17 Feb 2022 17:17:36 +0000
Subject: [PATCH 418/581] Translated using Weblate (Japanese)

Currently translated at 76.5% (2132 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 22 ++++++++++++++++------
 1 file changed, 16 insertions(+), 6 deletions(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 4add3097d21..f8011baea60 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -646,7 +646,7 @@
     暗号化されたメッセージ
     退出
     コミュニティーの詳細
-    読み込み中…
+    読み込んでいます…
     コミュニティー
     コミュニティー名で絞り込む
     招待
@@ -847,7 +847,7 @@
     このホームサーバーは月間アクティブユーザー上限に達しています。
     この上限を上げるには %s してください。
     このサービスを使い続けるには %s してください。
-    最初にルームのメンバーのみを読み込むことによりパフォーマンスを向上。
+    最初にルームのメンバーのみを読み込むことでパフォーマンスを向上。
     あなたのホームサーバーはルームのメンバーの簡易読み込みをサポートしていません。後で試してください。
     ルームのメンバーの簡易読み込み
     申し訳ありません、エラーが発生しました
@@ -1053,7 +1053,7 @@
     サムネイル送信中 (%1$s / %2$s)
     ファイル暗号化中…
     ファイル送信中 (%1$s / %2$s)
-    ファイルをダウンロード中 %1$s…
+    ファイル %1$s をダウンロードしています…
     ファイル
     連絡先
     カメラ
@@ -1173,7 +1173,7 @@
     QR コードによる追加
     コードを共有
     ${app_name} で会話しましょう: %s
-    フレンドを招待
+    友達を招待
     既知のユーザー
     無効なQRコード (無効な URI)!
     パスワードが一致しません
@@ -1624,7 +1624,7 @@
     リカバリーキーを入力してください
     履歴をアンロック
     鍵をインポート中…
-    鍵をダウンロード中…
+    鍵をダウンロードしています…
     リカバリーキーを計算しています…
     バックアップを復元:
     ネットワークエラー: 接続を確認して再試行してください。
@@ -2268,7 +2268,7 @@
     音声メッセージを録音
     あなたのホームサーバーのポリシー
     一番下に移動
-    %sを入力する
+    %sを入力
     %sが読みました
     %1$sと%2$sが読みました
     ファイルを使用
@@ -2325,4 +2325,14 @@
     
         %d人のユーザーが読みました
     
+    スペースへのアクセス
+    このサーバーはポリシーを提供していません。
+    数秒かかるかもしれません。少々お待ちください。
+    利用可能な言語を読み込んでいます…
+    ユーザーを招待
+    ユーザーを招待しています…
+    パスワードを選択してください。
+    メンバーを追加
+    ログインを検証
+    メッセージ…
 
\ No newline at end of file

From bdfe5639ee3a5b829a14a18a4958207a8be4d890 Mon Sep 17 00:00:00 2001
From: Maxime Naturel 
Date: Fri, 18 Feb 2022 09:53:09 +0100
Subject: [PATCH 419/581] Renaming merged event item class

---
 .../detail/timeline/factory/MergedHeaderItemFactory.kt | 10 +++++-----
 ...tMergedEventsItem.kt => MergedSimilarEventsItem.kt} |  2 +-
 2 files changed, 6 insertions(+), 6 deletions(-)
 rename vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/{DefaultMergedEventsItem.kt => MergedSimilarEventsItem.kt} (96%)

diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt
index 0b8c2eb5722..76ed024370d 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt
@@ -27,10 +27,10 @@ import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventVisi
 import im.vector.app.features.home.room.detail.timeline.helper.canBeMerged
 import im.vector.app.features.home.room.detail.timeline.helper.isRoomConfiguration
 import im.vector.app.features.home.room.detail.timeline.item.BasedMergedItem
-import im.vector.app.features.home.room.detail.timeline.item.DefaultMergedEventsItem
-import im.vector.app.features.home.room.detail.timeline.item.DefaultMergedEventsItem_
 import im.vector.app.features.home.room.detail.timeline.item.MergedRoomCreationItem
 import im.vector.app.features.home.room.detail.timeline.item.MergedRoomCreationItem_
+import im.vector.app.features.home.room.detail.timeline.item.MergedSimilarEventsItem
+import im.vector.app.features.home.room.detail.timeline.item.MergedSimilarEventsItem_
 import im.vector.app.features.home.room.detail.timeline.tools.createLinkMovementMethod
 import org.matrix.android.sdk.api.extensions.orFalse
 import org.matrix.android.sdk.api.query.QueryStringValue
@@ -83,7 +83,7 @@ class MergedHeaderItemFactory @Inject constructor(private val activeSessionHolde
                                                    event: TimelineEvent,
                                                    eventIdToHighlight: String?,
                                                    requestModelBuild: () -> Unit,
-                                                   callback: TimelineEventController.Callback?): DefaultMergedEventsItem_? {
+                                                   callback: TimelineEventController.Callback?): MergedSimilarEventsItem_? {
         val mergedEvents = timelineEventVisibilityHelper.prevSameTypeEvents(
                 items,
                 currentPosition,
@@ -129,7 +129,7 @@ class MergedHeaderItemFactory @Inject constructor(private val activeSessionHolde
                 else                            -> null
             }
             summaryTitleResId?.let { summaryTitle ->
-                val attributes = DefaultMergedEventsItem.Attributes(
+                val attributes = MergedSimilarEventsItem.Attributes(
                         summaryTitleResId = summaryTitle,
                         isCollapsed = isCollapsed,
                         mergeData = mergedData,
@@ -139,7 +139,7 @@ class MergedHeaderItemFactory @Inject constructor(private val activeSessionHolde
                             requestModelBuild()
                         }
                 )
-                DefaultMergedEventsItem_()
+                MergedSimilarEventsItem_()
                         .id(mergeId)
                         .leftGuideline(avatarSizeProvider.leftGuideline)
                         .highlighted(isCollapsed && highlighted)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/DefaultMergedEventsItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedSimilarEventsItem.kt
similarity index 96%
rename from vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/DefaultMergedEventsItem.kt
rename to vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedSimilarEventsItem.kt
index 65675efca91..f012ca6cdce 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/DefaultMergedEventsItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedSimilarEventsItem.kt
@@ -28,7 +28,7 @@ import im.vector.app.R
 import im.vector.app.features.home.AvatarRenderer
 
 @EpoxyModelClass(layout = R.layout.item_timeline_event_base_noinfo)
-abstract class DefaultMergedEventsItem : BasedMergedItem() {
+abstract class MergedSimilarEventsItem : BasedMergedItem() {
 
     override fun getViewStubId() = STUB_ID
 

From 3702ccd2bafa9458f140f6398f7642718b8622ad Mon Sep 17 00:00:00 2001
From: Valere 
Date: Wed, 2 Feb 2022 19:05:03 +0100
Subject: [PATCH 420/581] Defensive coding to ensure encryption when room was
 once e2e

---
 .../sdk/api/session/crypto/CryptoService.kt   |  8 +++++
 .../crypto/CryptoSessionInfoProvider.kt       |  2 ++
 .../internal/crypto/DefaultCryptoService.kt   |  4 +++
 .../internal/crypto/store/IMXCryptoStore.kt   |  2 ++
 .../crypto/store/db/RealmCryptoStore.kt       | 16 ++++++++-
 .../store/db/RealmCryptoStoreMigration.kt     |  4 ++-
 .../store/db/migration/MigrateCryptoTo015.kt  | 36 +++++++++++++++++++
 .../crypto/store/db/model/CryptoRoomEntity.kt |  5 ++-
 .../room/relation/DefaultRelationService.kt   |  6 ++--
 .../session/room/relation/EventEditor.kt      | 16 ++++-----
 .../session/room/send/DefaultSendService.kt   |  8 ++---
 .../queue/EventSenderProcessorCoroutine.kt    |  2 +-
 .../room/summary/RoomSummaryUpdater.kt        |  3 +-
 13 files changed, 89 insertions(+), 23 deletions(-)
 create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo015.kt

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 e3f00a24b66..9b617f3e4f6 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
@@ -113,6 +113,14 @@ interface CryptoService {
 
     fun isRoomEncrypted(roomId: String): Boolean
 
+    /**
+     * This is a bit different than isRoomEncrypted
+     * A room is encrypted when there is a m.room.encryption state event in the room (malformed/invalid or not)
+     * But the crypto layer has additional guaranty to ensure that encryption would never been reverted
+     * It's defensive coding out of precaution (if ever state is reset)
+     */
+    fun shouldEncryptInRoom(roomId: String?): Boolean
+
     fun encryptEventContent(eventContent: Content,
                             eventType: String,
                             roomId: String,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt
index 82eced43711..2a58d731e53 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt
@@ -35,6 +35,8 @@ internal class CryptoSessionInfoProvider @Inject constructor(
 ) {
 
     fun isRoomEncrypted(roomId: String): Boolean {
+        // We look at the presence at any m.room.encryption state event no matter if it's
+        // the latest one or if it is well formed
         val encryptionEvent = monarchy.fetchCopied { realm ->
             EventEntity.whereType(realm, roomId = roomId, type = EventType.STATE_ROOM_ENCRYPTION)
                     .isEmpty(EventEntityFields.STATE_KEY)
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 0646e4d2b8c..f10cde67590 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
@@ -634,6 +634,10 @@ internal class DefaultCryptoService @Inject constructor(
         return cryptoSessionInfoProvider.isRoomEncrypted(roomId)
     }
 
+    override fun shouldEncryptInRoom(roomId: String?): Boolean {
+        return roomId?.let { cryptoStore.roomWasOnceEncrypted(it) } ?: false
+    }
+
     /**
      * @return the stored device keys for a user.
      */
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 82fb5653777..46f94bc3bc9 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
@@ -240,6 +240,8 @@ internal interface IMXCryptoStore {
      */
     fun getRoomAlgorithm(roomId: String): String?
 
+    fun roomWasOnceEncrypted(roomId: String): Boolean
+
     fun shouldEncryptForInvitedMembers(roomId: String): Boolean
 
     fun setShouldEncryptForInvitedMembers(roomId: String, shouldEncryptForInvitedMembers: Boolean)
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 33578ba06af..a07827c033a 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
@@ -631,7 +631,15 @@ internal class RealmCryptoStore @Inject constructor(
 
     override fun storeRoomAlgorithm(roomId: String, algorithm: String?) {
         doRealmTransaction(realmConfiguration) {
-            CryptoRoomEntity.getOrCreate(it, roomId).algorithm = algorithm
+            CryptoRoomEntity.getOrCreate(it, roomId).let { entity ->
+                entity.algorithm = algorithm
+                // store anyway the new algorithm, but mark the room
+                // as having been encrypted once whatever, this can never
+                // go back to false
+                if (algorithm == MXCRYPTO_ALGORITHM_MEGOLM) {
+                    entity.wasEncryptedOnce = true
+                }
+            }
         }
     }
 
@@ -641,6 +649,12 @@ internal class RealmCryptoStore @Inject constructor(
         }
     }
 
+    override fun roomWasOnceEncrypted(roomId: String): Boolean {
+        return doWithRealm(realmConfiguration) {
+            CryptoRoomEntity.getById(it, roomId)?.wasEncryptedOnce ?: false
+        }
+    }
+
     override fun shouldEncryptForInvitedMembers(roomId: String): Boolean {
         return doWithRealm(realmConfiguration) {
             CryptoRoomEntity.getById(it, roomId)?.shouldEncryptForInvitedMembers
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 685b2d2967f..cac64994868 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
@@ -32,6 +32,7 @@ import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo
 import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo012
 import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo013
 import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo014
+import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo015
 import timber.log.Timber
 import javax.inject.Inject
 
@@ -46,7 +47,7 @@ internal class RealmCryptoStoreMigration @Inject constructor() : RealmMigration
     // 0, 1, 2: legacy Riot-Android
     // 3: migrate to RiotX schema
     // 4, 5, 6, 7, 8, 9: migrations from RiotX (which was previously 1, 2, 3, 4, 5, 6)
-    val schemaVersion = 14L
+    val schemaVersion = 15L
 
     override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
         Timber.d("Migrating Realm Crypto from $oldVersion to $newVersion")
@@ -65,5 +66,6 @@ internal class RealmCryptoStoreMigration @Inject constructor() : RealmMigration
         if (oldVersion < 12) MigrateCryptoTo012(realm).perform()
         if (oldVersion < 13) MigrateCryptoTo013(realm).perform()
         if (oldVersion < 14) MigrateCryptoTo014(realm).perform()
+        if (oldVersion < 15) MigrateCryptoTo015(realm).perform()
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo015.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo015.kt
new file mode 100644
index 00000000000..7bfea7d72f2
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo015.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.MXCRYPTO_ALGORITHM_MEGOLM
+import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoRoomEntityFields
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+
+// Version 14L Update the way we remember key sharing
+class MigrateCryptoTo015(realm: DynamicRealm) : RealmMigrator(realm, 15) {
+
+    override fun doMigrate(realm: DynamicRealm) {
+        realm.schema.get("CryptoRoomEntity")
+                ?.addField(CryptoRoomEntityFields.WAS_ENCRYPTED_ONCE, Boolean::class.java)
+                ?.setNullable(CryptoRoomEntityFields.WAS_ENCRYPTED_ONCE, true)
+                ?.transform {
+                    val currentAlgorithm = it.getString(CryptoRoomEntityFields.ALGORITHM)
+                    it.set(CryptoRoomEntityFields.WAS_ENCRYPTED_ONCE, currentAlgorithm == MXCRYPTO_ALGORITHM_MEGOLM)
+                }
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoRoomEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoRoomEntity.kt
index 711b6984641..6167314b5a3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoRoomEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoRoomEntity.kt
@@ -27,7 +27,10 @@ internal open class CryptoRoomEntity(
         // Store the current outbound session for this room,
         // to avoid re-create and re-share at each startup (if rotation not needed..)
         // This is specific to megolm but not sure how to model it better
-        var outboundSessionInfo: OutboundGroupSessionInfoEntity? = null
+        var outboundSessionInfo: OutboundGroupSessionInfoEntity? = null,
+        // a security to ensure that a room will never revert to not encrypted
+        // even if a new state event with empty encryption, or state is reset somehow
+        var wasEncryptedOnce: Boolean? = false
         ) :
     RealmObject() {
 
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 3abf28fdd4f..848e14ff57b 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
@@ -30,7 +30,6 @@ import org.matrix.android.sdk.api.util.Cancelable
 import org.matrix.android.sdk.api.util.NoOpCancellable
 import org.matrix.android.sdk.api.util.Optional
 import org.matrix.android.sdk.api.util.toOptional
-import org.matrix.android.sdk.internal.crypto.CryptoSessionInfoProvider
 import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
 import org.matrix.android.sdk.internal.database.mapper.asDomain
 import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity
@@ -48,7 +47,6 @@ internal class DefaultRelationService @AssistedInject constructor(
         private val eventEditor: EventEditor,
         private val eventSenderProcessor: EventSenderProcessor,
         private val eventFactory: LocalEchoEventFactory,
-        private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
         private val findReactionEventForUndoTask: FindReactionEventForUndoTask,
         private val fetchEditHistoryTask: FetchEditHistoryTask,
         private val fetchThreadTimelineTask: FetchThreadTimelineTask,
@@ -146,7 +144,7 @@ internal class DefaultRelationService @AssistedInject constructor(
                 ?.also { saveLocalEcho(it) }
                 ?: return null
 
-        return eventSenderProcessor.postEvent(event, cryptoSessionInfoProvider.isRoomEncrypted(roomId))
+        return eventSenderProcessor.postEvent(event)
     }
 
     override fun getEventAnnotationsSummary(eventId: String): EventAnnotationsSummary? {
@@ -202,7 +200,7 @@ internal class DefaultRelationService @AssistedInject constructor(
                         saveLocalEcho(it)
                     }
         }
-        return eventSenderProcessor.postEvent(event, cryptoSessionInfoProvider.isRoomEncrypted(roomId))
+        return eventSenderProcessor.postEvent(event)
     }
 
     override suspend fun fetchThreadTimeline(rootThreadEventId: String): Boolean {
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 4551f390e70..b54cd71e508 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,7 +23,6 @@ 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.internal.crypto.CryptoSessionInfoProvider
 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
@@ -33,7 +32,6 @@ import javax.inject.Inject
 
 internal class EventEditor @Inject constructor(private val eventSenderProcessor: EventSenderProcessor,
                                                private val eventFactory: LocalEchoEventFactory,
-                                               private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
                                                private val localEchoRepository: LocalEchoRepository) {
 
     fun editTextMessage(targetEvent: TimelineEvent,
@@ -51,7 +49,7 @@ internal class EventEditor @Inject constructor(private val eventSenderProcessor:
         } else if (targetEvent.root.sendState.isSent()) {
             val event = eventFactory
                     .createReplaceTextEvent(roomId, targetEvent.eventId, newBodyText, newBodyAutoMarkdown, msgType, compatibilityBodyText)
-            return sendReplaceEvent(roomId, event)
+            return sendReplaceEvent(event)
         } else {
             // Should we throw?
             Timber.w("Can't edit a sending event")
@@ -72,7 +70,7 @@ internal class EventEditor @Inject constructor(private val eventSenderProcessor:
         } else if (targetEvent.root.sendState.isSent()) {
             val event = eventFactory
                     .createPollReplaceEvent(roomId, pollType, targetEvent.eventId, question, options)
-            return sendReplaceEvent(roomId, event)
+            return sendReplaceEvent(event)
         } else {
             Timber.w("Can't edit a sending event")
             return NoOpCancellable
@@ -82,12 +80,12 @@ internal class EventEditor @Inject constructor(private val eventSenderProcessor:
     private fun sendFailedEvent(targetEvent: TimelineEvent, editedEvent: Event): Cancelable {
         val roomId = targetEvent.roomId
         updateFailedEchoWithEvent(roomId, targetEvent.eventId, editedEvent)
-        return eventSenderProcessor.postEvent(editedEvent, cryptoSessionInfoProvider.isRoomEncrypted(roomId))
+        return eventSenderProcessor.postEvent(editedEvent)
     }
 
-    private fun sendReplaceEvent(roomId: String, editedEvent: Event): Cancelable {
+    private fun sendReplaceEvent(editedEvent: Event): Cancelable {
         localEchoRepository.createLocalEcho(editedEvent)
-        return eventSenderProcessor.postEvent(editedEvent, cryptoSessionInfoProvider.isRoomEncrypted(roomId))
+        return eventSenderProcessor.postEvent(editedEvent)
     }
 
     fun editReply(replyToEdit: TimelineEvent,
@@ -107,7 +105,7 @@ internal class EventEditor @Inject constructor(private val eventSenderProcessor:
                     eventId = replyToEdit.eventId
             ) ?: return NoOpCancellable
             updateFailedEchoWithEvent(roomId, replyToEdit.eventId, editedEvent)
-            return eventSenderProcessor.postEvent(editedEvent, cryptoSessionInfoProvider.isRoomEncrypted(roomId))
+            return eventSenderProcessor.postEvent(editedEvent)
         } else if (replyToEdit.root.sendState.isSent()) {
             val event = eventFactory.createReplaceTextOfReply(
                     roomId,
@@ -119,7 +117,7 @@ internal class EventEditor @Inject constructor(private val eventSenderProcessor:
                     compatibilityBodyText
             )
                     .also { localEchoRepository.createLocalEcho(it) }
-            return eventSenderProcessor.postEvent(event, cryptoSessionInfoProvider.isRoomEncrypted(roomId))
+            return eventSenderProcessor.postEvent(event)
         } else {
             // Should we throw?
             Timber.w("Can't edit a sending event")
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 8c0ea0ec4cc..28c17f38b6a 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
@@ -46,7 +46,7 @@ import org.matrix.android.sdk.api.util.Cancelable
 import org.matrix.android.sdk.api.util.CancelableBag
 import org.matrix.android.sdk.api.util.JsonDict
 import org.matrix.android.sdk.api.util.NoOpCancellable
-import org.matrix.android.sdk.internal.crypto.CryptoSessionInfoProvider
+import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
 import org.matrix.android.sdk.internal.di.SessionId
 import org.matrix.android.sdk.internal.di.WorkManagerProvider
 import org.matrix.android.sdk.internal.session.content.UploadContentWorker
@@ -66,7 +66,7 @@ internal class DefaultSendService @AssistedInject constructor(
         private val workManagerProvider: WorkManagerProvider,
         @SessionId private val sessionId: String,
         private val localEchoEventFactory: LocalEchoEventFactory,
-        private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
+        private val cryptoStore: IMXCryptoStore,
         private val taskExecutor: TaskExecutor,
         private val localEchoRepository: LocalEchoRepository,
         private val eventSenderProcessor: EventSenderProcessor,
@@ -303,7 +303,7 @@ internal class DefaultSendService @AssistedInject constructor(
     private fun internalSendMedia(allLocalEchoes: List, attachment: ContentAttachmentData, compressBeforeSending: Boolean): Cancelable {
         val cancelableBag = CancelableBag()
 
-        allLocalEchoes.groupBy { cryptoSessionInfoProvider.isRoomEncrypted(it.roomId!!) }
+        allLocalEchoes.groupBy { cryptoStore.roomWasOnceEncrypted(it.roomId!!) }
                 .apply {
                     keys.forEach { isRoomEncrypted ->
                         // Should never be empty
@@ -334,7 +334,7 @@ internal class DefaultSendService @AssistedInject constructor(
     }
 
     private fun sendEvent(event: Event): Cancelable {
-        return eventSenderProcessor.postEvent(event, cryptoSessionInfoProvider.isRoomEncrypted(event.roomId!!))
+        return eventSenderProcessor.postEvent(event)
     }
 
     private fun createLocalEcho(event: Event) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt
index eb691616140..f892dcee558 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt
@@ -92,7 +92,7 @@ internal class EventSenderProcessorCoroutine @Inject constructor(
     }
 
     override fun postEvent(event: Event): Cancelable {
-        return postEvent(event, event.roomId?.let { cryptoService.isRoomEncrypted(it) } ?: false)
+        return postEvent(event, cryptoService.shouldEncryptInRoom(event.roomId))
     }
 
     override fun postEvent(event: Event, encrypt: Boolean): Cancelable {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt
index a7887d77f87..1c1d59fb3d2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt
@@ -119,9 +119,8 @@ internal class RoomSummaryUpdater @Inject constructor(
         roomSummaryEntity.roomType = roomType
         Timber.v("## Space: Updating summary room [$roomId] roomType: [$roomType]")
 
-        // Don't use current state for this one as we are only interested in having MXCRYPTO_ALGORITHM_MEGOLM event in the room
         val encryptionEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_ENCRYPTION, stateKey = "")?.root
-        Timber.v("## CRYPTO: currentEncryptionEvent is $encryptionEvent")
+        Timber.d("## CRYPTO: currentEncryptionEvent is $encryptionEvent")
 
         val latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId)
 

From 9c9695546cb9e8564b59406e26d356359c1976f4 Mon Sep 17 00:00:00 2001
From: Valere 
Date: Wed, 2 Feb 2022 19:11:30 +0100
Subject: [PATCH 421/581] update change log

---
 changelog.d/5136.misc | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 changelog.d/5136.misc

diff --git a/changelog.d/5136.misc b/changelog.d/5136.misc
new file mode 100644
index 00000000000..43404acc31d
--- /dev/null
+++ b/changelog.d/5136.misc
@@ -0,0 +1 @@
+Defensive coding to ensure encryption when room was once e2e
\ No newline at end of file

From 48fffc3dcf09f62b2e9e48dadc91c7e2366e8666 Mon Sep 17 00:00:00 2001
From: Valere 
Date: Fri, 18 Feb 2022 10:08:44 +0100
Subject: [PATCH 422/581] Code review

---
 .../android/sdk/api/session/crypto/CryptoService.kt       | 8 --------
 .../android/sdk/internal/crypto/DefaultCryptoService.kt   | 4 ----
 .../android/sdk/internal/crypto/store/IMXCryptoStore.kt   | 6 ++++++
 .../room/send/queue/EventSenderProcessorCoroutine.kt      | 7 ++++---
 4 files changed, 10 insertions(+), 15 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 9b617f3e4f6..e3f00a24b66 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
@@ -113,14 +113,6 @@ interface CryptoService {
 
     fun isRoomEncrypted(roomId: String): Boolean
 
-    /**
-     * This is a bit different than isRoomEncrypted
-     * A room is encrypted when there is a m.room.encryption state event in the room (malformed/invalid or not)
-     * But the crypto layer has additional guaranty to ensure that encryption would never been reverted
-     * It's defensive coding out of precaution (if ever state is reset)
-     */
-    fun shouldEncryptInRoom(roomId: String?): Boolean
-
     fun encryptEventContent(eventContent: Content,
                             eventType: String,
                             roomId: String,
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 f10cde67590..0646e4d2b8c 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
@@ -634,10 +634,6 @@ internal class DefaultCryptoService @Inject constructor(
         return cryptoSessionInfoProvider.isRoomEncrypted(roomId)
     }
 
-    override fun shouldEncryptInRoom(roomId: String?): Boolean {
-        return roomId?.let { cryptoStore.roomWasOnceEncrypted(it) } ?: false
-    }
-
     /**
      * @return the stored device keys for a user.
      */
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 46f94bc3bc9..96ea5c03fa2 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
@@ -240,6 +240,12 @@ internal interface IMXCryptoStore {
      */
     fun getRoomAlgorithm(roomId: String): String?
 
+    /**
+     * This is a bit different than isRoomEncrypted
+     * A room is encrypted when there is a m.room.encryption state event in the room (malformed/invalid or not)
+     * But the crypto layer has additional guaranty to ensure that encryption would never been reverted
+     * It's defensive coding out of precaution (if ever state is reset)
+     */
     fun roomWasOnceEncrypted(roomId: String): Boolean
 
     fun shouldEncryptForInvitedMembers(roomId: String): Boolean
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt
index f892dcee558..5b4efa5df6f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt
@@ -26,9 +26,9 @@ import org.matrix.android.sdk.api.failure.Failure
 import org.matrix.android.sdk.api.failure.getRetryDelay
 import org.matrix.android.sdk.api.failure.isLimitExceededError
 import org.matrix.android.sdk.api.session.Session
-import org.matrix.android.sdk.api.session.crypto.CryptoService
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.util.Cancelable
+import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
 import org.matrix.android.sdk.internal.session.SessionScope
 import org.matrix.android.sdk.internal.task.CoroutineSequencer
 import org.matrix.android.sdk.internal.task.SemaphoreCoroutineSequencer
@@ -54,7 +54,7 @@ private const val MAX_RETRY_COUNT = 3
  */
 @SessionScope
 internal class EventSenderProcessorCoroutine @Inject constructor(
-        private val cryptoService: CryptoService,
+        private val cryptoStore: IMXCryptoStore,
         private val sessionParams: SessionParams,
         private val queuedTaskFactory: QueuedTaskFactory,
         private val taskExecutor: TaskExecutor,
@@ -92,7 +92,8 @@ internal class EventSenderProcessorCoroutine @Inject constructor(
     }
 
     override fun postEvent(event: Event): Cancelable {
-        return postEvent(event, cryptoService.shouldEncryptInRoom(event.roomId))
+        val shouldEncrypt = event.roomId?.let { cryptoStore.roomWasOnceEncrypted(it) } ?: false
+        return postEvent(event, shouldEncrypt)
     }
 
     override fun postEvent(event: Event, encrypt: Boolean): Cancelable {

From b8a3bda1abb4b08a5f65b9c9fdc97e21e00a5bbf Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Fri, 18 Feb 2022 09:41:49 +0000
Subject: [PATCH 423/581] Translated using Weblate (Japanese)

Currently translated at 76.6% (2135 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 53 ++++++++++++-----------
 1 file changed, 27 insertions(+), 26 deletions(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index f8011baea60..653d1e4ccc1 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -88,7 +88,7 @@
     OK
     キャンセル
     保存
-    退室
+    退出
     送信
     再送信
     削除
@@ -146,7 +146,7 @@
     パスワードが短すぎます(最小6文字)
     正しくない電子メールアドレスのようです
     正しくない電話番号のようです
-    すでに登録されている電子メールアドレスです。
+    既に登録されている電子メールアドレスです。
     パスワードが一致しません
     パスワードを忘れましたか?
     登録を続行するには電子メールを確認して下さい
@@ -181,7 +181,7 @@
     切断中
     待機中
     招待
-    このルームを退出する
+    このルームを退出
     検索
     %sさんが文字入力中…
     %1$sさんと %2$sさんが文字入力中…
@@ -202,7 +202,7 @@
     確認
     無効にする
     送信中 (%s%%)
-    このユーザー名はすでに使用されています
+    このユーザー名は既に使用されています
     削除
     参加
     あなたは %s さんに、このルームへ招待されています
@@ -237,7 +237,7 @@
     通知音
     このアカウントで通知を許可
     この端末で通知を許可
-    会話で発言されたとき
+    1対1のチャットでのメッセージ
     グループチャットでのメッセージ
     ルームへ招待されたとき
     通話の呼び出しがあったとき
@@ -391,7 +391,7 @@
 \n
 \n設定からプロフィールにメールアドレスを追加できます。
     このホームサーバーはあなたがロボットではない認証を求めます
-    ユーザー名はすでに使用されています
+    ユーザー名は既に使用されています
     ホームサーバー:
     IDサーバー:
     メールアドレスを確認しました
@@ -449,7 +449,7 @@
     ファイル
     ルーム
     ルーム一覧を見る
-    退室
+    会話から退出
     会話
     設定
     Version
@@ -591,14 +591,14 @@
     検索
     メッセージ
     ディレクトリを検索中…
-    サードパーティの通知
+    サードパーティーの使用に関する掲示
     このアプリのシステムの情報を見る。
     アプリの情報
     自分の表示名を含むメッセージ
     自分のユーザー名を含むメッセージ
     バージョン
     olmのバージョン
-    サードパーティの通知
+    サードパーティーの使用に関する掲示
     ホーム画面
     逃した通知があるルームをピン止めする
     未読のあるルームをピン止めする
@@ -806,7 +806,7 @@
     ユーザーの権限レベルを決める
     指定したIDのユーザーの管理者権限を取り消す
     指定したユーザーを現在のルームに招待
-    指定されたアドレスのルームに参加します
+    指定されたアドレスのルームに参加
     ルームを退室
     ルームのテーマを設定
     指定したIDのユーザーとの接続を切断
@@ -829,7 +829,7 @@
 \n
 \nアカウントを停止しても、 デフォルトではあなたが送信したメッセージは忘却されません。メッセージの忘却を望む場合は、以下のボックスにチェックを入れてください。
 \n
-\nMatrixのメッセージの可視性は、電子メールと同様のものです。メッセージを忘却すると、あなたが送信したメッセージは、新規または未登録のユーザーには共有されることはありませんが、すでにメッセージを取得している登録ユーザーは、今後もそのコピーにアクセスできます。
+\nMatrixのメッセージの可視性は、電子メールと同様のものです。メッセージを忘却すると、あなたが送信したメッセージは、新規または未登録のユーザーには共有されることはありませんが、既にメッセージを取得している登録ユーザーは、今後もそのコピーにアクセスできます。
     アカウントを停止したとき自分の送信した全てのメッセージを削除(警告: この操作により、今後のユーザーは会話を不完全な形で見ることになります)
     続けるには、パスワードを入力してください:
     アカウントを停止
@@ -1128,7 +1128,7 @@
     このURLからホームサーバーに接続できませんでした、ご確認ください
     有効なMatrixサーバーアドレスではありません
     このURLは検索結果に表示できません、ご確認ください
-    この電話番号はすでに使用されています。
+    この電話番号は既に使用されています。
     復旧用のメールアドレスを設定します。後からオプションでメールアドレスや電話番号を使用して知人に見つけてもらえるようにできます。
     シングルサインオンを使用してサインインする
     HDを使用する
@@ -1459,9 +1459,9 @@
     絵文字キーボードを表示する
     アバターと表示名の変更が含まれます。
     アカウントイベントを表示する
-    招待、削除、禁止は影響を受けません。
+    招待、削除、ブロックは影響を受けません。
     招待/参加/退出/削除/禁止イベントや、アバター/表示名の変更などを含む。
-    入室と退室イベントの表示
+    参加・退出イベントを表示
     Use /confettiコマンドを使用するか、❄️または🎉を含むメッセージを送信します
     チャットでエフェクトを表示する
     ルームのメンバーのイベントを表示する
@@ -1476,9 +1476,9 @@
     ${app_name}は、デバイスの限られたリソース(バッテリー)を維持する方法でバックグラウンド同期をします。
 \nデバイスの状態によっては、OSによって同期が延期される場合があります。
     LEDの色、振動、音を選択してください…
-    サイレント通知を構成する
-    通話の通知を設定する
-    うるさい通知を設定する
+    サイレント通知を設定
+    通話の通知を設定
+    うるさい通知を設定
     アプリはバックグラウンドでホームサーバーに接続する必要がないためバッテリー使用量を減らすことができます
     最適化を無視する
     画面をオフにした状態でデバイスのプラグを抜いて一定時間静止したままにすると、デバイスは機内モードになります。 これにより、アプリがネットワークにアクセスできなくなり、ジョブ、同期、および標準のアラームが防止されます。
@@ -1554,7 +1554,7 @@
     あなたのテーマ
     あなたのユーザーID
     あなたのアバターURL
-    ブラウザで開く
+    ブラウザーで開く
     ウィジェットをロードする
     ウィジェット
     アクティブなウィジェット
@@ -1841,8 +1841,8 @@
     プッシュルール
     エキスパート
     クイックリアクション
-    あなたはすでにこのルームを見ています!
-    その他のサードパーティーの通知
+    あなたは既にこのルームを見ています!
+    その他のサードパーティーの使用に関する掲示
     Matrix SDK バージョン
     ファイル\"%1$s\"からe2eキーをインポートします。
     キーのバックアップデータの取得中にエラーが発生しました
@@ -1876,8 +1876,8 @@
     会話
     ここで未読メッセージに追いつく
     ホームへようこそ!
-    未読メッセージはもうありません
-    追いついてきました!
+    未読メッセージはありません
+    未読はありません!
     %sがセッションを検証を要求しています
     インタラクティブセッション検証
     ルームに参加してアプリの使用を開始します。
@@ -1955,7 +1955,7 @@
     暗号化されたダイレクトメッセージ
     ダイレクトメッセージ
     自分のユーザー名
-    自分のディスプレーネーム
+    自分の表示名
     グループチャットでのメッセージの暗号化
     個別チャットでのメッセージの暗号化
     通知してください
@@ -2196,7 +2196,7 @@
     メンションとキーワードのみ
     ルームのスレッドを絞り込む
     退出
-    モバイルでは、暗号化されたルームでのメンションとキーワードの通知は受信できません。
+    携帯端末では、暗号化されたルームでのメンションとキーワードの通知は受信できません。
     ルームの暗号化の有効化
     スペースのメインアドレスの変更
     スペースのアバターの変更
@@ -2205,7 +2205,7 @@
     暗号化が正しく設定されていないためメッセージを送ることができません。クリックして設定を開いてください。
     暗号化が正しく設定されていないためメッセージを送ることができません。管理者に連絡して、暗号化を正しい状態に復元してください。
     %2$dの%1$d
-    あなたはすでにこのスレッドを見ています!
+    あなたは既にこのスレッドを見ています!
     ルームに表示
     ルームに表示
     スレッドを表示
@@ -2230,7 +2230,7 @@
     このセッションを検証
     音声
     ルームのアドレスを入力してください
-    このアドレスはすでに使用されています
+    このアドレスは既に使用されています
     スペースのアドレス
     ルームのアドレス
     一致しません
@@ -2335,4 +2335,5 @@
     メンバーを追加
     ログインを検証
     メッセージ…
+    このファイルは大きすぎてアップロードできません。
 
\ No newline at end of file

From bda92a9ab451c313d9d19102c0f111be32ff1bfb Mon Sep 17 00:00:00 2001
From: NIkita Fedrunov 
Date: Fri, 18 Feb 2022 10:23:56 +0100
Subject: [PATCH 424/581] track screen event when user enters the screen
 instead of when user leaves the screen

---
 changelog.d/5256.misc                                  |  1 +
 .../im/vector/app/core/platform/VectorBaseActivity.kt  |  6 +++---
 .../platform/VectorBaseBottomSheetDialogFragment.kt    | 10 +++-------
 .../im/vector/app/core/platform/VectorBaseFragment.kt  |  6 +++---
 .../app/features/analytics/screen/ScreenEvent.kt       |  6 +-----
 .../app/features/call/dialpad/DialPadFragment.kt       |  7 +------
 .../java/im/vector/app/features/home/HomeActivity.kt   |  8 +-------
 .../features/home/room/detail/RoomDetailActivity.kt    |  8 +-------
 .../features/settings/VectorSettingsBaseFragment.kt    | 10 +++-------
 9 files changed, 17 insertions(+), 45 deletions(-)
 create mode 100644 changelog.d/5256.misc

diff --git a/changelog.d/5256.misc b/changelog.d/5256.misc
new file mode 100644
index 00000000000..e20f52c7aa5
--- /dev/null
+++ b/changelog.d/5256.misc
@@ -0,0 +1 @@
+Analytics screen events are now tracked on screen enter instead of screen leave
\ No newline at end of file
diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt
index 5767acd44b4..6c49a1ff01f 100644
--- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt
+++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt
@@ -98,7 +98,6 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver
      * ========================================================================================== */
 
     protected var analyticsScreenName: MobileScreen.ScreenName? = null
-    private var screenEvent: ScreenEvent? = null
 
     protected lateinit var analyticsTracker: AnalyticsTracker
 
@@ -337,7 +336,9 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver
     override fun onResume() {
         super.onResume()
         Timber.i("onResume Activity ${javaClass.simpleName}")
-        screenEvent = analyticsScreenName?.let { ScreenEvent(it) }
+        analyticsScreenName?.let {
+            ScreenEvent(it).send(analyticsTracker)
+        }
         configurationViewModel.onActivityResumed()
 
         if (this !is BugReportActivity && vectorPreferences.useRageshake()) {
@@ -376,7 +377,6 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver
 
     override fun onPause() {
         super.onPause()
-        screenEvent?.send(analyticsTracker, analyticsScreenName)
         Timber.i("onPause Activity ${javaClass.simpleName}")
 
         rageShake.stop()
diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt
index 869a12e8712..11bfcdd9ef5 100644
--- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt
+++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt
@@ -54,7 +54,6 @@ abstract class VectorBaseBottomSheetDialogFragment : BottomShe
      * ========================================================================================== */
 
     protected var analyticsScreenName: MobileScreen.ScreenName? = null
-    private var screenEvent: ScreenEvent? = null
 
     protected lateinit var analyticsTracker: AnalyticsTracker
 
@@ -139,12 +138,9 @@ abstract class VectorBaseBottomSheetDialogFragment : BottomShe
     override fun onResume() {
         super.onResume()
         Timber.i("onResume BottomSheet ${javaClass.simpleName}")
-        screenEvent = analyticsScreenName?.let { ScreenEvent(it) }
-    }
-
-    override fun onPause() {
-        super.onPause()
-        screenEvent?.send(analyticsTracker)
+        analyticsScreenName?.let {
+            ScreenEvent(it).send(analyticsTracker)
+        }
     }
 
     override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt
index 6bd62707f2e..7b8eda76ff4 100644
--- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt
+++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt
@@ -59,7 +59,6 @@ abstract class VectorBaseFragment : Fragment(), MavericksView
      * ========================================================================================== */
 
     protected var analyticsScreenName: MobileScreen.ScreenName? = null
-    private var screenEvent: ScreenEvent? = null
 
     protected lateinit var analyticsTracker: AnalyticsTracker
 
@@ -145,14 +144,15 @@ abstract class VectorBaseFragment : Fragment(), MavericksView
     override fun onResume() {
         super.onResume()
         Timber.i("onResume Fragment ${javaClass.simpleName}")
-        screenEvent = analyticsScreenName?.let { ScreenEvent(it) }
+        analyticsScreenName?.let {
+            ScreenEvent(it).send(analyticsTracker)
+        }
     }
 
     @CallSuper
     override fun onPause() {
         super.onPause()
         Timber.i("onPause Fragment ${javaClass.simpleName}")
-        screenEvent?.send(analyticsTracker)
     }
 
     @CallSuper
diff --git a/vector/src/main/java/im/vector/app/features/analytics/screen/ScreenEvent.kt b/vector/src/main/java/im/vector/app/features/analytics/screen/ScreenEvent.kt
index 1ad4a1fa324..400bede415b 100644
--- a/vector/src/main/java/im/vector/app/features/analytics/screen/ScreenEvent.kt
+++ b/vector/src/main/java/im/vector/app/features/analytics/screen/ScreenEvent.kt
@@ -16,7 +16,6 @@
 
 package im.vector.app.features.analytics.screen
 
-import android.os.SystemClock
 import im.vector.app.features.analytics.AnalyticsTracker
 import im.vector.app.features.analytics.plan.MobileScreen
 import timber.log.Timber
@@ -25,8 +24,6 @@ import timber.log.Timber
  * Track a screen display. Unique usage.
  */
 class ScreenEvent(val screenName: MobileScreen.ScreenName) {
-    private val startTime = SystemClock.elapsedRealtime()
-
     // Protection to avoid multiple sending
     private var isSent = false
 
@@ -42,8 +39,7 @@ class ScreenEvent(val screenName: MobileScreen.ScreenName) {
         isSent = true
         analyticsTracker.screen(
                 MobileScreen(
-                        screenName = screenNameOverride ?: screenName,
-                        durationMs = (SystemClock.elapsedRealtime() - startTime).toInt()
+                        screenName = screenNameOverride ?: screenName
                 )
         )
     }
diff --git a/vector/src/main/java/im/vector/app/features/call/dialpad/DialPadFragment.kt b/vector/src/main/java/im/vector/app/features/call/dialpad/DialPadFragment.kt
index b33ce25f559..9d77680ff52 100644
--- a/vector/src/main/java/im/vector/app/features/call/dialpad/DialPadFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/call/dialpad/DialPadFragment.kt
@@ -69,12 +69,7 @@ class DialPadFragment : Fragment(), TextWatcher {
     private var screenEvent: ScreenEvent? = null
     override fun onResume() {
         super.onResume()
-        screenEvent = ScreenEvent(MobileScreen.ScreenName.Dialpad)
-    }
-
-    override fun onPause() {
-        super.onPause()
-        screenEvent?.send(analyticsTracker)
+        ScreenEvent(MobileScreen.ScreenName.Dialpad).send(analyticsTracker)
     }
 
     override fun onCreateView(
diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt
index f6b32973a06..b7bfdef21c6 100644
--- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt
@@ -163,14 +163,8 @@ class HomeActivity :
     }
 
     private val drawerListener = object : DrawerLayout.SimpleDrawerListener() {
-        private var drawerScreenEvent: ScreenEvent? = null
         override fun onDrawerOpened(drawerView: View) {
-            drawerScreenEvent = ScreenEvent(MobileScreen.ScreenName.Sidebar)
-        }
-
-        override fun onDrawerClosed(drawerView: View) {
-            drawerScreenEvent?.send(analyticsTracker)
-            drawerScreenEvent = null
+            ScreenEvent(MobileScreen.ScreenName.Sidebar).send(analyticsTracker)
         }
 
         override fun onDrawerStateChanged(newState: Int) {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt
index f5bf086e960..f40bee44db8 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt
@@ -158,14 +158,8 @@ class RoomDetailActivity :
     }
 
     private val drawerListener = object : DrawerLayout.SimpleDrawerListener() {
-        private var drawerScreenEvent: ScreenEvent? = null
         override fun onDrawerOpened(drawerView: View) {
-            drawerScreenEvent = ScreenEvent(MobileScreen.ScreenName.Breadcrumbs)
-        }
-
-        override fun onDrawerClosed(drawerView: View) {
-            drawerScreenEvent?.send(analyticsTracker)
-            drawerScreenEvent = null
+            ScreenEvent(MobileScreen.ScreenName.Breadcrumbs).send(analyticsTracker)
         }
 
         override fun onDrawerStateChanged(newState: Int) {
diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsBaseFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsBaseFragment.kt
index 4185fde6636..c907168954c 100644
--- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsBaseFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsBaseFragment.kt
@@ -44,7 +44,6 @@ abstract class VectorSettingsBaseFragment : PreferenceFragmentCompat(), Maverick
      * ========================================================================================== */
 
     protected var analyticsScreenName: MobileScreen.ScreenName? = null
-    private var screenEvent: ScreenEvent? = null
 
     protected lateinit var analyticsTracker: AnalyticsTracker
 
@@ -91,17 +90,14 @@ abstract class VectorSettingsBaseFragment : PreferenceFragmentCompat(), Maverick
     override fun onResume() {
         super.onResume()
         Timber.i("onResume Fragment ${javaClass.simpleName}")
-        screenEvent = analyticsScreenName?.let { ScreenEvent(it) }
+        analyticsScreenName?.let {
+            ScreenEvent(it).send(analyticsTracker)
+        }
         vectorActivity.supportActionBar?.setTitle(titleRes)
         // find the view from parent activity
         mLoadingView = vectorActivity.findViewById(R.id.vector_settings_spinner_views)
     }
 
-    override fun onPause() {
-        super.onPause()
-        screenEvent?.send(analyticsTracker)
-    }
-
     abstract fun bindPref()
 
     abstract var titleRes: Int

From e15968bc03e043fcfe0a6fc36be8a4e99f9a7ba3 Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Fri, 18 Feb 2022 11:41:38 +0000
Subject: [PATCH 425/581] Translated using Weblate (Japanese)

Currently translated at 77.2% (2152 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 160 ++++++++++++----------
 1 file changed, 90 insertions(+), 70 deletions(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 653d1e4ccc1..4edeee4c307 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -40,7 +40,7 @@
     %1$s がVoIP会議をリクエストしました
     VoIP会議が開始されました
     VoIP会議が終了しました
-    (アバターも変更された)
+    (アバターも変更されました)
     %1$s がルーム名を削除しました
     %1$s がルームトピックを削除しました
     %1$s がプロフィール %2$s を更新しました
@@ -118,13 +118,13 @@
     
         %d名
     
-    バグを報告
-    不具合の内容と状況の説明をお願い致します。あなたは何をしましたか?何が起こると思いますか?実際何が起こったのですか?
-    ここに不具合内容を説明
-    画面のスクリーンショット画像を送信
-    不具合発生時の動作記録を送信
+    不具合を報告
+    不具合の内容と状況の説明をお願いします。何をしましたか?何が起こるべきでしたか?実際に起こった事象は何でしょうか?
+    ここに不具合の内容を記述
+    スクリーンショットの画像を送信
+    クラッシュ時の動作記録を送信
     動作記録を送信
-    開発者が問題を診断するために、このクライアントの動作記録がバグの報告と一緒に送信されます。このバグの報告は、ログとスクリーンショットを含め、公開されません。上記文章のみを送信したい場合は以下のチェックを解除してください:
+    開発者が問題を診断するために、このクライアントの動作記録が不具合報告と一緒に送信されます。不具合報告は、動作記録とスクリーンショットを含めて、公開されることはありません。上記の説明文だけを送信したい場合は、以下のチェックを解除してください。
     あなたは不満で端末を振っているようです。不具合報告の画面を開きますか?
     前回アプリケーションは正常に停止しませんでした。クラッシュ報告の画面を開きますか?
     不具合を報告しました
@@ -183,14 +183,14 @@
     招待
     このルームを退出
     検索
-    %sさんが文字入力中…
-    %1$sさんと %2$sさんが文字入力中…
-    %1$sさん、%2$sさん他が文字入力中…
+    %sさんが文字入力しています…
+    %1$sさんと %2$sさんが文字入力しています…
+    %1$sさん、%2$sさん他が文字入力しています…
     暗号文を送信…
     明るいテーマ
     暗いテーマ
     黒いテーマ
-    同期中…
+    同期しています…
     メッセージ
     メンバー詳細
     引用
@@ -317,7 +317,7 @@
     再入室禁止された参加者
     拡張設定
     このルームのサーバー内識別ID
-    実験的
+    ラボ
     これらは予期せぬ不具合が生じるかもしれない実験的機能です。慎重に使用してください。
     エンドツーエンド暗号化
     エンドツーエンド暗号化を使用中
@@ -397,7 +397,7 @@
     メールアドレスを確認しました
     パスワードを初期化するには, アカウントに登録されている電子メールアドレスを入力してください:
     あなたのアカウントに登録された電子メールアドレスの入力が必要です.
-    新しいパスワードの入力が必要です.
+    新しいパスワードの入力が必要です。
     %s へ電子メールが送信されました. リンクをたどったら以下をクリックしてください.
     電子メールアドレスの確認に失敗しました: 電子メールのリンクをクリックしたことを確認してください
     パスワードがリセットされました。
@@ -412,11 +412,11 @@
     以下の容量で画像を送信
     ルームの説明
     通話が接続されました
-    通話を接続中…
+    通話を接続しています…
     通話着信中
     ビデオ通話の着信中
     音声通話の着信中
-    通話中…
+    通話しています…
     映像の接続に失敗
     カメラを開始できません
     他の端末で通話に応答しました
@@ -452,7 +452,7 @@
     会話から退出
     会話
     設定
-    Version
+    バージョン
     利用規約
     著作権
     個人情報保護方針
@@ -465,7 +465,7 @@
     IDサーバー
     メールアドレスが見つかりません。
     この電話番号は既に使用されています。
-    パスワード変更
+    パスワードを変更
     現在のパスワード
     新しいパスワード
     新しいパスワードの確認
@@ -490,7 +490,7 @@
     あなたはこのルームで権限がありません。
     ルーム %s は、見ることができません。
     ユーザー名
-    ホームサーバー URL
+    ホームサーバーのURL
     IDサーバーのURL
     ログイン
     Matrixアプリを追加
@@ -512,7 +512,7 @@
     ${app_name}はビデオ通話を行うためにカメラとマイクにアクセスする許可を必要としています。
 \n
 \n次のポップアップでアクセスを許可して通話ができるようにしてください。
-    ${app_name}アプリでは、あなたの端末の電話帳のメールアドレスや電話番号をもとに、他のユーザーを検索することができます。${app_name}による電話帳の検索を許可する場合は、次のポップアップでアクセスを許可してください。
+    ${app_name}では、あなたの端末の電話帳のメールアドレスや電話番号をもとに、他のユーザーを検索することができます。${app_name}による電話帳の検索を許可する場合は、次のポップアップでアクセスを許可してください。
     ${app_name}はあなたの電話帳のメールアドレスや電話番号をもとに他のユーザーを見つけることができます。
 \n
 \nこのアプリがあなたの電話帳へアクセスすることを許可しますか?
@@ -557,7 +557,7 @@
     通話
     通知あり(音量大)
     通知あり(音量小)
-    不具合報告
+    不具合の報告
     開封確認メッセージのリスト
     ダウンロードファイルに保存しますか?
     この招待はこのアカウントに関連付けられていない %s に送信されました。
@@ -590,9 +590,9 @@
     ダウンロードをキャンセルする
     検索
     メッセージ
-    ディレクトリを検索中…
+    ディレクトリを検索しています…
     サードパーティーの使用に関する掲示
-    このアプリのシステムの情報を見る。
+    このアプリの情報をシステム設定で表示する。
     アプリの情報
     自分の表示名を含むメッセージ
     自分のユーザー名を含むメッセージ
@@ -693,13 +693,13 @@
     あなたは%2$sによって%1$sへの参加を禁止されました
     理由: %1$s
     再参加
-    バグを報告するには端末を降ってください
+    端末を振って不具合を報告
     
         %dメンバーシップの変更
     
     参加者を表示
     見出しを開く
-    同期中…
+    同期しています…
     
         %d名の参加者
     
@@ -717,7 +717,7 @@
     
     ルームの履歴を消す
     アバター
-    名前があがったときのみ
+    メンションのみ
     通知のプライバシー
     標準
     このアプリはバックグラウンド動作の権限が必要です
@@ -731,7 +731,7 @@
     許可がないため、この操作を実行できません。
     実行
     システムアラート
-    可能であれば、英語で説明を書いてください。
+    可能であれば、英語で説明文を記述してください。
     音声を送信
     スタンプを送信
     使用可能なスタンプパックがありません。 
@@ -851,7 +851,7 @@
     あなたのホームサーバーはルームのメンバーの簡易読み込みをサポートしていません。後で試してください。
     ルームのメンバーの簡易読み込み
     申し訳ありません、エラーが発生しました
-    Version %s
+    バージョン %s
     エクスポートされた鍵を暗号化するパスフレーズを作成してください。 キーをインポートするには、同じパスフレーズを入力する必要があります。
     パスフレーズの作成
     パスフレーズは一致する必要があります
@@ -922,10 +922,10 @@
     検証
     バックアップから復元
     バックアップを削除
-    鍵をバックアップ中…
+    鍵をバックアップしています…
     全ての鍵をバックアップしました
     
-        %d 件の鍵をバックアップ中…
+        %d 件の鍵をバックアップしています…
     
     バージョン
     アルゴリズム
@@ -990,7 +990,7 @@
     
     アップロード
     ルームを退出
-    ルームから退室中…
+    ルームから退室しています…
     管理者
     モデレーター
     カスタム
@@ -1048,11 +1048,11 @@
     完了
     無視
     レビュー
-    待機中…
-    サムネイル暗号化中…
-    サムネイル送信中 (%1$s / %2$s)
-    ファイル暗号化中…
-    ファイル送信中 (%1$s / %2$s)
+    待機しています…
+    サムネイルを暗号化しています…
+    サムネイルを送信しています (%1$s / %2$s)
+    ファイルを暗号化しています…
+    ファイルを送信しています (%1$s / %2$s)
     ファイル %1$s をダウンロードしています…
     ファイル
     連絡先
@@ -1110,7 +1110,7 @@
     セキュアバックアップを設定
     管理
     セキュアバックアップ
-    ルームを作成中…
+    ルームを作成しています…
     招待されています
     %s からの招待
     このセッションは正常に検証されました。
@@ -1166,7 +1166,7 @@
     あなたにはこのルームの暗号化を有効にする権限がありません。
     未読メッセージ
     タイムラインでのスワイプによる返信を有効にする
-    タイムラインで非表示のイベントを表示する
+    タイムラインで非表示のイベントを表示
     QR コードをスキャン
     QR コード画像
     QR コード
@@ -1193,17 +1193,17 @@
     カスタムルールの読み込みに失敗しました。再試行してください。
     一部の通知はカスタム設定で無効になっています。
     一部のメッセージがサイレントに設定されていることに注意してください(音を出さずに通知します)。
-    1つ以上のテストが失敗しました。調査に役立つバグレポートを送信してください。
+    1つ以上のテストが失敗しました。調査用の不具合報告を送信してください。
     1つ以上のテストが失敗しました。提案された修正を試してください。
-    基本的な診断はOKです。 それでも通知が届かない場合は、調査に役立つバグレポートを送信してください。
-    実行中… (%1$dの %2$d)
+    基本的な診断はOKです。 それでも通知が届かない場合は、調査用の不具合報告を送信してください。
+    実行しています… (%1$dの %2$d)
     テストを実行する
     診断トラブルシューティング
     イベントごとの通知の優先順位
     メールであなたに送ったリンクをクリックして確認してください。
     %sを削除しますか?
     認証が必要です
-    あなたのパスワードを確認する
+    パスワードを確認
     暗号化されたルームでの検索はまだサポートされていません。
     ブロックされたユーザーを絞り込む
     トピックを変更する
@@ -1519,8 +1519,8 @@
 \n%1$s
     FCMトークンの取得に失敗しました:
 \n%1$s
-    🎉全てのサーバーの参加を禁止されています!このルームは使用できなくなりました。
-    変化がありません。
+    🎉全てのサーバーの参加がブロックされています!このルームは使用できなくなりました。
+    変更はありません。
     • サーバーにマッチするIPリテラルが禁止されています。
     • サーバーにマッチするIPリテラルが許可されるようになりました。
     • %sに一致するサーバーが許可リストから削除されました。
@@ -1601,7 +1601,7 @@
     バックアップを削除
     バックアップの状態を確認中
     バックアップの削除に失敗しました(%s)
-    バックアップを削除中…
+    バックアップを削除しています…
     このセッションで暗号鍵のバックアップを使用するには、パスフレーズまたはリカバリーキーでバックアップを復元してください。
     バックアップは%sという未検証セッションによる不正なしょめいがあります
     バックアップは%sという検証されたセッションによる不正な署名があります
@@ -1623,7 +1623,7 @@
     このリカバリーキーではバックアップを復号できませんでした。正しいリカバリーキーを入力したことを確認してください。
     リカバリーキーを入力してください
     履歴をアンロック
-    鍵をインポート中…
+    鍵をインポートしています…
     鍵をダウンロードしています…
     リカバリーキーを計算しています…
     バックアップを復元:
@@ -1631,7 +1631,7 @@
     このパスフレーズではバックアップを復号できませんでした。正しい復元パスフレーズを入力したことを確認してください。
     リカバリーキーを喪失しましたか? 設定で新しいリカバリーキーを設定できます。
     メッセージの復元
-    バックアップのバージョンを取得中…
+    バックアップのバージョンを取得しています…
     暗号化されたメッセージ履歴のロックを解除するには、復元パスフレーズを使用してください
     復元パスフレーズをご存知でなければ、%sができます。
     リカバリーキーを使用して暗号化されたメッセージ履歴をアンロックします
@@ -1641,7 +1641,7 @@
     続行しますか?
     暗号鍵が現在バックグラウンドでホームサーバーへバックアップされています。初期バックアップは数分かかることがあります。
     バックアップが開始されました
-    予期せぬエラー
+    予期しないエラー
     リカバリーキー
     パスフレーズを使用してリカバリーキーを生成中です。数秒かかることがあります。
     リカバリーキーを共有…
@@ -1817,19 +1817,19 @@
     あなたの会話。 それを所有する。
     アカウントの復旧用のメールアドレスを設定して、後からオプションで知人に見つけてもらえるようにできます。
     ここが%sとのダイレクトメッセージのスタート地点です。
-    編集が見つかりません
-    メッセージを編集する
-    ファイル %1$s はダウンロードされました!
+    変更履歴はありません
+    メッセージの変更履歴
+    ファイル %1$s をダウンロードしました!
     ビデオの圧縮%d%%
     画像を圧縮しています…
-    暗号化されたルームで完全な履歴を表示する
+    暗号化されたルームで完全な履歴を表示
     フィードバックを与える
     フィードバックを送信できませんでした (%s)
     ありがとうございます、あなたのフィードバックは正常に送信されました
-    ご質問がある場合は、私にご連絡ください
+    追加で確認が必要な事項がある場合は、連絡可
     フィードバック
-    現在、spaceのベータ版を使用しています。お客様のフィードバックは次のバージョンに反映されます。ご意見を参考にさせていただくため、お客様のプラットフォームとユーザー名を記載させていただきます。
-    スペースについてフィードバック
+    現在「スペース」のベータ版を使用しています。あなたのフィードバックは今後のバージョンに反映されます。ご意見を最大限に参考にさせていただくため、あなたのプラットフォームとユーザー名を記録させていただきます。
+    スペースについてのフィードバック
     提案の送信に失敗しました(%s)
     ありがとうございます、提案は正常に送信されました
     トークンの登録
@@ -1838,7 +1838,7 @@
     push_key:
     登録されたプッシュゲートウェイはありません
     プッシュルールが定義されていません
-    プッシュルール
+    プッシュ通知に関するルール
     エキスパート
     クイックリアクション
     あなたは既にこのルームを見ています!
@@ -1969,7 +1969,7 @@
         %d 通話できません
     
     デフォルトで使いもう尋ねない
-    キー共有リクエストの送信履歴
+    キー共有リクエストの履歴を送信
     結果がありません
     自分で電話をかけることはできません。参加者が招待を受け入れるのを待ちます
     ミーティングはJitsiのセキュリティーとパーミッションポリシーを使用します。会議中は、現在ルームにいる全ての人に招待状が表示されます。
@@ -1978,24 +1978,24 @@
     この動作を行うには、システム設定からカメラの権限を許可してください。
     このアクションを実行するには、いくつかの権限が不足しています。システム設定から権限を付与してください。
     IDサーバーに接続できませんでした
-    IDサーバーのURLを入力する
-    あなた既存の連絡先を発見するために、あなたの連絡先データ(電話番号や電子メール)を設定されたIDサーバー(%1$s)に送信することに同意しますか?
+    IDサーバーのURLを入力
+    連絡先を発見するために、あなたの連絡先のデータ(電話番号や電子メール)を、設定されたIDサーバー(%1$s)に送信することに同意しますか?
 \n
-\nプライバシー保護のため、送信データは送信前に必ずハッシュ化されます。
-    メールや電話番号の送信
+\nプライバシーの保護のため、データは送信前にハッシュ化されます。
+    メールと電話番号を送信
     同意する
     同意を撤回する
     あなたの連絡先から他のユーザーを発見するために、電子メールや電話番号をこのIDサーバーに送信することに同意していません。
     あなたの連絡先から他のユーザーを発見するために、このIDサーバーにメールや電話番号を送信することに同意しています。
-    メールや電話番号の送信
+    メールと電話番号を送信
     未確認
-    %sに確認メールを送りました。まず、メールを確認してリンクをクリックしてください。
+    %sに確認メールを送りました。まず、メールを確認してリンクをクリックしてください
     %sに確認のためのメールを送りました。メールにて確認リンクをクリックしてください
-    発見できる電話番号
+    検出可能な電話番号
     IDサーバーとの接続を解除すると、他のユーザーから自分を発見できなくなり、メールや電話で他のユーザーを招待することができなくなります。
     電話番号を追加すると、ディスカバリーオプションが表示されます。
     メールを追加すると、ディスカバリーオプションが表示されます。
-    現在、IDサーバーを使用していません。既存の連絡先を発見したり、その連絡先から発見できるようにするには、以下のように設定します。
+    現在、IDサーバーを使用していません。あなたの知っている連絡先を発見したり、その連絡先から発見されるようにするには、以下のように設定します。
     %1$sを使って知り合いを見つけたり、見つけられるようにしています。
     IDサーバーを変更
     IDサーバーの設定
@@ -2003,21 +2003,21 @@
     IDサーバー
     ボット、ブリッジ、ウィジェット、ステッカーパックを使う
     他の人が見つけられるように
-    規約を確認する
+    規約を確認
     利用規約
     編集履歴を表示
     ルームに参加しています…
     提案
     連絡先
     最近の
-    検索結果を得るために入力を始める
+    入力すると検索結果が表示されます
     結果が見つかりません、matrix IDを使ってサーバー上で検索してください。
     クリップボードにコピーされたリンク
-    メイン画面に未読通知専用のタブを追加しました。
-    ルーム名を検索する
+    メイン画面に未読通知専用のタブを追加する。
+    ルーム名を検索
     名前もしくはID (#例えば:matrix.org)
     ルームディレクトリを見る
-    新しいルームをつくる
+    新しいルームを作成
     お探しのものが見つかりませんか?
     あなたの提案をここに書いてください
     ご意見・ご感想をお聞かせください。
@@ -2110,7 +2110,7 @@
     あなた
     動画。
     絵文字で検証
-    待機中…
+    待機しています…
     ステッカー
     ファイル
     音声
@@ -2336,4 +2336,24 @@
     ログインを検証
     メッセージ…
     このファイルは大きすぎてアップロードできません。
+    この情報の送信に同意しますか?
+    連絡先を発見するには、連絡先のデータ(電話番号や電子メール)をあなたのIDサーバーに送信する必要があります。プライバシーの保護のため、データは送信前にハッシュ化されます。
+    連絡先を発見するには、連絡先のデータをあなたのIDサーバーに送信する必要があります。
+\n
+\nプライバシーの保護のため、データは送信前にハッシュ化されます。この情報を送信することに同意しますか?
+    メールアドレスと電話番号を %s に送信
+    このIDサーバーはポリシーを提供していません
+    IDサーバーのポリシーを隠す
+    IDサーバーのポリシーを表示
+    アカウントの新しいパスワードを設定…
+    シェイクを検出しました!
+    電話を振って、しきい値を試してください
+    検出のしきい値
+    予期しないエラーが生じた際に、${app_name}はより頻繁にクラッシュするかもしれません
+    素早くクラッシュ
+    開発者モードは隠された機能を有効にするため、アプリケーションが不安定になる恐れがあります。開発者向けです!
+    復号エラーが生じた際に、自動的にログを送信
+    復号エラーを自動的に報告する。
+    変更を有効にするにはアプリケーションの再起動が必要です。
+    LaTeXによる数学表記を有効にする
 
\ No newline at end of file

From c11a0192c057bca75c774aaa50e5337d1e16d6af Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Fri, 18 Feb 2022 12:52:17 +0000
Subject: [PATCH 426/581] Translated using Weblate (Japanese)

Currently translated at 77.4% (2157 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 128 +++++++++++-----------
 1 file changed, 64 insertions(+), 64 deletions(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 4edeee4c307..cd9eb82f92c 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -302,7 +302,7 @@
     低優先度
     なし
     参加と可視範囲
-    ルームの一覧へ公開する
+    ルームの一覧へ公開
     ルームへの参加
     ルームの履歴の可視範囲
     ルームの履歴を読める人は\?
@@ -366,7 +366,7 @@
     ルーム
     ルーム一覧
     利用者一覧
-    送信する
+    送信
     サインアウト
     アカウントを作成
     ログイン
@@ -430,8 +430,8 @@
     ダイレクトメッセージ
     再入室禁止
     再入室禁止解除
-    この参加者の発言を全て非表示にする
-    この参加者の発言を全て表示する
+    この参加者の発言を全て非表示
+    この参加者の発言を全て表示
     指名して呼掛け
     ユーザーID, 表示名, 電子メールアドレス
     接続端末一覧を表示
@@ -456,7 +456,7 @@
     利用規約
     著作権
     個人情報保護方針
-    3秒間画面を表示する
+    3秒間画面を表示
     利用規約
     著作権
     個人情報保護方針
@@ -517,12 +517,12 @@
 \n
 \nこのアプリがあなたの電話帳へアクセスすることを許可しますか?
     申し訳ありません。権限がないために操作が実行されませんでした
-    発言を通報する
+    発言を通報
     既読
     写真を撮影
     動画を撮影
     要求を無視
-    検証せずに共有する
+    検証せずに共有
     検証を開始
     リクエストに user_id がありません。
     リクエストに room_id がありません。
@@ -551,7 +551,7 @@
     パスフレーズを確認
     パスフレーズを入力
     エクスポート
-    暗号鍵をローカルファイルにエクスポートする
+    暗号鍵をローカルファイルにエクスポート
     ルームの暗号鍵をエクスポート
     検証
     通話
@@ -570,7 +570,7 @@
     端末の連絡先 (%d)
     ユーザーディレクトリ (%s)
     Matrixユーザーのみ
-    ユーザーIDで招待する
+    ユーザーIDで招待
     1つまたは複数のメールアドレスか、Matrix IDを入力してください
     信用する
     信用しない
@@ -586,8 +586,8 @@
     このユーザーによる全てのメッセージを非表示にしますか?
 \n
 \nこの操作はアプリを再起動するため時間がかかる場合があります。
-    アップロードをキャンセルする
-    ダウンロードをキャンセルする
+    アップロードをキャンセル
+    ダウンロードをキャンセル
     検索
     メッセージ
     ディレクトリを検索しています…
@@ -600,8 +600,8 @@
     olmのバージョン
     サードパーティーの使用に関する掲示
     ホーム画面
-    逃した通知があるルームをピン止めする
-    未読のあるルームをピン止めする
+    逃した通知があるルームをピン止め
+    未読のあるルームをピン止め
     分析
     データ節約モード
     メールアドレスを認証できません。メールを確認して、そこに記載してあるリンクをクリックしてください。その後、「続ける」をクリックしてください。
@@ -625,7 +625,7 @@
     認証する
     認証を取り消す
     ブラックリスト
-    ブラックリストから除外する
+    ブラックリストから除外
     このデバイスが信頼できることを確認するために、その所有者に何らかの他の方法(直接会って、または、電話で)連絡し、以下のキーが、そのデバイスの「設定」で確認できるキーと一致するかお尋ねください:
     ルームのディレクトリを選択
     公開のルームを表示するホームサーバーを入力してください
@@ -686,7 +686,7 @@
     ルーム
     参加済
     招待済
-    グループのメンバーをフィルタリングする
+    グループのメンバーをフィルタリング
     グループのルームを絞り込み
     管理者はこのコミュニティーの詳細を規定していません。
     あなたは%2$sによって%1$sから除外されました
@@ -780,7 +780,7 @@
     解析データを送信
     ${app_name}はアプリを改善するため、匿名の解析データを収集します。
     ${app_name}を改善するのを助けるため、解析を許可してください。
-    はい、助けたいです!
+    はい、手伝いたいです!
     あなたは現在どのコミュニティーのメンバーでもありません。
     ここに入力…
     
@@ -865,7 +865,7 @@
     展開
     畳む
     メッセージとエラーの場合
-    とにかく通話する
+    とにかく通話
     了承
     このホームサーバーの方針を閲覧し承認してください:
     通話設定画面
@@ -955,7 +955,7 @@
     公開
     誰でもこのルームに参加できるようになります
     ルーム一覧
-    ルームの一覧へ公開する
+    ルームの一覧へ公開
     一般
     セキュリティーとプライバシー
     ヘルプと概要
@@ -978,7 +978,7 @@
     現在のセッション
     その他のセッション
     暗号化を有効にする
-    暗号化設定は有効化後変更できません。
+    有効にすると、あとで無効にすることはできません。
     セキュリティー
     詳細
     その他の設定
@@ -1065,8 +1065,8 @@
     その他の報告…
     コンテンツの報告
     このコンテンツを報告する理由
-    報告する
-    ユーザーを無視する
+    報告
+    ユーザーを無視
     ユーザーを無視
     警告:
     
@@ -1120,8 +1120,8 @@
     スキャンできません
     断る
     検証リクエスト
-    通話の開始前に確認する
-    意図しない通話を阻止する
+    通話の開始前に確認
+    意図しない通話を阻止
     お使いの端末は脆弱性のある古いTLSセキュリティープロトコルを使用しています、このセキュリティーでは接続できません
     SSLエラー。
     SSLエラー:相手のアイデンティティが認証されていません。
@@ -1130,7 +1130,7 @@
     このURLは検索結果に表示できません、ご確認ください
     この電話番号は既に使用されています。
     復旧用のメールアドレスを設定します。後からオプションでメールアドレスや電話番号を使用して知人に見つけてもらえるようにできます。
-    シングルサインオンを使用してサインインする
+    シングルサインオンを使用してサインイン
     HDを使用する
     HDを使用しない
     背面
@@ -1186,10 +1186,10 @@
     FCMトークンが正常に取得されました:
 \n%1$s
     Firebaseトークン
-    Playサービスを修正する
+    Playサービスを修正
     GooglePlayサービスAPKは利用可能で最新の状態になっています。
     Playサービスチェック
-    設定を確認する
+    設定を確認
     カスタムルールの読み込みに失敗しました。再試行してください。
     一部の通知はカスタム設定で無効になっています。
     一部のメッセージがサイレントに設定されていることに注意してください(音を出さずに通知します)。
@@ -1197,7 +1197,7 @@
     1つ以上のテストが失敗しました。提案された修正を試してください。
     基本的な診断はOKです。 それでも通知が届かない場合は、調査用の不具合報告を送信してください。
     実行しています… (%1$dの %2$d)
-    テストを実行する
+    テストを実行
     診断トラブルシューティング
     イベントごとの通知の優先順位
     メールであなたに送ったリンクをクリックして確認してください。
@@ -1206,7 +1206,7 @@
     パスワードを確認
     暗号化されたルームでの検索はまだサポートされていません。
     ブロックされたユーザーを絞り込む
-    トピックを変更する
+    トピックを変更
     ルームをアップグレード
     m.room.server_acl eventsを送信
     権限を変更
@@ -1220,7 +1220,7 @@
     他の人から送信されたメッセージの削除
     ユーザーのブロック
     ユーザーの除去
-    設定を変更する
+    設定を変更
     招待されたユーザー
     メッセージを送る
     デフォルトルール
@@ -1232,7 +1232,7 @@
     ブロックを解除すると、ユーザーは再びルームに参加できるようになります。
     禁止されたユーザー
     禁止の理由
-    ユーザーの禁止を解除する
+    ユーザーの禁止を解除
     このユーザーはルームから除去されます。
 \n
 \n再参加を防ぐためには、除去する代わりにブロックする必要があります。
@@ -1245,7 +1245,7 @@
     このユーザーを無視すると、あなたが共有しているルームからそのユーザーのメッセージが削除されます。
 \n
 \nこの動作は、設定からいつでも元に戻すことができます。
-    ユーザーを無視する
+    ユーザーを無視
     広角
     あなたは自分自身を降格させようとしているため、今後、この変更を元に戻すことはできなくなります。あなたがルームの中で最後の特権ユーザーである場合、特権を再取得することはできません。
     降格しますか?
@@ -1262,7 +1262,7 @@
     アクティブな通話(%s)
     あなたのホームサーバーがアシスト機能を提供しない場合、代わりに%sを使用します(IPアドレスは通話中に共有されます)
     ビデオ通話が行われています…
-    フォールバックコールアシストサーバーを許可する
+    フォールバックコールアシストサーバーを許可
     有効な認証情報ではありません
     ${app_name} 呼び出し失敗
     通話が確実に機能させるためには、ホームサーバー(%1$s)の管理者にTURNサーバーの設定を依頼してください。
@@ -1405,10 +1405,10 @@
 \nサーバーからの応答を待っています…
     空のルーム(%sでした)
     
-        %1$sと%2$sと%3$sそれに%4$dその他
+        %1$sと%2$sと%3$sと%4$dなど
     
-    %1$sと%2$sと%3$sそれに%4$s
-    %1$sと%2$s それに%3$s
+    %1$sと%2$sと%3$sと%4$s
+    %1$sと%2$sと%3$s
     %1$sを%2$sから%3$sへ
     %2$sが%1$sの権限を変更しました。
     %1$sの権限を変更しました。
@@ -1455,16 +1455,16 @@
     これにより、現在のキーまたはフレーズが置き換えられます。
     新しいセキュリティーキーを生成するか、既存のバックアップに新しいセキュリティーフレーズを設定します。
     サーバー上の暗号化キーをバックアップすることにより、暗号化されたメッセージとデータへのアクセスが失われるのを防ぎます。
-    メッセージ作成画面に絵文字キーボードを開くボタンを追加する
-    絵文字キーボードを表示する
+    メッセージ作成画面に絵文字キーボードを開くボタンを追加
+    絵文字キーボードを表示
     アバターと表示名の変更が含まれます。
-    アカウントイベントを表示する
+    アカウントイベントを表示
     招待、削除、ブロックは影響を受けません。
     招待/参加/退出/削除/禁止イベントや、アバター/表示名の変更などを含む。
     参加・退出イベントを表示
     Use /confettiコマンドを使用するか、❄️または🎉を含むメッセージを送信します
-    チャットでエフェクトを表示する
-    ルームのメンバーのイベントを表示する
+    チャットでエフェクトを表示
+    ルームのメンバーのイベントを表示
     ホームサーバーがこの機能をサポートしている場合は、チャット内のリンクをプレビューします。
     ボット、ブリッジ、ウィジェット、ステッカーパックの管理をします。
 \nユーザーに代わり、構成データを受信しウィジェットを変更、ルーム招待の送信、権限の設定などができます。
@@ -1480,7 +1480,7 @@
     通話の通知を設定
     うるさい通知を設定
     アプリはバックグラウンドでホームサーバーに接続する必要がないためバッテリー使用量を減らすことができます
-    最適化を無視する
+    最適化を無視
     画面をオフにした状態でデバイスのプラグを抜いて一定時間静止したままにすると、デバイスは機内モードになります。 これにより、アプリがネットワークにアクセスできなくなり、ジョブ、同期、および標準のアラームが防止されます。
     ${app_name}に対してバックグラウンド制限が有効になっています。
 \nアプリがバックグラウンドで実行しようとすると積極的に制限され、通知に影響を与える可能性があります。
@@ -1531,7 +1531,7 @@
         %1$sや %2$sそれに%3$dに他の人も読みました
     
     %1$sや %2$sそれに%3$sが読みました
-    メッセージをマークダウンとして解釈せずにプレーンテキストとして送信する
+    メッセージをマークダウンとして解釈せずにプレーンテキストとして送信
     ファイルとして保存
     共有
     完了
@@ -1539,7 +1539,7 @@
     バックアップを作成中
     パスフレーズを設定
     手動でキーをエクスポート
-    キーバックアップを使って開始する
+    キーバックアップを使って開始
     パスフレーズが弱すぎます
     パスフレーズを入力してください
     Google PlayサービスのAPKが見つかりませんでした。通知がうまく機能しない場合があります。
@@ -1555,7 +1555,7 @@
     あなたのユーザーID
     あなたのアバターURL
     ブラウザーで開く
-    ウィジェットをロードする
+    ウィジェットをロード
     ウィジェット
     アクティブなウィジェット
     自分
@@ -1571,22 +1571,22 @@
     選択
     選択
     詳細情報: %s
-    ローカルアドレスを追加する
+    ローカルアドレスを追加
     このルームにはローカルアドレスがありません
     アドレス \"%1$s\" を削除しますか?
     メインアドレス
     これがメインアドレスです
     電話番号の認証中にエラーが発生しました。
     リンクを共有
-    %s に招待する
+    %s に招待
     Eメールで招待
     詳細
-    人を招待する
+    人を招待
     とにかく参加
     ルームを追加
     %s はあなたを招待しています
     このルームでグループ通話をする権利がありません
-    オーディオミーティングを開始する
+    オーディオミーティングを開始
     安全バックアップを設定
     暗号鍵バックアップで管理
     暗号鍵バックアップを使用
@@ -1760,7 +1760,7 @@
     ルームへのアクセス
     発信履歴閲覧権限の変更は今後送信されるメッセージにのみ適用されます。既存履歴の表示は影響されません。
     無視して続行
-    毎回確認する
+    毎回確認
     招待
     おすすめのルーム
     スペース
@@ -1772,11 +1772,11 @@
     表示
     このルーム内のメッセージはエンドツーエンド暗号化されています。
     ダイレクトメッセージ
-    新しいダイレクトメッセージを送信する
+    新しいダイレクトメッセージを送信
     メールアドレス(任意)
     メールアドレス
     アカウントを回復するためのメールを設定します。 後で、オプションで、あなたが知人にあなたのメールに見つけてもらえるようにできます。
-    メールアドレスを設定する
+    メールアドレスを設定
     メールアドレスを確認しました
     検出可能なメールアドレス
     続行するには条件に同意してください
@@ -1799,7 +1799,7 @@
     SSOを続行します
     サインイン
     サインアップ
-    Element Matrix Servicesに接続する
+    Element Matrix Servicesに接続
     Matrix IDでサインイン
     Matrix IDでサインイン
     もっと詳しく知る
@@ -1811,7 +1811,7 @@
     メールと同じように、アカウントには1つのホームがありますが、誰とでも話すことができます
     サーバーを選択
     始めましょう
-    エクスペリエンスを拡張およびカスタマイズする
+    エクスペリエンスを拡張およびカスタマイズ
     暗号化して会話をプライベートに保つ
     直接またはグループで人々とチャットする
     あなたの会話。 それを所有する。
@@ -1866,7 +1866,7 @@
     ルーム管理者によってモデレートされたイベント
     リアクション
     リアクションを見る
-    リアクションを追加する
+    リアクションを追加
     いいね
     同意
     リアクション
@@ -1921,7 +1921,7 @@
     最大限のセキュリティーを確保するために、これを行うか、別の信頼できる通信手段を用いることをお勧めします。
     短い文字列を比較して検証します。
     認証が無効または期限切れのため、ログアウトされました。
-    構成を使用する
+    構成を使用
     ${app_name}がuserIdドメイン\"%1$s \"のカスタムサーバー構成を検出しました。
 \n%2$s
     サーバーオプションをおまかせする
@@ -1937,8 +1937,8 @@
     %1$s: %2$s
     あなたが知らないかもしれない他のスペースやルーム
     このルームを見つけて参加できるか決める。
-    タップしスペースを編集する
-    スペースを選択する
+    タップしスペースを編集
+    スペースを選択
     アクセス可能なスペース
     スペースのメンバーに発見とアクセスを許可します。
     スペース%sのメンバーが検索、プレビュー、参加できます。
@@ -1984,7 +1984,7 @@
 \nプライバシーの保護のため、データは送信前にハッシュ化されます。
     メールと電話番号を送信
     同意する
-    同意を撤回する
+    同意を撤回
     あなたの連絡先から他のユーザーを発見するために、電子メールや電話番号をこのIDサーバーに送信することに同意していません。
     あなたの連絡先から他のユーザーを発見するために、このIDサーバーにメールや電話番号を送信することに同意しています。
     メールと電話番号を送信
@@ -1992,11 +1992,11 @@
     %sに確認メールを送りました。まず、メールを確認してリンクをクリックしてください
     %sに確認のためのメールを送りました。メールにて確認リンクをクリックしてください
     検出可能な電話番号
-    IDサーバーとの接続を解除すると、他のユーザーから自分を発見できなくなり、メールや電話で他のユーザーを招待することができなくなります。
-    電話番号を追加すると、ディスカバリーオプションが表示されます。
-    メールを追加すると、ディスカバリーオプションが表示されます。
-    現在、IDサーバーを使用していません。あなたの知っている連絡先を発見したり、その連絡先から発見されるようにするには、以下のように設定します。
-    %1$sを使って知り合いを見つけたり、見つけられるようにしています。
+    IDサーバーとの接続を解除すると、他のユーザーによって発見されなくなり、また、メールアドレスや電話で他のユーザーを招待することができなくなります。
+    電話番号を追加すると、発見可能に設定する電話番号を選択できるようになります。
+    メールアドレスを追加すると、発見可能に設定するメールアドレスを選択できるようになります。
+    現在、IDサーバーを使用していません。あなたの知っている連絡先を発見したり、その連絡先から発見されるようにするには、以下を設定してください。
+    あなたは現在 %1$sを使って連絡先を見つけたり、連絡先から見つけられるようにしています。
     IDサーバーを変更
     IDサーバーの設定
     IDサーバーの切断
@@ -2035,7 +2035,7 @@
 \n設定画面からパスワードとリカバリーキーを早急に変更することを推奨します。
     Eメール
     アドレス
-    継続する
+    継続
     ファイル
     このユーザーはスペースから除去されます。
 \n

From 3ad7701ad7c28382e681c8f8c0c7e14a329ec8aa Mon Sep 17 00:00:00 2001
From: Maxime Naturel 
Date: Thu, 17 Feb 2022 15:14:08 +0100
Subject: [PATCH 427/581] Adding changelog entry

---
 changelog.d/5218.bugfix | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 changelog.d/5218.bugfix

diff --git a/changelog.d/5218.bugfix b/changelog.d/5218.bugfix
new file mode 100644
index 00000000000..93aece938ca
--- /dev/null
+++ b/changelog.d/5218.bugfix
@@ -0,0 +1 @@
+Fix crash during registration when redirecting to Web Browser

From cee5ea03aeaa28a9c999a40f44ac2601d8bb7fb4 Mon Sep 17 00:00:00 2001
From: Maxime Naturel 
Date: Thu, 17 Feb 2022 16:42:24 +0100
Subject: [PATCH 428/581] Retrieve session in init method differently

---
 .../app/features/login/LoginWebFragment.kt    |  1 +
 .../app/features/login2/LoginWebFragment2.kt  |  1 +
 .../ftueauth/FtueAuthWebFragment.kt           |  1 +
 .../signout/soft/SoftLogoutViewModel.kt       | 29 ++++++++++++-------
 4 files changed, 22 insertions(+), 10 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/login/LoginWebFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginWebFragment.kt
index ca21e96d20c..fc3392df097 100644
--- a/vector/src/main/java/im/vector/app/features/login/LoginWebFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/login/LoginWebFragment.kt
@@ -51,6 +51,7 @@ class LoginWebFragment @Inject constructor(
         private val assetReader: AssetReader
 ) : AbstractLoginFragment() {
 
+    // TODO confirm the need of this viewModel
     val softLogoutViewModel: SoftLogoutViewModel by activityViewModel()
 
     override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginWebBinding {
diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginWebFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginWebFragment2.kt
index ebe59ee1b91..6996cb552a2 100644
--- a/vector/src/main/java/im/vector/app/features/login2/LoginWebFragment2.kt
+++ b/vector/src/main/java/im/vector/app/features/login2/LoginWebFragment2.kt
@@ -56,6 +56,7 @@ class LoginWebFragment2 @Inject constructor(
         return FragmentLoginWebBinding.inflate(inflater, container, false)
     }
 
+    // TODO confirm the need of this viewModel
     val softLogoutViewModel: SoftLogoutViewModel by activityViewModel()
 
     private var isWebViewLoaded = false
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthWebFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthWebFragment.kt
index 879830a1c0a..b1f6de9d49d 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthWebFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthWebFragment.kt
@@ -56,6 +56,7 @@ class FtueAuthWebFragment @Inject constructor(
         private val assetReader: AssetReader
 ) : AbstractFtueAuthFragment() {
 
+    // TODO confirm the need of this viewModel
     val softLogoutViewModel: SoftLogoutViewModel by activityViewModel()
 
     override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginWebBinding {
diff --git a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt
index 52986a1f3bc..84b443a60f7 100644
--- a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt
@@ -16,7 +16,6 @@
 
 package im.vector.app.features.signout.soft
 
-import com.airbnb.mvrx.ActivityViewModelContext
 import com.airbnb.mvrx.Fail
 import com.airbnb.mvrx.Loading
 import com.airbnb.mvrx.MavericksViewModelFactory
@@ -26,8 +25,10 @@ import com.airbnb.mvrx.ViewModelContext
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
+import dagger.hilt.EntryPoints
 import im.vector.app.core.di.ActiveSessionHolder
 import im.vector.app.core.di.MavericksAssistedViewModelFactory
+import im.vector.app.core.di.SingletonEntryPoint
 import im.vector.app.core.di.hiltMavericksViewModelFactory
 import im.vector.app.core.extensions.hasUnsavedKeys
 import im.vector.app.core.platform.VectorViewModel
@@ -56,15 +57,23 @@ class SoftLogoutViewModel @AssistedInject constructor(
     companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() {
 
         override fun initialState(viewModelContext: ViewModelContext): SoftLogoutViewState? {
-            val activity: SoftLogoutActivity = (viewModelContext as ActivityViewModelContext).activity()
-            val userId = activity.session.myUserId
-            return SoftLogoutViewState(
-                    homeServerUrl = activity.session.sessionParams.homeServerUrl,
-                    userId = userId,
-                    deviceId = activity.session.sessionParams.deviceId ?: "",
-                    userDisplayName = activity.session.getUser(userId)?.displayName ?: userId,
-                    hasUnsavedKeys = activity.session.hasUnsavedKeys()
-            )
+            val sessionHolder = EntryPoints.get(viewModelContext.app(), SingletonEntryPoint::class.java)
+                    .activeSessionHolder()
+
+            return if (sessionHolder.hasActiveSession()) {
+                val session = sessionHolder.getActiveSession()
+                val userId = session.myUserId
+
+                SoftLogoutViewState(
+                        homeServerUrl = session.sessionParams.homeServerUrl,
+                        userId = userId,
+                        deviceId = session.sessionParams.deviceId.orEmpty(),
+                        userDisplayName = session.getUser(userId)?.displayName ?: userId,
+                        hasUnsavedKeys = session.hasUnsavedKeys()
+                )
+            } else {
+                null
+            }
         }
     }
 

From 8115b4b6e632b42284b9fb46032bc2f30a71296f Mon Sep 17 00:00:00 2001
From: Maxime Naturel 
Date: Fri, 18 Feb 2022 14:22:38 +0100
Subject: [PATCH 429/581] Removing unused Session recovery action from
 onboarding

---
 .../app/features/onboarding/OnboardingAction.kt   |  6 ------
 .../features/onboarding/OnboardingViewModel.kt    | 13 -------------
 .../onboarding/ftueauth/FtueAuthWebFragment.kt    | 15 +--------------
 3 files changed, 1 insertion(+), 33 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt
index 2ca6a1f2fd4..3ddea5ca2e2 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt
@@ -21,7 +21,6 @@ import im.vector.app.features.login.LoginConfig
 import im.vector.app.features.login.ServerType
 import im.vector.app.features.login.SignMode
 import org.matrix.android.sdk.api.auth.data.Credentials
-import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
 import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
 import org.matrix.android.sdk.internal.network.ssl.Fingerprint
 
@@ -71,11 +70,6 @@ sealed class OnboardingAction : VectorViewModelAction {
     // Homeserver history
     object ClearHomeServerHistory : OnboardingAction()
 
-    // For the soft logout case
-    data class SetupSsoForSessionRecovery(val homeServerUrl: String,
-                                          val deviceId: String,
-                                          val ssoIdentityProviders: List?) : OnboardingAction()
-
     data class PostViewEvent(val viewEvent: OnboardingViewEvents) : OnboardingAction()
 
     data class UserAcceptCertificate(val fingerprint: Fingerprint) : OnboardingAction()
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt
index a125c57ac94..d279c5bbe9e 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt
@@ -143,7 +143,6 @@ class OnboardingViewModel @AssistedInject constructor(
             is OnboardingAction.ResetPasswordMailConfirmed -> handleResetPasswordMailConfirmed()
             is OnboardingAction.RegisterAction             -> handleRegisterAction(action)
             is OnboardingAction.ResetAction                -> handleResetAction(action)
-            is OnboardingAction.SetupSsoForSessionRecovery -> handleSetupSsoForSessionRecovery(action)
             is OnboardingAction.UserAcceptCertificate      -> handleUserAcceptCertificate(action)
             OnboardingAction.ClearHomeServerHistory        -> handleClearHomeServerHistory()
             is OnboardingAction.PostViewEvent              -> _viewEvents.post(action.viewEvent)
@@ -249,18 +248,6 @@ class OnboardingViewModel @AssistedInject constructor(
         }
     }
 
-    private fun handleSetupSsoForSessionRecovery(action: OnboardingAction.SetupSsoForSessionRecovery) {
-        setState {
-            copy(
-                    signMode = SignMode.SignIn,
-                    loginMode = LoginMode.Sso(action.ssoIdentityProviders),
-                    homeServerUrlFromUser = action.homeServerUrl,
-                    homeServerUrl = action.homeServerUrl,
-                    deviceId = action.deviceId
-            )
-        }
-    }
-
     private fun handleRegisterAction(action: OnboardingAction.RegisterAction) {
         when (action) {
             is OnboardingAction.CaptchaDone                  -> handleCaptchaDone(action)
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthWebFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthWebFragment.kt
index b1f6de9d49d..4c99a4d1d80 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthWebFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthWebFragment.kt
@@ -30,7 +30,6 @@ import android.view.ViewGroup
 import android.webkit.SslErrorHandler
 import android.webkit.WebView
 import android.webkit.WebViewClient
-import com.airbnb.mvrx.activityViewModel
 import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import im.vector.app.R
 import im.vector.app.core.utils.AssetReader
@@ -40,8 +39,6 @@ import im.vector.app.features.login.SignMode
 import im.vector.app.features.onboarding.OnboardingAction
 import im.vector.app.features.onboarding.OnboardingViewEvents
 import im.vector.app.features.onboarding.OnboardingViewState
-import im.vector.app.features.signout.soft.SoftLogoutAction
-import im.vector.app.features.signout.soft.SoftLogoutViewModel
 import org.matrix.android.sdk.api.auth.data.Credentials
 import org.matrix.android.sdk.internal.di.MoshiProvider
 import timber.log.Timber
@@ -56,15 +53,11 @@ class FtueAuthWebFragment @Inject constructor(
         private val assetReader: AssetReader
 ) : AbstractFtueAuthFragment() {
 
-    // TODO confirm the need of this viewModel
-    val softLogoutViewModel: SoftLogoutViewModel by activityViewModel()
-
     override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginWebBinding {
         return FragmentLoginWebBinding.inflate(inflater, container, false)
     }
 
     private var isWebViewLoaded = false
-    private var isForSessionRecovery = false
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
@@ -76,8 +69,6 @@ class FtueAuthWebFragment @Inject constructor(
     override fun updateWithState(state: OnboardingViewState) {
         setupTitle(state)
 
-        isForSessionRecovery = state.deviceId?.isNotBlank() == true
-
         if (!isWebViewLoaded) {
             setupWebView(state)
             isWebViewLoaded = true
@@ -240,11 +231,7 @@ class FtueAuthWebFragment @Inject constructor(
     }
 
     private fun notifyViewModel(credentials: Credentials) {
-        if (isForSessionRecovery) {
-            softLogoutViewModel.handle(SoftLogoutAction.WebLoginSuccess(credentials))
-        } else {
-            viewModel.handle(OnboardingAction.WebLoginSuccess(credentials))
-        }
+        viewModel.handle(OnboardingAction.WebLoginSuccess(credentials))
     }
 
     override fun resetViewModel() {

From 7df5372d4d67a6020289c59c54b1d1aed3c3140e Mon Sep 17 00:00:00 2001
From: Maxime Naturel 
Date: Fri, 18 Feb 2022 14:22:51 +0100
Subject: [PATCH 430/581] Adding TODO to confirm

---
 .../java/im/vector/app/features/login/LoginWebFragment.kt   | 6 ++++--
 .../java/im/vector/app/features/login2/LoginWebFragment2.kt | 6 ++++--
 2 files changed, 8 insertions(+), 4 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/login/LoginWebFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginWebFragment.kt
index fc3392df097..e7c15445d0e 100644
--- a/vector/src/main/java/im/vector/app/features/login/LoginWebFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/login/LoginWebFragment.kt
@@ -51,8 +51,9 @@ class LoginWebFragment @Inject constructor(
         private val assetReader: AssetReader
 ) : AbstractLoginFragment() {
 
-    // TODO confirm the need of this viewModel
-    val softLogoutViewModel: SoftLogoutViewModel by activityViewModel()
+    // TODO I noticed in the Git history this variable was a local variable inside notifyViewModel method
+    // TODO was there any reason ? To be able to create this ViewModel we must be sure we will have a valid session
+    private val softLogoutViewModel: SoftLogoutViewModel by activityViewModel()
 
     override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginWebBinding {
         return FragmentLoginWebBinding.inflate(inflater, container, false)
@@ -71,6 +72,7 @@ class LoginWebFragment @Inject constructor(
     override fun updateWithState(state: LoginViewState) {
         setupTitle(state)
 
+        // TODO check how it is possible to arrive in this case
         isForSessionRecovery = state.deviceId?.isNotBlank() == true
 
         if (!isWebViewLoaded) {
diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginWebFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginWebFragment2.kt
index 6996cb552a2..17a271177ad 100644
--- a/vector/src/main/java/im/vector/app/features/login2/LoginWebFragment2.kt
+++ b/vector/src/main/java/im/vector/app/features/login2/LoginWebFragment2.kt
@@ -56,8 +56,9 @@ class LoginWebFragment2 @Inject constructor(
         return FragmentLoginWebBinding.inflate(inflater, container, false)
     }
 
-    // TODO confirm the need of this viewModel
-    val softLogoutViewModel: SoftLogoutViewModel by activityViewModel()
+    // TODO I noticed in the Git history this variable was a local variable inside notifyViewModel method
+    // TODO was there any reason ? To be able to create this ViewModel we must be sure we will have a valid session
+    private val softLogoutViewModel: SoftLogoutViewModel by activityViewModel()
 
     private var isWebViewLoaded = false
     private var isForSessionRecovery = false
@@ -72,6 +73,7 @@ class LoginWebFragment2 @Inject constructor(
     override fun updateWithState(state: LoginViewState2) {
         setupTitle(state)
 
+        // TODO check how it is possible to arrive in this case
         isForSessionRecovery = state.deviceId?.isNotBlank() == true
 
         if (!isWebViewLoaded) {

From bfc6cd04a65134dc40b571bb49412d86667708cb Mon Sep 17 00:00:00 2001
From: Maxime Naturel 
Date: Fri, 18 Feb 2022 14:23:31 +0100
Subject: [PATCH 431/581] Updating changelog entry

---
 changelog.d/5218.bugfix | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/changelog.d/5218.bugfix b/changelog.d/5218.bugfix
index 93aece938ca..4f92338a4f4 100644
--- a/changelog.d/5218.bugfix
+++ b/changelog.d/5218.bugfix
@@ -1 +1 @@
-Fix crash during registration when redirecting to Web Browser
+Fix crash during account registration when redirecting to Web View

From cb9df953fb46dc02dbd60de98607425497c1590c Mon Sep 17 00:00:00 2001
From: Maxime Naturel 
Date: Fri, 18 Feb 2022 15:00:52 +0100
Subject: [PATCH 432/581] Removing some TODOs

---
 .../main/java/im/vector/app/features/login/LoginWebFragment.kt   | 1 -
 .../main/java/im/vector/app/features/login2/LoginWebFragment2.kt | 1 -
 2 files changed, 2 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/login/LoginWebFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginWebFragment.kt
index e7c15445d0e..f1558518f72 100644
--- a/vector/src/main/java/im/vector/app/features/login/LoginWebFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/login/LoginWebFragment.kt
@@ -72,7 +72,6 @@ class LoginWebFragment @Inject constructor(
     override fun updateWithState(state: LoginViewState) {
         setupTitle(state)
 
-        // TODO check how it is possible to arrive in this case
         isForSessionRecovery = state.deviceId?.isNotBlank() == true
 
         if (!isWebViewLoaded) {
diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginWebFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginWebFragment2.kt
index 17a271177ad..4e28d8e56c7 100644
--- a/vector/src/main/java/im/vector/app/features/login2/LoginWebFragment2.kt
+++ b/vector/src/main/java/im/vector/app/features/login2/LoginWebFragment2.kt
@@ -73,7 +73,6 @@ class LoginWebFragment2 @Inject constructor(
     override fun updateWithState(state: LoginViewState2) {
         setupTitle(state)
 
-        // TODO check how it is possible to arrive in this case
         isForSessionRecovery = state.deviceId?.isNotBlank() == true
 
         if (!isWebViewLoaded) {

From 66b30c33c83d6c58c0bedcafcae6072d62cb5352 Mon Sep 17 00:00:00 2001
From: Michael Kaye <1917473+michaelkaye@users.noreply.github.com>
Date: Fri, 18 Feb 2022 14:38:22 +0000
Subject: [PATCH 433/581] Improve reliability of sanity tests.

We add the permission so we can write to the external storage with the screenshots
We rename the screenshots so they can be uploaded via the github action correctly

We always do the upload even if the test build has failed.
---
 .github/workflows/sanity_test.yml                              | 3 ++-
 .../java/im/vector/app/espresso/tools/ScreenshotFailureRule.kt | 2 +-
 .../java/im/vector/app/ui/UiAllScreensSanityTest.kt            | 3 +++
 3 files changed, 6 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/sanity_test.yml b/.github/workflows/sanity_test.yml
index 93e4686fe74..483926fa1fb 100644
--- a/.github/workflows/sanity_test.yml
+++ b/.github/workflows/sanity_test.yml
@@ -69,9 +69,10 @@ jobs:
             touch emulator.log
             chmod 777 emulator.log
             adb logcat >> emulator.log &
-            ./gradlew $CI_GRADLE_ARG_PROPERTIES -PallWarningsAsErrors=false connectedGplayDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=im.vector.app.ui.UiAllScreensSanityTest || adb pull storage/emulated/0/Pictures/failure_screenshots
+            ./gradlew $CI_GRADLE_ARG_PROPERTIES -PallWarningsAsErrors=false connectedGplayDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=im.vector.app.ui.UiAllScreensSanityTest || (adb pull storage/emulated/0/Pictures/failure_screenshots && exit 1 )
       - name: Upload Test Report Log
         uses: actions/upload-artifact@v2
+        if: always()
         with:
           name: sanity-error-results
           path: |
diff --git a/vector/src/androidTest/java/im/vector/app/espresso/tools/ScreenshotFailureRule.kt b/vector/src/androidTest/java/im/vector/app/espresso/tools/ScreenshotFailureRule.kt
index 2e329ebb6bc..2939dcf4e03 100644
--- a/vector/src/androidTest/java/im/vector/app/espresso/tools/ScreenshotFailureRule.kt
+++ b/vector/src/androidTest/java/im/vector/app/espresso/tools/ScreenshotFailureRule.kt
@@ -40,7 +40,7 @@ private val deviceLanguage = Locale.getDefault().language
 
 class ScreenshotFailureRule : TestWatcher() {
     override fun failed(e: Throwable?, description: Description) {
-        val screenShotName = "$deviceLanguage-${description.methodName}-${SimpleDateFormat("EEE-MMMM-dd-HH:mm:ss").format(Date())}"
+        val screenShotName = "$deviceLanguage-${description.methodName}-${SimpleDateFormat("EEE-MMMM-dd-HHmmss").format(Date())}"
         val bitmap = getInstrumentation().uiAutomation.takeScreenshot()
         storeFailureScreenshot(bitmap, screenShotName)
     }
diff --git a/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt b/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt
index bab397678e5..417d28d6252 100644
--- a/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt
+++ b/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt
@@ -16,10 +16,12 @@
 
 package im.vector.app.ui
 
+import android.Manifest
 import androidx.test.espresso.IdlingPolicies
 import androidx.test.ext.junit.rules.ActivityScenarioRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
+import androidx.test.rule.GrantPermissionRule
 import im.vector.app.R
 import im.vector.app.espresso.tools.ScreenshotFailureRule
 import im.vector.app.features.MainActivity
@@ -43,6 +45,7 @@ class UiAllScreensSanityTest {
     @get:Rule
     val testRule = RuleChain
             .outerRule(ActivityScenarioRule(MainActivity::class.java))
+            .around(GrantPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE))
             .around(ScreenshotFailureRule())
 
     private val elementRobot = ElementRobot()

From 95b3afd1488b046b3d36e99a8ed91d11a710778a Mon Sep 17 00:00:00 2001
From: ClaireG 
Date: Fri, 18 Feb 2022 16:45:01 +0100
Subject: [PATCH 434/581] #3771: When accepting an invite, expecting to see the
 room (#5247)

Open the room when user accepts an invite from the room list
---
 changelog.d/3771.feature                      |  1 +
 .../home/room/detail/RoomDetailViewEvents.kt  |  2 +-
 .../home/room/detail/RoomDetailViewState.kt   |  2 ++
 .../home/room/detail/TimelineFragment.kt      |  8 +++++---
 .../home/room/detail/TimelineViewModel.kt     | 19 +++++++++++++++++--
 .../room/detail/arguments/TimelineArgs.kt     |  3 ++-
 .../home/room/list/RoomListFragment.kt        |  6 +++---
 .../home/room/list/RoomListViewEvents.kt      |  2 +-
 .../home/room/list/RoomListViewModel.kt       | 16 ++--------------
 .../features/navigation/DefaultNavigator.kt   |  5 +++--
 .../app/features/navigation/Navigator.kt      |  2 +-
 11 files changed, 38 insertions(+), 28 deletions(-)
 create mode 100644 changelog.d/3771.feature

diff --git a/changelog.d/3771.feature b/changelog.d/3771.feature
new file mode 100644
index 00000000000..c480bb649d7
--- /dev/null
+++ b/changelog.d/3771.feature
@@ -0,0 +1 @@
+Open the room when user accepts an invite from the room list
\ No newline at end of file
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt
index 86240a5ffe3..d08a27324cc 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt
@@ -29,7 +29,7 @@ import java.io.File
  * Transient events for RoomDetail
  */
 sealed class RoomDetailViewEvents : VectorViewEvents {
-    data class Failure(val throwable: Throwable) : RoomDetailViewEvents()
+    data class Failure(val throwable: Throwable, val showInDialog: Boolean = false) : RoomDetailViewEvents()
     data class OnNewTimelineEvents(val eventIds: List) : RoomDetailViewEvents()
 
     data class ActionSuccess(val action: RoomDetailAction) : RoomDetailViewEvents()
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 22d5fc2a773..71a299e11bf 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
@@ -49,6 +49,7 @@ data class JitsiState(
 data class RoomDetailViewState(
         val roomId: String,
         val eventId: String?,
+        val isInviteAlreadyAccepted: Boolean,
         val myRoomMember: Async = Uninitialized,
         val asyncInviter: Async = Uninitialized,
         val asyncRoomSummary: Async = Uninitialized,
@@ -77,6 +78,7 @@ data class RoomDetailViewState(
     constructor(args: TimelineArgs) : this(
             roomId = args.roomId,
             eventId = args.eventId,
+            isInviteAlreadyAccepted = args.isInviteAlreadyAccepted,
             // Also highlight the target event, if any
             highlightedEventId = args.eventId,
             switchToParentSpace = args.switchToParentSpace,
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 ff392eaf1ba..b6cbd538f32 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
@@ -448,7 +448,7 @@ class TimelineFragment @Inject constructor(
 
         timelineViewModel.observeViewEvents {
             when (it) {
-                is RoomDetailViewEvents.Failure                          -> showErrorInSnackbar(it.throwable)
+                is RoomDetailViewEvents.Failure                          -> displayErrorMessage(it)
                 is RoomDetailViewEvents.OnNewTimelineEvents              -> scrollOnNewMessageCallback.addNewTimelineEventIds(it.eventIds)
                 is RoomDetailViewEvents.ActionSuccess                    -> displayRoomDetailActionSuccess(it)
                 is RoomDetailViewEvents.ActionFailure                    -> displayRoomDetailActionFailure(it)
@@ -623,6 +623,10 @@ class TimelineFragment @Inject constructor(
                 )
     }
 
+    private fun displayErrorMessage(error: RoomDetailViewEvents.Failure) {
+        if (error.showInDialog) displayErrorDialog(error.throwable) else showErrorInSnackbar(error.throwable)
+    }
+
     private fun requestNativeWidgetPermission(it: RoomDetailViewEvents.RequestNativeWidgetPermission) {
         val tag = RoomWidgetPermissionBottomSheet::class.java.name
         val dFrag = childFragmentManager.findFragmentByTag(tag) as? RoomWidgetPermissionBottomSheet
@@ -2374,12 +2378,10 @@ class TimelineFragment @Inject constructor(
 
     // VectorInviteView.Callback
     override fun onAcceptInvite() {
-        notificationDrawerManager.updateEvents { it.clearMemberShipNotificationForRoom(timelineArgs.roomId) }
         timelineViewModel.handle(RoomDetailAction.AcceptInvite)
     }
 
     override fun onRejectInvite() {
-        notificationDrawerManager.updateEvents { it.clearMemberShipNotificationForRoom(timelineArgs.roomId) }
         timelineViewModel.handle(RoomDetailAction.RejectInvite)
     }
 
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 a404f9136b8..0198c77280f 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
@@ -53,6 +53,7 @@ import im.vector.app.features.home.room.detail.sticker.StickerPickerActionHandle
 import im.vector.app.features.home.room.detail.timeline.factory.TimelineFactory
 import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever
 import im.vector.app.features.home.room.typing.TypingHelper
+import im.vector.app.features.notifications.NotificationDrawerManager
 import im.vector.app.features.powerlevel.PowerLevelsFlowFactory
 import im.vector.app.features.session.coroutineScope
 import im.vector.app.features.settings.VectorDataStore
@@ -123,6 +124,7 @@ class TimelineViewModel @AssistedInject constructor(
         private val analyticsTracker: AnalyticsTracker,
         private val activeConferenceHolder: JitsiActiveConferenceHolder,
         private val decryptionFailureTracker: DecryptionFailureTracker,
+        private val notificationDrawerManager: NotificationDrawerManager,
         timelineFactory: TimelineFactory,
         appStateHandler: AppStateHandler
 ) : VectorViewModel(initialState),
@@ -190,6 +192,11 @@ class TimelineViewModel @AssistedInject constructor(
             prepareForEncryption()
         }
 
+        // If the user had already accepted the invitation in the room list
+        if (initialState.isInviteAlreadyAccepted) {
+            handleAcceptInvite()
+        }
+
         if (initialState.switchToParentSpace) {
             // We are coming from a notification, try to switch to the most relevant space
             // so that when hitting back the room will appear in the list
@@ -800,16 +807,24 @@ class TimelineViewModel @AssistedInject constructor(
     }
 
     private fun handleRejectInvite() {
+        notificationDrawerManager.updateEvents { it.clearMemberShipNotificationForRoom(initialState.roomId) }
         viewModelScope.launch {
-            tryOrNull { session.leaveRoom(room.roomId) }
+            try {
+               session.leaveRoom(room.roomId)
+            } catch (throwable: Throwable) {
+                _viewEvents.post(RoomDetailViewEvents.Failure(throwable, showInDialog = true))
+            }
         }
     }
 
     private fun handleAcceptInvite() {
+        notificationDrawerManager.updateEvents { it.clearMemberShipNotificationForRoom(initialState.roomId) }
         viewModelScope.launch {
-            tryOrNull {
+            try {
                 session.joinRoom(room.roomId)
                 analyticsTracker.capture(room.roomSummary().toAnalyticsJoinedRoom())
+            } catch (throwable: Throwable) {
+                _viewEvents.post(RoomDetailViewEvents.Failure(throwable, showInDialog = true))
             }
         }
     }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/arguments/TimelineArgs.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/arguments/TimelineArgs.kt
index f22fe1b7dfb..a21567acb17 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/arguments/TimelineArgs.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/arguments/TimelineArgs.kt
@@ -28,5 +28,6 @@ data class TimelineArgs(
         val sharedData: SharedData? = null,
         val openShareSpaceForId: String? = null,
         val threadTimelineArgs: ThreadTimelineArgs? = null,
-        val switchToParentSpace: Boolean = false
+        val switchToParentSpace: Boolean = false,
+        val isInviteAlreadyAccepted: Boolean = false
 ) : Parcelable
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 b023c26590b..28849204c40 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
@@ -121,7 +121,7 @@ class RoomListFragment @Inject constructor(
             when (it) {
                 is RoomListViewEvents.Loading                   -> showLoading(it.message)
                 is RoomListViewEvents.Failure                   -> showFailure(it.throwable)
-                is RoomListViewEvents.SelectRoom                -> handleSelectRoom(it)
+                is RoomListViewEvents.SelectRoom                -> handleSelectRoom(it, it.isInviteAlreadyAccepted)
                 is RoomListViewEvents.Done                      -> Unit
                 is RoomListViewEvents.NavigateToMxToBottomSheet -> handleShowMxToLink(it.link)
             }.exhaustive
@@ -184,8 +184,8 @@ class RoomListFragment @Inject constructor(
         super.onDestroyView()
     }
 
-    private fun handleSelectRoom(event: RoomListViewEvents.SelectRoom) {
-        navigator.openRoom(requireActivity(), event.roomSummary.roomId)
+    private fun handleSelectRoom(event: RoomListViewEvents.SelectRoom, isInviteAlreadyAccepted: Boolean) {
+        navigator.openRoom(context = requireActivity(), roomId = event.roomSummary.roomId, isInviteAlreadyAccepted = isInviteAlreadyAccepted)
     }
 
     private fun setupCreateRoomButton() {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewEvents.kt
index df2ff58da6f..15e16c464f7 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewEvents.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewEvents.kt
@@ -27,7 +27,7 @@ sealed class RoomListViewEvents : VectorViewEvents {
     data class Loading(val message: CharSequence? = null) : RoomListViewEvents()
     data class Failure(val throwable: Throwable) : RoomListViewEvents()
 
-    data class SelectRoom(val roomSummary: RoomSummary) : RoomListViewEvents()
+    data class SelectRoom(val roomSummary: RoomSummary, val isInviteAlreadyAccepted: Boolean = false) : RoomListViewEvents()
     object Done : RoomListViewEvents()
     data class NavigateToMxToBottomSheet(val link: String) : RoomListViewEvents()
 }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt
index a5977501d2b..4a81a8b5265 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt
@@ -33,7 +33,6 @@ import im.vector.app.core.extensions.exhaustive
 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.extensions.toAnalyticsJoinedRoom
 import im.vector.app.features.displayname.getBestName
 import im.vector.app.features.invite.AutoAcceptInvites
 import im.vector.app.features.settings.VectorPreferences
@@ -174,7 +173,7 @@ class RoomListViewModel @AssistedInject constructor(
     // PRIVATE METHODS *****************************************************************************
 
     private fun handleSelectRoom(action: RoomListAction.SelectRoom) = withState {
-        _viewEvents.post(RoomListViewEvents.SelectRoom(action.roomSummary))
+        _viewEvents.post(RoomListViewEvents.SelectRoom(action.roomSummary, false))
     }
 
     private fun handleToggleSection(roomSection: RoomsSection) {
@@ -208,6 +207,7 @@ class RoomListViewModel @AssistedInject constructor(
             Timber.w("Try to join an already joining room. Should not happen")
             return@withState
         }
+        _viewEvents.post(RoomListViewEvents.SelectRoom(action.roomSummary, true))
 
         // quick echo
         setState {
@@ -221,18 +221,6 @@ class RoomListViewModel @AssistedInject constructor(
                     }
             )
         }
-
-        viewModelScope.launch {
-            try {
-                session.joinRoom(roomId)
-                analyticsTracker.capture(action.roomSummary.toAnalyticsJoinedRoom())
-                // We do not update the joiningRoomsIds here, because, the room is not joined yet regarding the sync data.
-                // Instead, we wait for the room to be joined
-            } catch (failure: Throwable) {
-                // Notify the user
-                _viewEvents.post(RoomListViewEvents.Failure(failure))
-            }
-        }
     }
 
     private fun handleRejectInvitation(action: RoomListAction.RejectInvitation) = withState { state ->
diff --git a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt
index b521710c1ed..cc02687d93b 100644
--- a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt
+++ b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt
@@ -147,13 +147,14 @@ class DefaultNavigator @Inject constructor(
             context: Context,
             roomId: String,
             eventId: String?,
-            buildTask: Boolean
+            buildTask: Boolean,
+            isInviteAlreadyAccepted: Boolean
     ) {
         if (sessionHolder.getSafeActiveSession()?.getRoom(roomId) == null) {
             fatalError("Trying to open an unknown room $roomId", vectorPreferences.failFast())
             return
         }
-        val args = TimelineArgs(roomId, eventId)
+        val args = TimelineArgs(roomId = roomId, eventId = eventId, isInviteAlreadyAccepted = isInviteAlreadyAccepted)
         val intent = RoomDetailActivity.newIntent(context, args)
         startActivity(context, intent, buildTask)
     }
diff --git a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt
index b5e94241ced..a31dc8fb897 100644
--- a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt
+++ b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt
@@ -50,7 +50,7 @@ interface Navigator {
 
     fun softLogout(context: Context)
 
-    fun openRoom(context: Context, roomId: String, eventId: String? = null, buildTask: Boolean = false)
+    fun openRoom(context: Context, roomId: String, eventId: String? = null, buildTask: Boolean = false, isInviteAlreadyAccepted: Boolean = false)
 
     sealed class PostSwitchSpaceAction {
         object None : PostSwitchSpaceAction()

From d29fc9f2d343f93c5a8c586743abd6a637ce54e7 Mon Sep 17 00:00:00 2001
From: Maxime Naturel 
Date: Fri, 18 Feb 2022 17:04:02 +0100
Subject: [PATCH 435/581] Replacing vctr_unread_room_badge by
 vctr_content_secondary

---
 library/ui-styles/src/main/res/values/colors.xml             | 5 -----
 library/ui-styles/src/main/res/values/theme_black.xml        | 1 -
 library/ui-styles/src/main/res/values/theme_dark.xml         | 1 -
 library/ui-styles/src/main/res/values/theme_light.xml        | 1 -
 .../java/im/vector/app/features/home/HomeDetailFragment.kt   | 2 +-
 vector/src/main/res/drawable/bg_unread_notification.xml      | 4 ++--
 6 files changed, 3 insertions(+), 11 deletions(-)

diff --git a/library/ui-styles/src/main/res/values/colors.xml b/library/ui-styles/src/main/res/values/colors.xml
index 48ac48a8ca3..770b0018935 100644
--- a/library/ui-styles/src/main/res/values/colors.xml
+++ b/library/ui-styles/src/main/res/values/colors.xml
@@ -57,11 +57,6 @@
     
 
     
-    
-    @color/palette_gray_200
-    @color/palette_gray_250
-    @color/palette_gray_250
-
     
     @android:color/white
     #FF181B21
diff --git a/library/ui-styles/src/main/res/values/theme_black.xml b/library/ui-styles/src/main/res/values/theme_black.xml
index c472a4fae51..44d4206d437 100644
--- a/library/ui-styles/src/main/res/values/theme_black.xml
+++ b/library/ui-styles/src/main/res/values/theme_black.xml
@@ -7,7 +7,6 @@
         
 
         
-        @color/vctr_unread_room_badge_black
         @color/vctr_fab_label_bg_black
         @color/vctr_fab_label_stroke_black
         @color/vctr_fab_label_color_black
diff --git a/library/ui-styles/src/main/res/values/theme_dark.xml b/library/ui-styles/src/main/res/values/theme_dark.xml
index b1d95c54399..100a07f41d3 100644
--- a/library/ui-styles/src/main/res/values/theme_dark.xml
+++ b/library/ui-styles/src/main/res/values/theme_dark.xml
@@ -16,7 +16,6 @@
         @color/element_system_dark
 
         
-        @color/vctr_unread_room_badge_dark
         @color/vctr_fab_label_bg_dark
         @color/vctr_fab_label_stroke_dark
         @color/vctr_fab_label_color_dark
diff --git a/library/ui-styles/src/main/res/values/theme_light.xml b/library/ui-styles/src/main/res/values/theme_light.xml
index dba39c97cae..39e78ee5b1e 100644
--- a/library/ui-styles/src/main/res/values/theme_light.xml
+++ b/library/ui-styles/src/main/res/values/theme_light.xml
@@ -16,7 +16,6 @@
         @color/element_system_light
 
         
-        @color/vctr_unread_room_badge_light
         @color/vctr_fab_label_bg_light
         @color/vctr_fab_label_stroke_light
         @color/vctr_fab_label_color_light
diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt
index a07409d063d..ea03b833ac9 100644
--- a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt
@@ -457,7 +457,7 @@ class HomeDetailFragment @Inject constructor(
         backgroundColor = if (highlight) {
             ThemeUtils.getColor(requireContext(), R.attr.colorError)
         } else {
-            ThemeUtils.getColor(requireContext(), R.attr.vctr_unread_room_badge)
+            ThemeUtils.getColor(requireContext(), R.attr.vctr_content_secondary)
         }
     }
 
diff --git a/vector/src/main/res/drawable/bg_unread_notification.xml b/vector/src/main/res/drawable/bg_unread_notification.xml
index 7c9ea18eec8..4926f588dae 100644
--- a/vector/src/main/res/drawable/bg_unread_notification.xml
+++ b/vector/src/main/res/drawable/bg_unread_notification.xml
@@ -4,5 +4,5 @@
 
     
 
-    
-
\ No newline at end of file
+    
+

From 4713fd96bf5e291b19f1cdfc4e8e4d677dbfbaf3 Mon Sep 17 00:00:00 2001
From: Maxime Naturel 
Date: Fri, 18 Feb 2022 17:05:29 +0100
Subject: [PATCH 436/581] Adding changelog entry

---
 changelog.d/5225.misc | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 changelog.d/5225.misc

diff --git a/changelog.d/5225.misc b/changelog.d/5225.misc
new file mode 100644
index 00000000000..799a3a4d814
--- /dev/null
+++ b/changelog.d/5225.misc
@@ -0,0 +1 @@
+Replacing color "vctr_unread_room_badge" by "vctr_content_secondary"

From 468220cde7b9741fc3028203b557126e74a26f2d Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Fri, 18 Feb 2022 19:35:08 +0000
Subject: [PATCH 437/581] Translated using Weblate (Japanese)

Currently translated at 77.4% (2158 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 91 ++++++++++++-----------
 1 file changed, 46 insertions(+), 45 deletions(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index cd9eb82f92c..24eb67c67a5 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -183,9 +183,9 @@
     招待
     このルームを退出
     検索
-    %sさんが文字入力しています…
-    %1$sさんと %2$sさんが文字入力しています…
-    %1$sさん、%2$sさん他が文字入力しています…
+    %sさんが文字を入力しています…
+    %1$sさんと %2$sさんが文字を入力しています…
+    %1$sさん、%2$sさん他が文字を入力しています…
     暗号文を送信…
     明るいテーマ
     暗いテーマ
@@ -214,7 +214,7 @@
     権限を司会者へ変更
     権限を管理者へ変更
     ここに送信文を入力 (暗号なし)…
-    サーバーとの接続が失われました.
+    サーバーとの接続が失われました。
     全て再送信
     未送信の文を再送信
     未送信の文を削除
@@ -252,7 +252,7 @@
     画像等を一時保存する期間
     利用者設定
     通知
-    無視する相手
+    無視しているユーザー
     その他
     拡張設定
     暗号化
@@ -264,7 +264,7 @@
     全てのメッセージにタイムスタンプを表示
     タイムスタンプを12時間形式で表示
     セッションの詳細
-    ID(端末固有番号)
+    ID(端末固有番号)
     公開端末名
     公開端末名の更新
     最終接続日
@@ -302,7 +302,7 @@
     低優先度
     なし
     参加と可視範囲
-    ルームの一覧へ公開
+    ルームディレクトリへ公開
     ルームへの参加
     ルームの履歴の可視範囲
     ルームの履歴を読める人は\?
@@ -364,8 +364,8 @@
     端末の電話帳を${app_name}アプリが読み取ることは許可されていません
     結果なし
     ルーム
-    ルーム一覧
-    利用者一覧
+    ルームディレクトリ
+    ユーザーディレクトリ
     送信
     サインアウト
     アカウントを作成
@@ -402,7 +402,7 @@
     電子メールアドレスの確認に失敗しました: 電子メールのリンクをクリックしたことを確認してください
     パスワードがリセットされました。
 \n
-\n全てのセッションからログアウトしたため、プッシュ通知を受け取れなくなりました。通知を再び有効にするには、各デバイスで再ログインをお願いします。
+\n全てのセッションからログアウトしたため、プッシュ通知を受け取れなくなりました。通知を再び有効にするには、各端末で再ログインをお願いします。
     登録ができません : 電子メールがあなた個人のものであるか確認できません
     指定されたアクセストークンが認識されませんでした
     不正な形式のJSON
@@ -448,7 +448,7 @@
     参加者
     ファイル
     ルーム
-    ルーム一覧を見る
+    ディレクトリを見る
     会話から退出
     会話
     設定
@@ -479,7 +479,7 @@
     このルームは暗号化されていません。
     暗号化を有効にします
 \n(警告: 有効後にこれを無効にすることはできません!)
-    ルーム一覧
+    ディレクトリ
     外観
     エンドツーエンド暗号化についての情報
     公開端末名
@@ -537,13 +537,13 @@
 \n続行する前に、各セッションの検証プロセスを進めることをおすすめしますが、検証せずにメッセージを再送信することもできます。
 \n
 \n不明なセッション:
-    ルームに不明なデバイスが含まれています
+    ルームに不明な端末が含まれています
     キーが一致していることを確認
-    一致する場合は、下の確認ボタンを押します。そうでない場合は、他の誰かがこのデバイスを盗聴しているので、代わりにブロックボタンを押すことをおすすめします。今後この検証プロセスはより洗練されたものになるでしょう。
-    デバイスの検証
-    不明なデバイス
+    一致する場合は、下の確認ボタンを押します。そうでない場合は、他の誰かがこの端末を盗聴しているので、代わりにブロックボタンを押すことをおすすめします。今後この検証プロセスはより洗練されたものになるでしょう。
+    端末の検証
+    不明な端末
     このセッションでは、未検証のセッションに対して暗号化されたメッセージを送信しない
-    認証済みデバイスに対してのみ暗号化
+    認証済み端末に対してのみ暗号化
     インポート
     ローカルファイルからキーをインポート
     ルームキーをインポート
@@ -617,7 +617,7 @@
     復号エラー
     発信者装置の情報
     公開端末名
-    デバイスキー
+    端末キー
     Ed25519 フィンガープリント
     未認証
     ブラックリスト
@@ -626,14 +626,14 @@
     認証を取り消す
     ブラックリスト
     ブラックリストから除外
-    このデバイスが信頼できることを確認するために、その所有者に何らかの他の方法(直接会って、または、電話で)連絡し、以下のキーが、そのデバイスの「設定」で確認できるキーと一致するかお尋ねください:
+    この端末が信頼できることを確認するために、その所有者に何らかの他の方法(直接会って、または、電話で)連絡し、以下のキーが、その端末の「設定」で確認できるキーと一致するかお尋ねください:
     ルームのディレクトリを選択
     公開のルームを表示するホームサーバーを入力してください
     サーバー名
     %sサーバー上の全てのルーム
     全てのローカルの %s ルーム
     メッセージが未送信です。今 %1$s または %2$s しますか?
-    不明なデバイスが存在しているため、メッセージを送ることができませんでした。今 %1$s または %2$s しますか?
+    不明な端末が存在しているため、メッセージを送ることができませんでした。今 %1$s または %2$s しますか?
     要求されたフィンガープリントキー Ed25519
     jitsiを用いて会議通話を始める
     端末のカメラを使う
@@ -739,10 +739,10 @@
 \n追加しますか?
     続行する…
     申し訳ありません、この操作を完了するための外部アプリが見つかりません。
-    他のデバイスから 暗号鍵を再度要求 します。
+    他の端末から 暗号鍵を再度要求 します。
     鍵のリクエストが送信されました。
     リクエスト送信済
-    鍵をこのデバイスに送信できるように、メッセージを復号化できる他のデバイスで${app_name}を起動してください。
+    鍵をこの端末に送信できるように、メッセージを復号化できる他の端末で${app_name}を起動してください。
     
         %d秒
     
@@ -877,7 +877,7 @@
     サービスを初期化
     鍵のバックアップ
     鍵のバックアップを使用
-    デバイスを認証
+    端末を認証
     鍵のバックアップが終了していません。しばらくお待ちください…
     会議通話中。
 \n%1$s または %2$s として参加
@@ -890,7 +890,7 @@
     バックグラウンド同期を行わない
     優先同期間隔
     %s
-\n同期は、デバイスのリソース (バッテリ残量) または状態 (スリープ) に応じて延期される場合があります。
+\n同期は、端末のリソース (バッテリ残量) または状態 (スリープ) に応じて延期される場合があります。
     文字入力中通知を送信
     文字入力中であることを他の参加者に伝えます。
     開封確認メッセージを表示
@@ -911,7 +911,7 @@
     パスワード
     今ここでサインアウトすると、あなたの暗号化されたメッセージは失われてしまいます
     鍵のバックアップは現在処理中です。処理中にサインアウトすると暗号化されたメッセージにアクセスできなくなります。
-    暗号化されたメッセージにアクセスできなくなることを防ぐため、鍵の安全なバックアップはあなたのデバイス全てで有効化してください。
+    暗号化されたメッセージにアクセスできなくなることを防ぐため、鍵の安全なバックアップはあなたの端末全てで有効化してください。
     暗号化されたメッセージは不要です
     鍵をバックアップしています…
     鍵のバックアップを使用
@@ -954,8 +954,8 @@
     名前
     公開
     誰でもこのルームに参加できるようになります
-    ルーム一覧
-    ルームの一覧へ公開
+    ルームディレクトリ
+    ルームディレクトリへ公開
     一般
     セキュリティーとプライバシー
     ヘルプと概要
@@ -1105,7 +1105,7 @@
     他の利用可能な言語
     メッセージエディタ
     環境設定
-    このデバイスを設定
+    この端末を設定
     セキュアバックアップをリセット
     セキュアバックアップを設定
     管理
@@ -1115,7 +1115,7 @@
     %s からの招待
     このセッションは正常に検証されました。
     概ね完了しました。%s の画面にも同じシールドアイコンが表示されていますか?
-    相手ユーザーのデバイスのコードをスキャンし、相互に安全性を検証します
+    相手ユーザーの端末のコードをスキャンし、相互に安全性を検証
     相手のコードをスキャン
     スキャンできません
     断る
@@ -1140,7 +1140,7 @@
     ヘッドセット
     スピーカー
     電話
-    サウンドデバイスを選択
+    サウンド端末を選択
     リアルタイム接続を確立できませんでした。
 \nホームサーバーの管理者に、通話が正常に動作するためにTURNを設定するようご連絡ください。
     再び表示しない
@@ -1268,7 +1268,7 @@
     通話が確実に機能させるためには、ホームサーバー(%1$s)の管理者にTURNサーバーの設定を依頼してください。
 \n
 \n代わりに、%2$sのパブリックサーバーを使用することもできますが、信頼性は低く、あなたのIPアドレスがそのサーバーと共有されてしまいます。これは設定から管理することができます。
-    ルームのディレクトリ内の全てのルームを表示(露骨なコンテンツのあるルームを含む)する。
+    ルームディレクトリの全てのルームを表示(露骨なコンテンツのあるルームを含む)する。
     露骨なコンテンツのあるルームを表示
     ルームディレクトリ
     新着情報
@@ -1473,28 +1473,28 @@
     アプリがバックグラウンドにある場合、着信メッセージは通知されません。
     ${app_name}は正確な時間に定期的にバックグラウンドで同期します(構成可能)。
 \nこれはラジオとバッテリーの使用量に影響し$ {app_name}がイベントをリッスンしていることを示す永続的な通知が表示されます。
-    ${app_name}は、デバイスの限られたリソース(バッテリー)を維持する方法でバックグラウンド同期をします。
-\nデバイスの状態によっては、OSによって同期が延期される場合があります。
+    ${app_name}は、端末の限られたリソース(バッテリー)を維持する方法でバックグラウンド同期をします。
+\n端末の状態によっては、OSによって同期が延期される場合があります。
     LEDの色、振動、音を選択してください…
     サイレント通知を設定
     通話の通知を設定
     うるさい通知を設定
     アプリはバックグラウンドでホームサーバーに接続する必要がないためバッテリー使用量を減らすことができます
     最適化を無視
-    画面をオフにした状態でデバイスのプラグを抜いて一定時間静止したままにすると、デバイスは機内モードになります。 これにより、アプリがネットワークにアクセスできなくなり、ジョブ、同期、および標準のアラームが防止されます。
+    画面をオフにした状態で端末のプラグを抜いて一定時間静止したままにすると、端末は機内モードになります。 これにより、アプリがネットワークにアクセスできなくなり、ジョブ、同期、および標準のアラームが防止されます。
     ${app_name}に対してバックグラウンド制限が有効になっています。
 \nアプリがバックグラウンドで実行しようとすると積極的に制限され、通知に影響を与える可能性があります。
 \n%1$s
     ${app_name}のバックグラウンド制限は無効になっています。 このテストは、モバイル(WIFIでない)を使用して実行する必要があります。
 \n%1$s
-    デバイスを再起動してもサービスは開始されません。${app_name}が一度開かれるまで通知は届きません。
+    端末を再起動してもサービスは開始されません。${app_name}が一度開かれるまで通知は届きません。
     %1$s
-\nこのエラーは${app_name}の管理外です。 これはいくつかの理由で発生する可能性があります。 後で再試行すればうまくいくかもしれません。システム設定でGoogle Play Serviceのデータ使用量が制限されていないか、デバイスの時刻が正しいかどうか、もしくはカスタムROMで起こることがあります。
+\nこのエラーは${app_name}の管理外です。 これはいくつかの理由で発生する可能性があります。 後で再試行すればうまくいくかもしれません。システム設定でGoogle Play Serviceのデータ使用量が制限されていないか、端末の時刻が正しいかどうか、もしくはカスタムROMで起こることがあります。
     ${app_name}モバイルからこれを行うことはできません
     ${app_name}はバッテリー最適化の影響を受けません。
     制限を無効にする
     起動時の開始を有効にする
-    デバイスを再起動するとサービスが開始されます。
+    端末を再起動するとサービスが開始されます。
     サービスの再起動に失敗しました
     サービスが強制終了され、自動的に再起動されました。
     通知サービスの自動再起動
@@ -1514,7 +1514,7 @@
     [%1$s]
 \nこのエラーは、${app_name}の管理外です。スマホにはGoogleアカウントがありません。アカウントマネージャーを開いて、Googleアカウントを追加してください。
     %1$s
-\nこのエラーは${app_name}の管理外であり、Googleによると、このエラーは、デバイスにFCMに登録されているアプリが多すぎることを示しています。 このエラーは、アプリの数が極端に多い場合にのみ発生するため、平均的なユーザーには影響しません。
+\nこのエラーは${app_name}の管理外であり、Googleによると、このエラーは、端末にFCMに登録されているアプリが多すぎることを示しています。 このエラーは、アプリの数が極端に多い場合にのみ発生するため、平均的なユーザーには影響しません。
     ${app_name}はGoogle Playサービスを使用してプッシュメッセージを配信していますが、正しく設定されていないようです:
 \n%1$s
     FCMトークンの取得に失敗しました:
@@ -1716,7 +1716,7 @@
     
         %d件の招待
     
-    既にリストに載っているサーバーです
+    既に一覧に載っているサーバーです
     サーバーまたはそのルーム一覧が見つかりません
     検索される新しいサーバーの名前を入力します。
     新しいサーバーを追加
@@ -1735,8 +1735,8 @@
     非公開
     不明のアクセス設定(%s)
     誰でもノックができ、メンバーがその参加を承認または拒否できます
-    現在のルーム一覧の見え方を取得できません(%1$s)。
-    このルームを%1$sのルーム一覧に公開しますか?
+    現在のルームディレクトリの見え方を取得できません(%1$s)。
+    このルームを%1$sのルームディレクトリに公開しますか?
     アドレスを非公開にする
     アドレスを公開
     アドレスを設定すれば、他のユーザーがあなたのホームサーバー (%1$s) を通じてこのルームを見つけられるようになります。
@@ -1744,7 +1744,7 @@
     新しい公開アドレス(例: #alias:server)
     他の公開アドレスはまだありません。以下から追加できます。
     他の公開アドレスはまだありません。
-    このルームを%1$sのルーム一覧に公開しますか?
+    このルームを%1$sのルームディレクトリに公開しますか?
     \"%1$s\"を非公開にしますか?
     公開
     手動で新しいアドレスを公開
@@ -1752,7 +1752,7 @@
     公開されたアドレスを通して、どのサーバーのどのユーザーでもこのルームに参加できます。アドレスを公開するには、まずローカルアドレスとして設定する必要があります。
     公開アドレス
     ルームのアドレス
-    ルームのアドレス及びルーム一覧における可視性を管理できます。
+    ルームのアドレス及びルームディレクトリにおける可視性を管理できます。
     スペースのアドレスを管理できます。
     スペースのアドレス
     ルームのアドレス
@@ -1958,7 +1958,7 @@
     自分の表示名
     グループチャットでのメッセージの暗号化
     個別チャットでのメッセージの暗号化
-    通知してください
+    以下がメッセージに含まれる場合に通知
     その他
     メンションとキーワード
     通知のデフォルト
@@ -2224,7 +2224,7 @@
     自分のみ
     スレッドでディスカッションを整理して管理
     %sを待機しています…
-    このデバイスでスキャン
+    この端末でスキャン
     検証を送信済
     手動で検証
     このセッションを検証
@@ -2356,4 +2356,5 @@
     復号エラーを自動的に報告する。
     変更を有効にするにはアプリケーションの再起動が必要です。
     LaTeXによる数学表記を有効にする
+    以下が含まれる場合に通知
 
\ No newline at end of file

From 0c1c65112a267fec48f68e3fc0cae605ecf1a4e3 Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Fri, 18 Feb 2022 21:27:04 +0000
Subject: [PATCH 438/581] Translated using Weblate (Japanese)

Currently translated at 77.5% (2159 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 267 +++++++++++-----------
 1 file changed, 134 insertions(+), 133 deletions(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 24eb67c67a5..7c9b8e92806 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -1,6 +1,6 @@
 
 
-    %1$s: %2$s
+    %1$s:%2$s
     %1$sが画像を送信しました。
     %1$sがスタンプを送信しました。
     %sの招待
@@ -36,17 +36,17 @@
     ルームのメンバー全員。
     誰でも。
     不明 (%s)。
-    %1$s がエンドツーエンド暗号化を有効にしました (%2$s)
-    %1$s がVoIP会議をリクエストしました
+    %1$sがエンドツーエンド暗号化を有効にしました (%2$s)
+    %1$sがVoIP会議をリクエストしました
     VoIP会議が開始されました
     VoIP会議が終了しました
     (アバターも変更されました)
-    %1$s がルーム名を削除しました
-    %1$s がルームトピックを削除しました
-    %1$s がプロフィール %2$s を更新しました
-    %1$s は %2$s にルームに参加するよう招待状を送りました
+    %1$sがルーム名を削除しました
+    %1$sがルームトピックを削除しました
+    %1$sがプロフィール %2$sを更新しました
+    %1$sは%2$sにルームに参加するよう招待状を送りました
     %1$sは%2$sの招待を受け入れました
-    ** 解読できません: %s **
+    ** 解読できません:%s **
     送信者の端末からこのメッセージのキーが送信されていません。
     修正できませんでした
     メッセージを送信できません
@@ -164,7 +164,7 @@
     ダウンロードを停止しますか?
     アップロードを停止しますか?
     %d秒
-    %1$d分 %2$d秒
+    %1$d分%2$d秒
     昨日
     今日
     ルーム名
@@ -177,14 +177,14 @@
     ルームを退出
     このルームを退出してよろしいですか?
     作成
-    接続中
-    切断中
-    待機中
+    オンライン
+    オフライン
+    アイドル
     招待
     このルームを退出
     検索
     %sさんが文字を入力しています…
-    %1$sさんと %2$sさんが文字を入力しています…
+    %1$sさんと%2$sさんが文字を入力しています…
     %1$sさん、%2$sさん他が文字を入力しています…
     暗号文を送信…
     明るいテーマ
@@ -205,7 +205,7 @@
     このユーザー名は既に使用されています
     削除
     参加
-    あなたは %s さんに、このルームへ招待されています
+    あなたは%sさんに、このルームへ招待されています
     新しい会話
     参加者を追加
     1名
@@ -398,7 +398,7 @@
     パスワードを初期化するには, アカウントに登録されている電子メールアドレスを入力してください:
     あなたのアカウントに登録された電子メールアドレスの入力が必要です.
     新しいパスワードの入力が必要です。
-    %s へ電子メールが送信されました. リンクをたどったら以下をクリックしてください.
+    %sへ電子メールが送信されました. リンクをたどったら以下をクリックしてください.
     電子メールアドレスの確認に失敗しました: 電子メールのリンクをクリックしたことを確認してください
     パスワードがリセットされました。
 \n
@@ -555,12 +555,12 @@
     ルームの暗号鍵をエクスポート
     検証
     通話
-    通知あり(音量大)
-    通知あり(音量小)
+    通知あり(音量大)
+    通知あり(サイレント)
     不具合の報告
     開封確認メッセージのリスト
     ダウンロードファイルに保存しますか?
-    この招待はこのアカウントに関連付けられていない %s に送信されました。
+    この招待はこのアカウントに関連付けられていない%sに送信されました。
 \n別のアカウントでログインするか、このメールを自分のアカウントに追加してください。
     あなたは%sにアクセスしようとしています。ディスカッションに参加しますか?
     ルーム
@@ -571,7 +571,7 @@
     ユーザーディレクトリ (%s)
     Matrixユーザーのみ
     ユーザーIDで招待
-    1つまたは複数のメールアドレスか、Matrix IDを入力してください
+    1つまたは複数のメールアドレスか、Matrix IDを入力してください
     信用する
     信用しない
     フィンガープリント (%s):
@@ -608,7 +608,7 @@
     この通知の対象を削除してよろしいですか?
     %1$s %2$sを削除してよろしいですか?
     コード
-    %s はこのルームのタイムラインのある箇所を読み込もうとしましたが、見つかりませんでした。
+    %sはこのルームのタイムラインのある箇所を読み込もうとしましたが、見つかりませんでした。
     イベント情報
     ユーザーID
     Curve25519 固有鍵
@@ -631,16 +631,16 @@
     公開のルームを表示するホームサーバーを入力してください
     サーバー名
     %sサーバー上の全てのルーム
-    全てのローカルの %s ルーム
-    メッセージが未送信です。今 %1$s または %2$s しますか?
-    不明な端末が存在しているため、メッセージを送ることができませんでした。今 %1$s または %2$s しますか?
+    全てのローカルの%sルーム
+    メッセージが未送信です。今%1$sまたは%2$sしますか?
+    不明な端末が存在しているため、メッセージを送ることができませんでした。今%1$sまたは%2$sしますか?
     要求されたフィンガープリントキー Ed25519
     jitsiを用いて会議通話を始める
-    端末のカメラを使う
+    端末のカメラを使用
     警告!
     ビデオ会議は開発中であり、確実でない可能性があります。
     コマンドエラー
-    認識されないコマンド: %s
+    認識されないコマンド:%s
     
     音量大
     暗号化されたメッセージ
@@ -657,7 +657,7 @@
     ビデオ通話を開始してよろしいですか?
     グループリスト
     ユーザーをブロックすると、ユーザーはこのルームから削除され、二度と参加できなくなります。
-    全てのメッセージ (大音量)
+    全てのメッセージ (音量大)
     全てのメッセージ
     ミュート
     ホーム画面にショートカットを作成
@@ -691,7 +691,7 @@
     管理者はこのコミュニティーの詳細を規定していません。
     あなたは%2$sによって%1$sから除外されました
     あなたは%2$sによって%1$sへの参加を禁止されました
-    理由: %1$s
+    理由:%1$s
     再参加
     端末を振って不具合を報告
     
@@ -713,7 +713,7 @@
         %dルーム
     
     
-        %2$s に %1$s ルーム見つかりました
+        %2$sに%1$sルーム見つかりました
     
     ルームの履歴を消す
     アバター
@@ -755,10 +755,10 @@
     
         %d日
     
-    現在 %1$s
-    %2$s 前 %1$s
+    現在%1$s
+    %2$sより%1$s
     "%1$s、 "
-    %1$s と %2$s
+    %1$sと%2$s
     %1$s %2$s
     暗号化された返信を送信…
     返信を送信 (未暗号化)…
@@ -792,7 +792,7 @@
     
         %dルーム
     
-    %2$s件 中 %1$s件
+    %2$s件中%1$s件
     
         %d個のウィジェットが使用中
     
@@ -820,7 +820,7 @@
         %dルーム
     
     アバターを読み込み
-    %1$s ホームサーバーを使用し続けるには、利用規約を読み、同意する必要があります。
+    %1$のホームサーバーを使用し続けるには、利用規約を読み、同意する必要があります。
     エラー
     アバターに通知を表示
     今すぐ見る
@@ -845,8 +845,8 @@
     このホームサーバーはリソース制限の1つを超過しています。
      このホームサーバーは月間アクティブユーザー上限に達しているため、 ユーザーがログインできなくなることがあります
     このホームサーバーは月間アクティブユーザー上限に達しています。
-    この上限を上げるには %s してください。
-    このサービスを使い続けるには %s してください。
+    この上限を上げるには%sしてください。
+    このサービスを使い続けるには%sしてください。
     最初にルームのメンバーのみを読み込むことでパフォーマンスを向上。
     あなたのホームサーバーはルームのメンバーの簡易読み込みをサポートしていません。後で試してください。
     ルームのメンバーの簡易読み込み
@@ -859,7 +859,7 @@
     常に
     エラーの場合のみ
     %1$s:
-    %1$s: %2$s
+    %1$s:%2$s
     +%d
     %d+
     展開
@@ -869,7 +869,7 @@
     了承
     このホームサーバーの方針を閲覧し承認してください:
     通話設定画面
-    着信に${app_name}の既定の着信音を使う
+    着信に${app_name}の既定の着信音を使用
     着信音
     着信音を選んでください:
     追い出す
@@ -880,7 +880,7 @@
     端末を認証
     鍵のバックアップが終了していません。しばらくお待ちください…
     会議通話中。
-\n%1$s または %2$s として参加
+\n%1$sまたは%2$sとして参加
     音声
     ビデオ
     詳細な通知設定
@@ -891,7 +891,7 @@
     優先同期間隔
     %s
 \n同期は、端末のリソース (バッテリ残量) または状態 (スリープ) に応じて延期される場合があります。
-    文字入力中通知を送信
+    入力中通知を送信
     文字入力中であることを他の参加者に伝えます。
     開封確認メッセージを表示
     開封確認メッセージをクリックすると詳細なリストを確認できます。
@@ -903,7 +903,7 @@
     メディア
     シャッター音を再生
     公開端末名 (会話を行うユーザーに表示されます)
-    音なし
+    サイレント
     パスワードを表示
     パスワードを隠す
     新しいパスワード
@@ -964,7 +964,7 @@
     会話を検索…
     Matrix ID から追加
     ユーザー名または ID で検索…
-    全てのメッセージ (大音量)
+    全てのメッセージ (音量大)
     全てのメッセージ
     メンションのみ
     ミュート
@@ -996,10 +996,10 @@
     カスタム
     招待者
     ユーザー
-    %1$s の管理者
-    %1$s のモデレーター
-    %1$s のデフォルトユーザー
-    %2$s のカスタム (%1$d)
+    %1$sの管理者
+    %1$sのモデレーター
+    %1$sのデフォルトユーザー
+    %2$sのカスタム (%1$d)
     タイムライン
     エンドツーエンド暗号化を有効にする…
     暗号化を有効にする
@@ -1050,10 +1050,10 @@
     レビュー
     待機しています…
     サムネイルを暗号化しています…
-    サムネイルを送信しています (%1$s / %2$s)
+    サムネイルを送信しています (%1$s/%2$s)
     ファイルを暗号化しています…
-    ファイルを送信しています (%1$s / %2$s)
-    ファイル %1$s をダウンロードしています…
+    ファイルを送信しています (%1$s/%2$s)
+    ファイル %1$sをダウンロードしています…
     ファイル
     連絡先
     カメラ
@@ -1112,16 +1112,16 @@
     セキュアバックアップ
     ルームを作成しています…
     招待されています
-    %s からの招待
+    %sからの招待
     このセッションは正常に検証されました。
-    概ね完了しました。%s の画面にも同じシールドアイコンが表示されていますか?
+    概ね完了しました。%sの画面にも同じシールドアイコンが表示されていますか?
     相手ユーザーの端末のコードをスキャンし、相互に安全性を検証
     相手のコードをスキャン
     スキャンできません
     断る
     検証リクエスト
     通話の開始前に確認
-    意図しない通話を阻止
+    意図しない通話を防止
     お使いの端末は脆弱性のある古いTLSセキュリティープロトコルを使用しています、このセキュリティーでは接続できません
     SSLエラー。
     SSLエラー:相手のアイデンティティが認証されていません。
@@ -1172,7 +1172,7 @@
     QR コード
     QR コードによる追加
     コードを共有
-    ${app_name} で会話しましょう: %s
+    ${app_name} で会話しましょう:%s
     友達を招待
     既知のユーザー
     無効なQRコード (無効な URI)!
@@ -1196,7 +1196,7 @@
     1つ以上のテストが失敗しました。調査用の不具合報告を送信してください。
     1つ以上のテストが失敗しました。提案された修正を試してください。
     基本的な診断はOKです。 それでも通知が届かない場合は、調査用の不具合報告を送信してください。
-    実行しています… (%1$dの %2$d)
+    実行しています…(%1$dの%2$d)
     テストを実行
     診断トラブルシューティング
     イベントごとの通知の優先順位
@@ -1232,7 +1232,7 @@
     ブロックを解除すると、ユーザーは再びルームに参加できるようになります。
     禁止されたユーザー
     禁止の理由
-    ユーザーの禁止を解除
+    ユーザーのブロックを解除
     このユーザーはルームから除去されます。
 \n
 \n再参加を防ぐためには、除去する代わりにブロックする必要があります。
@@ -1246,7 +1246,7 @@
 \n
 \nこの動作は、設定からいつでも元に戻すことができます。
     ユーザーを無視
-    広角
+    降格
     あなたは自分自身を降格させようとしているため、今後、この変更を元に戻すことはできなくなります。あなたがルームの中で最後の特権ユーザーである場合、特権を再取得することはできません。
     降格しますか?
     招待をキャンセル
@@ -1276,8 +1276,8 @@
     非公開
     切り替える
     加える
-    %1$sはエンドツーエンド暗号化 (認識されていないアルゴリズム%2$s) をオンにしました。
-    エンドツーエンド暗号化 (認識されていないアルゴリズム%1$s) をオンにしました。
+    %1$sはエンドツーエンド暗号化(認識されていないアルゴリズム %2$s)をオンにしました。
+    エンドツーエンド暗号化(認識されていないアルゴリズム %1$s)をオンにしました。
     会話を始める
     %1$sがエンドツーエンド暗号化をオンにしました。
     エンドツーエンド暗号化をオンにしました。
@@ -1288,7 +1288,7 @@
     ここへのゲストの入室を許可しました。
     %1$sはここにゲストが参加することを許可しました。
     ゲストにルームへの参加を許可しました。
-    %1$s がゲストにルームへの参加を許可しました。
+    %1$sがゲストにルームへの参加を許可しました。
     システムデフォルト
     このルームのメインおよび代替のアドレスを変更しました。
     このルームの代替アドレスを変更しました。
@@ -1304,10 +1304,10 @@
     
         このルームのアドレスの%1$sを削除しました。
     
-    %1$sの招待を取り消しました。理由:%2$s
-    %1$sの招待を受諾しました%2$。理由:%2$s
-    %1$sのルームへの招待を取り消しました。理由:%2$s
-    %1$sにルームへの招待状を送りました。理由:%2$s
+    %1$sの招待を取り消しました。理由:%2$s
+    %1$sの招待を受諾しました%2$。理由:%2$s
+    %1$sのルームへの招待を取り消しました。理由:%2$s
+    %1$sにルームへの招待状を送りました。理由:%2$s
     VoIPカンファレンスをリクエストしました
     このルームのサーバーのアクセス制御リストを変更しました。
     このルームのサーバーアクセス制御リストを設定しました。
@@ -1317,28 +1317,28 @@
     通話に応答しました。
     通話を設定するためのデータを送信しました。
     このルームのアドレスを変更しました。
-    %1$s はこのルームのメインおよび代替アドレスを変更しました。
-    %1$s がこのルームのアドレスを変更しました。
+    %1$sはこのルームのメインおよび代替アドレスを変更しました。
+    %1$sがこのルームのアドレスを変更しました。
     %1$sはこのルームの代替アドレスを変更しました。
     
-        %1$s がこのルームの代替アドレス%2$sを削除しました。
+        %1$sがこのルームの代替アドレス%2$sを削除しました。
     
     
-        %1$s はこのルームの代替アドレス%2$sを追加しました。
+        %1$sはこのルームの代替アドレス%2$sを追加しました。
     
     %1$sがこのルームのメインアドレスを削除しました。
     %1$sがこのルームのメインアドレスを%2$sに設定しました。
-    %1$sはこのルームのアドレスとして %2$sを追加し%3$sを削除しました。
+    %1$sはこのルームのアドレスとして%2$sを追加し%3$sを削除しました。
     
-        %1$s はこのルームのアドレスの%2$sを削除しました。
+        %1$sはこのルームのアドレスの%2$sを削除しました。
     
     
         このルームのアドレスとして%1$sが追加されました。
     
-    %1$sが%2$s にルームへの招待を送りました。理由:%3$s
-    %1$sが%2$sのルームへの招待を取り消しました。理由:%3$s
-    %1$sが %2$sの招待を承諾しました。理由:%3$s
-    %1$sは%2$sの招待を取り下げました。理由:%3$s
+    %1$sが%2$sにルームへの招待を送りました。理由:%3$s
+    %1$sが%2$sのルームへの招待を取り消しました。理由:%3$s
+    %1$sが %2$sの招待を承諾しました。理由:%3$s
+    %1$sは%2$sの招待を取り下げました。理由:%3$s
     
         %1$sはこのルームのアドレスとして%2$sを追加しました。
     
@@ -1350,25 +1350,25 @@
     ・%sに一致するサーバーは禁止されています。
     %sがこのルームのサーバーアクセス制御リストを設定しました。
     %sがここをアップグレードしました。
-    %s がこのルームをアップグレードしました。
+    %sがこのルームをアップグレードしました。
     エンドツーエンド暗号化をオンにしました (%1$s)
     あなたは今後のメッセージを%1$sに見えるように設定しました
     今後のルーム履歴を%1$sに見えるように設定しました
-    %1$s は今後のメッセージを %2$sに見えるように設定しました
+    %1$sは今後のメッセージを%2$sに見えるように設定しました
     %sが通話を設定するためのデータを送信しました。
     通話をしました。
     ビデオ通話をしました。
-    あなたが%1$sを永久追放しました。理由: %2$s
-    %1$sが%2$sを永久追放しました。理由: %3$s
-    あなたが%1$sの禁止を解除しました。理由: %2$s
-    %1$sが%2$sの禁止を解除しました。理由: %3$s
-    あなたは%1$sを除去しました。理由: %2$s
-    %1$sが%2$sを除去しました。理由: %3$s
-    あなたは招待を拒否しました。理由: %1$s
-    %1$s は招待を拒否しました。理由: %2$s
-    退出しました。理由: %1$s
-    %1$sが退出しました。理由: %2$s
-    あなたがこのルームを退出しました。理由: %1$s
+    あなたが%1$sをブロックしました。理由:%2$s
+    %1$sが%2$sをブロックしました。理由:%3$s
+    あなたが%1$sのブロックを解除しました。理由:%2$s
+    %1$sが%2$sのブロックを解除しました。理由:%3$s
+    あなたは%1$sを除去しました。理由:%2$s
+    %1$sが%2$sを除去しました。理由:%3$s
+    あなたは招待を拒否しました。理由:%1$s
+    %1$sは招待を拒否しました。理由:%2$s
+    退出しました。理由:%1$s
+    %1$sが退出しました。理由:%2$s
+    あなたがこのルームを退出しました。理由:%1$s
     初期同期:
 \n退出したルームをインポートしています
     初期同期:
@@ -1376,16 +1376,16 @@
     初期同期:
 \n会話を読み込んでいます
 \n多くのルームに参加している場合、読み込みに時間がかかるかもしれません
-    %1$sがこのルームを退出しました。理由: %2$s
-    あなたがこのルームに参加しました。理由: %1$s
-    %1$sがこのルームに参加しました。理由: %2$s
-    あなたがこのルームに参加しました。理由: %1$s
-    %1$sがこのルームに参加しました。理由: %2$s
-    %1$sがあなたを招待しました。 理由: %2$s
-    あなたが%1$sを招待しました。 理由: %2$s
-    %1$sが%2$sを招待しました。 理由: %3$s
-    あなたの招待です。理由: %1$s
-    %1$sの招待です。理由: %2$s
+    %1$sがこのルームを退出しました。理由:%2$s
+    あなたがこのルームに参加しました。理由:%1$s
+    %1$sがこのルームに参加しました。理由:%2$s
+    あなたがこのルームに参加しました。理由:%1$s
+    %1$sがこのルームに参加しました。理由:%2$s
+    %1$sがあなたを招待しました。 理由:%2$s
+    あなたが%1$sを招待しました。 理由:%2$s
+    %1$sが%2$sを招待しました。 理由:%3$s
+    あなたの招待です。理由:%1$s
+    %1$sの招待です。理由:%2$s
     送信キューのクリア
     メッセージを送っています…
     メッセージを送りました
@@ -1426,11 +1426,11 @@
     あなたは%1$sウィジェットを変更しました
     %1$sは%2$sウィジェットを変更しました
     あなたは%1$sウィジェットを削除しました
-    %1$sが %2$sウィジェットを削除しました
+    %1$sが%2$sウィジェットを削除しました
     あなたは%1$sウィジェットを追加しました
-    あなたは %1$sの招待を受けました
-    %1$sが %2$sウィジェットを追加しました
-    ルーム名を変更しました: %1$s
+    あなたは%1$sの招待を受けました
+    %1$sが%2$sウィジェットを追加しました
+    ルーム名を変更しました:%1$s
     あなたは%1$sの招待を取り消しました
     %1$sが%2$sの招待を取り消しました
     あなたは%1$sを招待しました
@@ -1438,12 +1438,12 @@
     %1$sが%2$sのルームへの招待を取り消しました
     %1$sが%2$sを招待しました
     あなたは%1$sにルームへの招待を送りました
-    メッセージが%1$sによって削除されました [理由: %2$s]
-    メッセージが削除されました [理由: %1$s]
+    メッセージが%1$sによって削除されました[理由:%2$s]
+    メッセージが削除されました[理由:%1$s]
     %1$sがメッセージを削除しました
     メッセージを削除
     ルームのアバターを削除しました
-    %1$s がルームのアバターを削除しました
+    %1$sがルームのアバターを削除しました
     ルームのトピックを削除しました
     ルーム名を削除しました
     許可を与える
@@ -1476,9 +1476,9 @@
     ${app_name}は、端末の限られたリソース(バッテリー)を維持する方法でバックグラウンド同期をします。
 \n端末の状態によっては、OSによって同期が延期される場合があります。
     LEDの色、振動、音を選択してください…
-    サイレント通知を設定
+    通知(サイレント)を設定
     通話の通知を設定
-    うるさい通知を設定
+    通知(音量大)を設定
     アプリはバックグラウンドでホームサーバーに接続する必要がないためバッテリー使用量を減らすことができます
     最適化を無視
     画面をオフにした状態で端末のプラグを抜いて一定時間静止したままにすると、端末は機内モードになります。 これにより、アプリがネットワークにアクセスできなくなり、ジョブ、同期、および標準のアラームが防止されます。
@@ -1528,9 +1528,9 @@
     • %sに一致するサーバーが禁止リストから削除されました。
     • %sに一致するサーバーは禁止されています。
     
-        %1$sや %2$sそれに%3$dに他の人も読みました
+        %1$s、%2$s、他%3$d人のユーザーが読みました
     
-    %1$sや %2$sそれに%3$sが読みました
+    %1$s、%2$s、%3$sが読みました
     メッセージをマークダウンとして解釈せずにプレーンテキストとして送信
     ファイルとして保存
     共有
@@ -1564,13 +1564,13 @@
     新しいイベント
     不明なIP
     
-        キー %1$dと%2$dのインポートに成功。
+        キー%1$dと%2$dのインポートに成功。
     
     キーバックアップを管理
     キーのエクスポートに成功しました
     選択
     選択
-    詳細情報: %s
+    詳細情報:%s
     ローカルアドレスを追加
     このルームにはローカルアドレスがありません
     アドレス \"%1$s\" を削除しますか?
@@ -1578,13 +1578,13 @@
     これがメインアドレスです
     電話番号の認証中にエラーが発生しました。
     リンクを共有
-    %s に招待
+    %sに招待
     Eメールで招待
     詳細
     人を招待
     とにかく参加
     ルームを追加
-    %s はあなたを招待しています
+    %sはあなたを招待しています
     このルームでグループ通話をする権利がありません
     オーディオミーティングを開始
     安全バックアップを設定
@@ -1678,12 +1678,12 @@
     ”%s”とのコマンドはいくつかのパラメータが欠けているか不正です。
     この機能はメッセージを録音するために第三者のアプリを必要とします。
     新しいセッションが暗号鍵を要請しています。
-\nセッション名: %1$s
-\n最後のオンライン時刻: %2$s
+\nセッション名:%1$s
+\n最後のオンライン時刻:%2$s
 \n新たにログインして新しいセッションを開始しなかった場合、この要求を無視してください。
     未検証のセッションが暗号鍵を要請しています。
-\nセッション名: %1$s
-\n最後のオンライン時刻: %2$s
+\nセッション名:%1$s
+\n最後のオンライン時刻:%2$s
 \n新たにログインして新しいセッションを開始しなかった場合、この要求を無視してください。
     暗号鍵共有要請
     カスタムカメラ画面の代わりにシステムカメラを使用します。
@@ -1706,12 +1706,12 @@
     **送信に失敗 - ルームを開いてください
     新しい招待
     %1$sと%2$s
-    %1$s に %2$s と %3$s
+    %1$sに%2$sと%3$s
     
         %d件の通知
     
     
-        %1$s: %2$d件のメッセージ
+        %1$s:%2$d件のメッセージ
     
     
         %d件の招待
@@ -1767,7 +1767,7 @@
     ホームサーバーAPIのURL
     復旧用のメールアドレスを設定します。後からオプションでメールアドレスや電話番号を使用して知人に見つけてもらえるようにできます。
     電話番号を設定して、後からオプションで知人に見つけてもらえるようにできます。
-    %s を使用してみてください
+    %sを使用してみてください
     アクセスを取り消す
     表示
     このルーム内のメッセージはエンドツーエンド暗号化されています。
@@ -1819,7 +1819,7 @@
     ここが%sとのダイレクトメッセージのスタート地点です。
     変更履歴はありません
     メッセージの変更履歴
-    ファイル %1$s をダウンロードしました!
+    ファイル %1$sをダウンロードしました!
     ビデオの圧縮%d%%
     画像を圧縮しています…
     暗号化されたルームで完全な履歴を表示
@@ -1847,7 +1847,7 @@
     ファイル\"%1$s\"からe2eキーをインポートします。
     キーのバックアップデータの取得中にエラーが発生しました
     信頼情報の取得中にエラーが発生しました
-    ルームが作成されましたが、一部の招待が送信されていません。理由:
+    ルームが作成されましたが、一部の招待が送信されていません。理由:
 \n
 \n%s
     ルームの設定
@@ -1899,7 +1899,7 @@
     このルームを含むスペースを知る
     このルームにアクセスできるスペースを決定します。スペースが選択されるとそのメンバーはルーム名を見つけて参加できます。
     検証がキャンセルされました。
-\n理由:%s
+\n理由:%s
     相手が検証をキャンセルしました。
 \n%s
     リクエストはキャンセルされました
@@ -1922,7 +1922,7 @@
     短い文字列を比較して検証します。
     認証が無効または期限切れのため、ログアウトされました。
     構成を使用
-    ${app_name}がuserIdドメイン\"%1$s \"のカスタムサーバー構成を検出しました。
+    ${app_name}がuserIdドメイン\"%1$s\"のカスタムサーバー構成を検出しました。
 \n%2$s
     サーバーオプションをおまかせする
     無効なホームサーバーディスカバリーレスポンス
@@ -1932,9 +1932,9 @@
     暗号化されたメッセージを失いません
     バックアップ (%s) のtrust infoの取得に失敗しました。
     セッションの暗号化が有効になっていません
-    これを使用するとデータを %sと共有します。
-    %1$s: %2$s %3$s
-    %1$s: %2$s
+    これを使用するとデータを%sと共有します。
+    %1$s:%2$s %3$s
+    %1$s:%2$s
     あなたが知らないかもしれない他のスペースやルーム
     このルームを見つけて参加できるか決める。
     タップしスペースを編集
@@ -1996,12 +1996,12 @@
     電話番号を追加すると、発見可能に設定する電話番号を選択できるようになります。
     メールアドレスを追加すると、発見可能に設定するメールアドレスを選択できるようになります。
     現在、IDサーバーを使用していません。あなたの知っている連絡先を発見したり、その連絡先から発見されるようにするには、以下を設定してください。
-    あなたは現在 %1$sを使って連絡先を見つけたり、連絡先から見つけられるようにしています。
+    あなたは現在%1$sを使って連絡先を見つけたり、連絡先から見つけられるようにしています。
     IDサーバーを変更
     IDサーバーの設定
     IDサーバーの切断
     IDサーバー
-    ボット、ブリッジ、ウィジェット、ステッカーパックを使う
+    ボット、ブリッジ、ウィジェット、ステッカーパックを使用
     他の人が見つけられるように
     規約を確認
     利用規約
@@ -2056,7 +2056,7 @@
     %sとのビデオ通話
     呼び出しています…
     ホームサーバーを選択
-    %s のURLにあるホームサーバーに接続できません。リンクをチェックするか、手動でホームサーバーを選択してください。
+    %sのURLにあるホームサーバーに接続できません。リンクをチェックするか、手動でホームサーバーを選択してください。
     後で
     スペース
     スレッドから
@@ -2069,14 +2069,14 @@
     コンテンツが報告されました
     ヘルプとサポート
     ヘルプ
-    ${app_name} ポリシー
+    ${app_name}のポリシー
     ここ
     キーワードを追加
     自分のスレッド
     全てのスレッド
     ユーザーを自動的に招待
     ユーザー
-    このユーザーの禁止を解除すると、そのユーザーはスペースに再び参加できるようになります。
+    このユーザーのブロックを解除すると、そのユーザーはスペースに再び参加できるようになります。
     このルームを招待者のみ参加可能に設定しました。
     低優先度から削除
     低優先度に追加
@@ -2216,9 +2216,9 @@
     添付ファイルの取得中にエラーが発生しました。
     キーバックアップのバナーを閉じる
     キーワードに「%s」を含めることはできません
-    %sへのメール通知を有効にする
-    ヒント:メッセージを長押しして「%s」を使う。
-    スレッドを使うと、会話のテーマを保ったり、会話を追跡したりするのが容易になります。
+    %s へのメール通知を有効にする
+    ヒント:メッセージを長押しして「%s」を選択。
+    スレッドを用いると、会話のテーマを保ったり、会話を追跡したりするのが容易になります。
     あなたの非公開スペース
     あなたの公開スペース
     自分のみ
@@ -2341,7 +2341,7 @@
     連絡先を発見するには、連絡先のデータをあなたのIDサーバーに送信する必要があります。
 \n
 \nプライバシーの保護のため、データは送信前にハッシュ化されます。この情報を送信することに同意しますか?
-    メールアドレスと電話番号を %s に送信
+    メールアドレスと電話番号を%sに送信
     このIDサーバーはポリシーを提供していません
     IDサーバーのポリシーを隠す
     IDサーバーのポリシーを表示
@@ -2357,4 +2357,5 @@
     変更を有効にするにはアプリケーションの再起動が必要です。
     LaTeXによる数学表記を有効にする
     以下が含まれる場合に通知
+    アップグレードすると、このルームの新しいバージョンが作成されます。今ある全てのメッセージは、アーカイブしたルームに残ります。
 
\ No newline at end of file

From 0b0ca4f92f8968942f05af2b3bcd6adf65e4937d Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Sat, 19 Feb 2022 10:25:19 +0000
Subject: [PATCH 439/581] Translated using Weblate (Japanese)

Currently translated at 77.5% (2159 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 7c9b8e92806..9517c5aedcd 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -386,7 +386,7 @@
     電話番号が入力されていません
     電子メールアドレスまたは電話番号が入力されていません
     接続先サーバーを指定する(追加設定)
-    トークンが正しくありません
+    不正なトークン
     メールと電話番号の同時登録はまだシステムが対応できませんが、電話番号だけの登録は可能です。
 \n
 \n設定からプロフィールにメールアドレスを追加できます。
@@ -761,7 +761,7 @@
     %1$sと%2$s
     %1$s %2$s
     暗号化された返信を送信…
-    返信を送信 (未暗号化)…
+    返信を送る(未暗号化)…
     
         %d個選択済
     

From 540a410941a5d8819a332ddf3fc4204eea9a208f Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Sat, 19 Feb 2022 12:51:40 +0000
Subject: [PATCH 440/581] Translated using Weblate (Japanese)

Currently translated at 77.6% (2162 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 79 ++++++++++++-----------
 1 file changed, 41 insertions(+), 38 deletions(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 9517c5aedcd..4ca27087e7d 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -207,10 +207,10 @@
     参加
     あなたは%sさんに、このルームへ招待されています
     新しい会話
-    参加者を追加
+    メンバーを追加
     1名
     端末
-    権限を一般参加者へ変更
+    権限を一般メンバーへ変更
     権限を司会者へ変更
     権限を管理者へ変更
     ここに送信文を入力 (暗号なし)…
@@ -221,7 +221,7 @@
     ファイルが見つかりません
     あなたはこのルームで発言する権限がありません。
     ルームの詳細
-    参加者
+    メンバー
     ファイル
     設定
     お気に入り
@@ -306,15 +306,15 @@
     ルームへの参加
     ルームの履歴の可視範囲
     ルームの履歴を読める人は\?
-    ルームへ参加できる人は\?
+    誰がルームにアクセスできますか?
     誰でも
-    参加者のみ (この設定を選択した時点から)
-    参加者のみ (招待を送った時点から)
-    参加者のみ (参加した時点から)
-    このルームに招待された人だけ
-    ルームのリンクを知る人なら誰でも(ゲストユーザーを除く)
-    ルームのリンクを知る人なら誰でも(ゲストユーザーも含む)
-    再入室禁止された参加者
+    メンバーのみ(この設定を選択した時点から)
+    メンバーのみ(招待を送った時点から)
+    メンバーのみ(参加した時点から)
+    招待された人のみ
+    ルームのリンクを知っている人なら誰でも(ゲストユーザーを除く)
+    ルームのリンクを知っている人なら誰でも(ゲストユーザーを含む)
+    再入室禁止されたメンバー
     拡張設定
     このルームのサーバー内識別ID
     ラボ
@@ -322,13 +322,13 @@
     エンドツーエンド暗号化
     エンドツーエンド暗号化を使用中
     暗号を有効にするためにはログアウトする必要があります.
-    認証された端末のみで暗号化
+    検証済のセッションに対してのみ暗号化
     このセッションでは、このルームの未検証のセッションに対して暗号化されたメッセージを送信しない。
     新しいアドレス (記入例 #foo:matrix.org)
     このルームにはローカルアドレスがありません
     住所表記
-    住所表記が正しくありません
-    \'%s\' は正しくない形式の住所表記です
+    エイリアスのフォーマットが正しくありません
+    \'%s\'はエイリアスの正しいフォーマットではありません
     このルームのメインアドレスが設定されていません。
     メインアドレスの警告
     メインアドレスとして設定
@@ -430,8 +430,8 @@
     ダイレクトメッセージ
     再入室禁止
     再入室禁止解除
-    この参加者の発言を全て非表示
-    この参加者の発言を全て表示
+    このメンバーの発言を全て非表示
+    このメンバーの発言を全て表示
     指名して呼掛け
     ユーザーID, 表示名, 電子メールアドレス
     接続端末一覧を表示
@@ -442,10 +442,10 @@
     ログアウト
     無視
     招待中
-    参加者
-    参加者を検索
+    メンバー
+    メンバーを検索
     結果なし
-    参加者
+    メンバー
     ファイル
     ルーム
     ディレクトリを見る
@@ -477,7 +477,7 @@
     このルームへのリンクを作成するには、アドレスが必要です。
     このルームは暗号化されています。
     このルームは暗号化されていません。
-    暗号化を有効にします
+    暗号化を有効にする
 \n(警告: 有効後にこれを無効にすることはできません!)
     ディレクトリ
     外観
@@ -543,7 +543,7 @@
     端末の検証
     不明な端末
     このセッションでは、未検証のセッションに対して暗号化されたメッセージを送信しない
-    認証済み端末に対してのみ暗号化
+    認証済端末に対してのみ暗号化
     インポート
     ローカルファイルからキーをインポート
     ルームキーをインポート
@@ -680,7 +680,7 @@
     コミュニティーID
     
     ホーム
-    参加者
+    メンバー
     ルーム
     ユーザーがいません
     ルーム
@@ -697,11 +697,11 @@
     
         %dメンバーシップの変更
     
-    参加者を表示
+    メンバーを表示
     見出しを開く
     同期しています…
     
-        %d名の参加者
+        %d名のメンバー
     
     
         %d名
@@ -814,7 +814,7 @@
     Markdown書式の入/切
     Matrixアプリの管理を修正するには
     
-        %d名の参加者
+        %d名のメンバー
     
     
         %dルーム
@@ -892,7 +892,7 @@
     %s
 \n同期は、端末のリソース (バッテリ残量) または状態 (スリープ) に応じて延期される場合があります。
     入力中通知を送信
-    文字入力中であることを他の参加者に伝えます。
+    文字入力中であることを他のメンバーに伝えます。
     開封確認メッセージを表示
     開封確認メッセージをクリックすると詳細なリストを確認できます。
     エンター入力でメッセージを送信
@@ -939,8 +939,8 @@
     とどまる
     編集
     返信
-    削除済みのメッセージ
-    削除済みのメッセージを表示
+    メッセージが削除されました
+    削除済のメッセージを表示
     削除されたメッセージの代わりに削除されたという通知を表示します。
     ユーザーによって削除されたイベント
     新しいルームを作成
@@ -960,7 +960,7 @@
     セキュリティーとプライバシー
     ヘルプと概要
     ダイレクトメッセージ
-    (編集済み)
+    (編集済)
     会話を検索…
     Matrix ID から追加
     ユーザー名または ID で検索…
@@ -986,7 +986,7 @@
     ルームの設定
     通知
     
-        %1$d 人の参加者
+        %1$d 人のメンバー
     
     アップロード
     ルームを退出
@@ -1725,9 +1725,9 @@
     セッションの公開名は会話中の相手に閲覧できます
     ルームのバージョン
     
-        banされたユーザー%d人
+        ブロックされたユーザー%d人
     
-    このルームのあるスペースの参加者は誰でも発見し参加できます。ルームをスペースに追加できるのは、ルームの管理者だけです。
+    このルームのあるスペースのメンバーは誰でも発見し参加できます。ルームをスペースに追加できるのは、ルームの管理者だけです。
     スペースのメンバーのみ
     誰でもルームを発見し参加できます
     公開
@@ -1896,7 +1896,7 @@
     セッションはそのトランザクションについて知りません
     検証プロセスがタイムアウトしました
     ユーザーが検証をキャンセルしました
-    このルームを含むスペースを知る
+    このルームを含む参加済のスペース
     このルームにアクセスできるスペースを決定します。スペースが選択されるとそのメンバーはルーム名を見つけて参加できます。
     検証がキャンセルされました。
 \n理由:%s
@@ -1914,8 +1914,8 @@
     検証リクエストを受信しました。
     相手の画面に次の番号が表示されていることを確認して、このセッションを確認します
     相手の画面に次の絵文字が表示されることを確認して、このセッションを確認します
-    このセッションを検証すると、信頼済みとしてマークされ、自分も相手に信頼済みとしてマークされます。
-    このセッションを検証して、信頼済みとしてマークします。やり取りの前に検証することで、より安全にメッセージすることができます。
+    このセッションを検証すると、信頼済としてマークされ、自分も相手に信頼済としてマークされます。
+    このセッションを検証して、信頼済としてマークします。相手のセッションを信頼すると、さらに安心してエンドツーエンド暗号化を使用することができます。
     入力された検証要求
     検証を開始します
     最大限のセキュリティーを確保するために、これを行うか、別の信頼できる通信手段を用いることをお勧めします。
@@ -1936,12 +1936,12 @@
     %1$s:%2$s %3$s
     %1$s:%2$s
     あなたが知らないかもしれない他のスペースやルーム
-    このルームを見つけて参加できるか決める。
+    誰がこのルームを検索し、参加できるか決める。
     タップしスペースを編集
     スペースを選択
     アクセス可能なスペース
     スペースのメンバーに発見とアクセスを許可します。
-    スペース%sのメンバーが検索、プレビュー、参加できます。
+    スペース %s のメンバーが検索、プレビュー、参加できます。
     非公開(招待のみ)
     デフォルトのメディアソース
     デフォルトの圧縮
@@ -1971,7 +1971,7 @@
     デフォルトで使いもう尋ねない
     キー共有リクエストの履歴を送信
     結果がありません
-    自分で電話をかけることはできません。参加者が招待を受け入れるのを待ちます
+    自分で電話をかけることはできません。メンバーが招待を受け入れるのを待ちます
     ミーティングはJitsiのセキュリティーとパーミッションポリシーを使用します。会議中は、現在ルームにいる全ての人に招待状が表示されます。
     権限がありません
     音声メッセージを送信するには、マイクの権限を許可してください。
@@ -2358,4 +2358,7 @@
     LaTeXによる数学表記を有効にする
     以下が含まれる場合に通知
     アップグレードすると、このルームの新しいバージョンが作成されます。今ある全てのメッセージは、アーカイブしたルームに残ります。
+    誰がアクセスできますか?
+    通知は%1$sで管理できます。
+    暗号化されたルームでのメンションとキーワードによる通知は、携帯端末では利用できません。
 
\ No newline at end of file

From 821913788d8609f564970a3b72b7c23198f73a60 Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Sat, 19 Feb 2022 13:18:34 +0000
Subject: [PATCH 441/581] Translated using Weblate (Japanese)

Currently translated at 77.7% (2165 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 4ca27087e7d..99988c650b1 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -303,7 +303,7 @@
     なし
     参加と可視範囲
     ルームディレクトリへ公開
-    ルームへの参加
+    ルームへのアクセス
     ルームの履歴の可視範囲
     ルームの履歴を読める人は\?
     誰がルームにアクセスできますか?

From 22369717b5f11493719d0c48661a2accaa99b822 Mon Sep 17 00:00:00 2001
From: Joe Sagawa 
Date: Sat, 19 Feb 2022 13:17:20 +0000
Subject: [PATCH 442/581] Translated using Weblate (Japanese)

Currently translated at 77.7% (2165 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 99988c650b1..0ce844ca664 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2358,7 +2358,10 @@
     LaTeXによる数学表記を有効にする
     以下が含まれる場合に通知
     アップグレードすると、このルームの新しいバージョンが作成されます。今ある全てのメッセージは、アーカイブしたルームに残ります。
-    誰がアクセスできますか?
+    誰にアクセスさせますか?
     通知は%1$sで管理できます。
     暗号化されたルームでのメンションとキーワードによる通知は、携帯端末では利用できません。
+    ユーザーを無視し、そのメッセージを非表示にします
+    %sとのコマンドは認識されていますが、スレッドではサポートされていません。
+    誰でもこのスペースを発見し参加できます
 
\ No newline at end of file

From 0da00efa8ac7fee7f29b09ca55f853c38486db7d Mon Sep 17 00:00:00 2001
From: Joe Sagawa 
Date: Sat, 19 Feb 2022 13:33:48 +0000
Subject: [PATCH 443/581] Translated using Weblate (Japanese)

Currently translated at 78.3% (2181 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 0ce844ca664..d64895bc74d 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2364,4 +2364,9 @@
     ユーザーを無視し、そのメッセージを非表示にします
     %sとのコマンドは認識されていますが、スレッドではサポートされていません。
     誰でもこのスペースを発見し参加できます
+    法律関連
+    ユーザーに関する情報を表示します
+    この部屋においてのみアバターを変更します
+    この部屋においてのみ表示名を変更します
+    ユーザーの無視を解除し、これからのメッセージを表示します
 
\ No newline at end of file

From e3ca25c4f5c75186fdd31e21383f5b4f13126e55 Mon Sep 17 00:00:00 2001
From: oksya8and8 
Date: Sat, 19 Feb 2022 13:32:11 +0000
Subject: [PATCH 444/581] Translated using Weblate (Japanese)

Currently translated at 78.3% (2181 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index d64895bc74d..85c7ebd8171 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2369,4 +2369,7 @@
     この部屋においてのみアバターを変更します
     この部屋においてのみ表示名を変更します
     ユーザーの無視を解除し、これからのメッセージを表示します
+    続行するには%sを入力してください
+    リカバリーパスフレーズ
+    有効なリカバリーキーではありません
 
\ No newline at end of file

From e9c10d719d12d16a20b3393ca85fea61a9f9867d Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Sat, 19 Feb 2022 13:31:48 +0000
Subject: [PATCH 445/581] Translated using Weblate (Japanese)

Currently translated at 78.3% (2181 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 85c7ebd8171..9d008725a48 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2358,7 +2358,7 @@
     LaTeXによる数学表記を有効にする
     以下が含まれる場合に通知
     アップグレードすると、このルームの新しいバージョンが作成されます。今ある全てのメッセージは、アーカイブしたルームに残ります。
-    誰にアクセスさせますか?
+    誰がアクセスできますか?
     通知は%1$sで管理できます。
     暗号化されたルームでのメンションとキーワードによる通知は、携帯端末では利用できません。
     ユーザーを無視し、そのメッセージを非表示にします
@@ -2372,4 +2372,12 @@
     続行するには%sを入力してください
     リカバリーパスフレーズ
     有効なリカバリーキーではありません
+    リカバリーキーを入力してください
+    投票の種類
+    実施中の投票
+    投票する
+    投票した人には、投票の際に即座に結果が表示されます
+    終了した投票
+    結果は投票を終了した後でのみ明らかにされます
+    以下で開く
 
\ No newline at end of file

From e3243f7fc1694aeff8857d2f1e97ff8d2999ba6e Mon Sep 17 00:00:00 2001
From: oksya8and8 
Date: Sat, 19 Feb 2022 13:50:20 +0000
Subject: [PATCH 446/581] Translated using Weblate (Japanese)

Currently translated at 78.9% (2200 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 9d008725a48..acbb77a455f 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2380,4 +2380,5 @@
     終了した投票
     結果は投票を終了した後でのみ明らかにされます
     以下で開く
+    暗号化のアップグレードが利用できます
 
\ No newline at end of file

From 681fd3d97c1e995157370bc176a3b678203754b6 Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Sat, 19 Feb 2022 13:49:56 +0000
Subject: [PATCH 447/581] Translated using Weblate (Japanese)

Currently translated at 78.9% (2200 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 20 +++++++++++++++++++-
 1 file changed, 19 insertions(+), 1 deletion(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index acbb77a455f..cdcf6197c2d 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2364,7 +2364,7 @@
     ユーザーを無視し、そのメッセージを非表示にします
     %sとのコマンドは認識されていますが、スレッドではサポートされていません。
     誰でもこのスペースを発見し参加できます
-    法律関連
+    法的情報
     ユーザーに関する情報を表示します
     この部屋においてのみアバターを変更します
     この部屋においてのみ表示名を変更します
@@ -2381,4 +2381,22 @@
     結果は投票を終了した後でのみ明らかにされます
     以下で開く
     暗号化のアップグレードが利用できます
+    SSSSキーをリカバリーキーから生成しています
+    ${app_name} iOS
+\n${app_name} Android
+    ${app_name}ウェブ版
+\n${app_name}デスクトップ版
+    リカバリーキーを選択、直接入力、あるいはクリップボードからペースト
+    リカバリーキーを使用
+    暗号化されたルームでのみサポート
+    メディアファイルを保存できませんでした
+    メディアファイルをギャラリーに追加できませんでした
+    このスペースへの招待が、アカウントに関連付けられていないメールアドレス %s に送られました
+    
+        %1$d個の投票
+    
+    投票を締め切り、投票の最終結果を表示します。
+    招待者のみ参加可能。個人やチームに最適
+    スペースを作成
+    スペースに招待
 
\ No newline at end of file

From 44b1f2f955b28da0d4ce20c7fa894bc295199ed7 Mon Sep 17 00:00:00 2001
From: Joe Sagawa 
Date: Sat, 19 Feb 2022 13:46:27 +0000
Subject: [PATCH 448/581] Translated using Weblate (Japanese)

Currently translated at 78.9% (2200 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index cdcf6197c2d..5d329abd4e3 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2399,4 +2399,8 @@
     招待者のみ参加可能。個人やチームに最適
     スペースを作成
     スペースに招待
+    IDサーバーは利用規約がありません
+    あなたの連絡先はプライベートです。端末の連絡先からユーザーを発見できるように、連絡先の情報をIDサーバーへ送信する許可が必要です。
+    ディスカバリー設定を開く
+    既読時間
 
\ No newline at end of file

From 9019edb04598ee6ff469c082c4972373e5991e36 Mon Sep 17 00:00:00 2001
From: oksya8and8 
Date: Sat, 19 Feb 2022 13:50:43 +0000
Subject: [PATCH 449/581] Translated using Weblate (Japanese)

Currently translated at 79.0% (2201 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 5d329abd4e3..7c1607e3444 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2403,4 +2403,5 @@
     あなたの連絡先はプライベートです。端末の連絡先からユーザーを発見できるように、連絡先の情報をIDサーバーへ送信する許可が必要です。
     ディスカバリー設定を開く
     既読時間
+    クロスサイニングを有効にする
 
\ No newline at end of file

From 73e51e3efcd3d4aad4497aad1d5f30f23399f25a Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Sat, 19 Feb 2022 13:50:30 +0000
Subject: [PATCH 450/581] Translated using Weblate (Japanese)

Currently translated at 79.0% (2201 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 7c1607e3444..9600b36733c 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2380,7 +2380,7 @@
     終了した投票
     結果は投票を終了した後でのみ明らかにされます
     以下で開く
-    暗号化のアップグレードが利用できます
+    暗号化のアップグレードが利用可能です
     SSSSキーをリカバリーキーから生成しています
     ${app_name} iOS
 \n${app_name} Android

From 96a6793d7b8e2ad4c6ea8f23007bc7232bee86eb Mon Sep 17 00:00:00 2001
From: oksya8and8 
Date: Sat, 19 Feb 2022 13:51:33 +0000
Subject: [PATCH 451/581] Translated using Weblate (Japanese)

Currently translated at 79.0% (2202 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 9600b36733c..c4053b34f0e 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2404,4 +2404,5 @@
     ディスカバリー設定を開く
     既読時間
     クロスサイニングを有効にする
+    トピックを追加する
 
\ No newline at end of file

From c1e0b4d1f5699e77c98cadcc1c17da0dcd428f0a Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Sat, 19 Feb 2022 13:50:51 +0000
Subject: [PATCH 452/581] Translated using Weblate (Japanese)

Currently translated at 79.0% (2202 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index c4053b34f0e..00d18e951a6 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2403,6 +2403,6 @@
     あなたの連絡先はプライベートです。端末の連絡先からユーザーを発見できるように、連絡先の情報をIDサーバーへ送信する許可が必要です。
     ディスカバリー設定を開く
     既読時間
-    クロスサイニングを有効にする
+    クロス証明を有効にする
     トピックを追加する
 
\ No newline at end of file

From ed4634acf0f96dce952f8660ee7508a893827754 Mon Sep 17 00:00:00 2001
From: oksya8and8 
Date: Sat, 19 Feb 2022 13:55:29 +0000
Subject: [PATCH 453/581] Translated using Weblate (Japanese)

Currently translated at 79.2% (2208 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 00d18e951a6..8674aa6d09f 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2405,4 +2405,7 @@
     既読時間
     クロス証明を有効にする
     トピックを追加する
+    %sが部屋を作成し構成しました。
+    参加しました。
+    %sが参加しました。
 
\ No newline at end of file

From 4e12ab1a06244dc49363ea084e4a65485d070097 Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Sat, 19 Feb 2022 13:54:46 +0000
Subject: [PATCH 454/581] Translated using Weblate (Japanese)

Currently translated at 79.2% (2208 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 8674aa6d09f..03cf2312cc0 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -1031,7 +1031,7 @@
     このイベントを削除してよろしいですか?ルーム名やトピックの変更を削除すると、変更が元に戻る点にご注意ください。
     暗号化は有効です
     このルーム内でのメッセージはエンドツーエンド暗号化されます。詳細の確認や検証はユーザーのプロフィールをご確認ください。
-    暗号化が有効化されていません
+    暗号化が有効になっていません
     通知設定
     切断
     ログアウトしますか?
@@ -2404,8 +2404,10 @@
     ディスカバリー設定を開く
     既読時間
     クロス証明を有効にする
-    トピックを追加する
+    トピックを追加
     %sが部屋を作成し構成しました。
     参加しました。
     %sが参加しました。
+    このルームで使用されている暗号化はサポートされていません
+    暗号化が正しく設定されていません
 
\ No newline at end of file

From 56d1dd29050709b620277d6c89611236bc0b2340 Mon Sep 17 00:00:00 2001
From: Joe Sagawa 
Date: Sat, 19 Feb 2022 13:53:56 +0000
Subject: [PATCH 455/581] Translated using Weblate (Japanese)

Currently translated at 79.2% (2208 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 03cf2312cc0..0eae3a46e11 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2410,4 +2410,6 @@
     %sが参加しました。
     このルームで使用されている暗号化はサポートされていません
     暗号化が正しく設定されていません
+    %sにテキストメッセージを送信しました。メッセージに含まれた確認コードを入力してください。
+    選択したIDサーバーは利用規約がありません。サービス提供者を信頼しなければ続行しないほうがいいです
 
\ No newline at end of file

From 12f503932e2bb73e791400eb2eb24cba4335eb17 Mon Sep 17 00:00:00 2001
From: Joe Sagawa 
Date: Sat, 19 Feb 2022 13:55:51 +0000
Subject: [PATCH 456/581] Translated using Weblate (Japanese)

Currently translated at 79.3% (2210 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 0eae3a46e11..90d786a8e63 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2412,4 +2412,5 @@
     暗号化が正しく設定されていません
     %sにテキストメッセージを送信しました。メッセージに含まれた確認コードを入力してください。
     選択したIDサーバーは利用規約がありません。サービス提供者を信頼しなければ続行しないほうがいいです
+    現在IDさーバー%1$sにメールアドレスや電話番号を共有しています。共有を停止させるためには%2$sに再接続する必要があります。
 
\ No newline at end of file

From 4e494e53cded5269afa4800ab1b1f0059a7edd1a Mon Sep 17 00:00:00 2001
From: oksya8and8 
Date: Sat, 19 Feb 2022 13:55:49 +0000
Subject: [PATCH 457/581] Translated using Weblate (Japanese)

Currently translated at 79.3% (2210 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 90d786a8e63..109a38cc3d9 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2413,4 +2413,5 @@
     %sにテキストメッセージを送信しました。メッセージに含まれた確認コードを入力してください。
     選択したIDサーバーは利用規約がありません。サービス提供者を信頼しなければ続行しないほうがいいです
     現在IDさーバー%1$sにメールアドレスや電話番号を共有しています。共有を停止させるためには%2$sに再接続する必要があります。
+    部屋を作成し構成しました。
 
\ No newline at end of file

From e6398d6b750a60a1341df55422e51e82ff04b125 Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Sat, 19 Feb 2022 13:55:43 +0000
Subject: [PATCH 458/581] Translated using Weblate (Japanese)

Currently translated at 79.3% (2210 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 109a38cc3d9..748f3d33603 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2405,7 +2405,7 @@
     既読時間
     クロス証明を有効にする
     トピックを追加
-    %sが部屋を作成し構成しました。
+    %sはルームを作成し設定しました。
     参加しました。
     %sが参加しました。
     このルームで使用されている暗号化はサポートされていません

From c6a0ccf3688069c0fd8db9928979d8afabe9c508 Mon Sep 17 00:00:00 2001
From: oksya8and8 
Date: Sat, 19 Feb 2022 13:58:21 +0000
Subject: [PATCH 459/581] Translated using Weblate (Japanese)

Currently translated at 79.4% (2212 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 748f3d33603..96126a57600 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2414,4 +2414,6 @@
     選択したIDサーバーは利用規約がありません。サービス提供者を信頼しなければ続行しないほうがいいです
     現在IDさーバー%1$sにメールアドレスや電話番号を共有しています。共有を停止させるためには%2$sに再接続する必要があります。
     部屋を作成し構成しました。
+    QRコードを読み取り新しいダイレクトメッセージを作成する
+    キーのインポートに失敗しました
 
\ No newline at end of file

From 24acbcccc686f6ef98365d02fc6bf75170b47c48 Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Sat, 19 Feb 2022 13:57:32 +0000
Subject: [PATCH 460/581] Translated using Weblate (Japanese)

Currently translated at 79.4% (2212 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 96126a57600..2ce27cec310 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2412,8 +2412,8 @@
     暗号化が正しく設定されていません
     %sにテキストメッセージを送信しました。メッセージに含まれた確認コードを入力してください。
     選択したIDサーバーは利用規約がありません。サービス提供者を信頼しなければ続行しないほうがいいです
-    現在IDさーバー%1$sにメールアドレスや電話番号を共有しています。共有を停止させるためには%2$sに再接続する必要があります。
-    部屋を作成し構成しました。
+    現在IDサーバー %1$s でメールアドレスや電話番号を共有しています。共有を停止するには %2$s に再接続する必要があります。
+    ルームを作成し設定しました。
     QRコードを読み取り新しいダイレクトメッセージを作成する
     キーのインポートに失敗しました
 
\ No newline at end of file

From df72019e1d58f90f7575111ddc2313f76e5333bd Mon Sep 17 00:00:00 2001
From: oksya8and8 
Date: Sat, 19 Feb 2022 14:10:11 +0000
Subject: [PATCH 461/581] Translated using Weblate (Japanese)

Currently translated at 79.8% (2223 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 2ce27cec310..f8a05219228 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2416,4 +2416,5 @@
     ルームを作成し設定しました。
     QRコードを読み取り新しいダイレクトメッセージを作成する
     キーのインポートに失敗しました
+    Matrix IDで新しいダイレクトメッセージを作成する
 
\ No newline at end of file

From c78c20c06e077f52b6d6473ee9bdefccfb3ee5aa Mon Sep 17 00:00:00 2001
From: Joe Sagawa 
Date: Sat, 19 Feb 2022 14:09:49 +0000
Subject: [PATCH 462/581] Translated using Weblate (Japanese)

Currently translated at 79.8% (2223 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index f8a05219228..8ff8eeb66ff 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2417,4 +2417,8 @@
     QRコードを読み取り新しいダイレクトメッセージを作成する
     キーのインポートに失敗しました
     Matrix IDで新しいダイレクトメッセージを作成する
+    新しいダイレクトメッセージを作成
+    部屋作成メニューを閉じる…
+    部屋作成メニューを開く
+    サーバーの応答に時間がかかりすぎています。おそらく接続が遅すぎるからか、サーバー側でエラーが発生しているからです。後で再試行してください。
 
\ No newline at end of file

From 53061be8719151fe93312357cea168d686c25ed1 Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Sat, 19 Feb 2022 14:09:46 +0000
Subject: [PATCH 463/581] Translated using Weblate (Japanese)

Currently translated at 79.8% (2223 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 8ff8eeb66ff..a4957dc68e4 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -362,7 +362,7 @@
     ホーム
     会話なし
     端末の電話帳を${app_name}アプリが読み取ることは許可されていません
-    結果なし
+    結果がありません
     ルーム
     ルームディレクトリ
     ユーザーディレクトリ
@@ -2414,11 +2414,17 @@
     選択したIDサーバーは利用規約がありません。サービス提供者を信頼しなければ続行しないほうがいいです
     現在IDサーバー %1$s でメールアドレスや電話番号を共有しています。共有を停止するには %2$s に再接続する必要があります。
     ルームを作成し設定しました。
-    QRコードを読み取り新しいダイレクトメッセージを作成する
+    QRコードを読み取り、新しいダイレクトメッセージを作成
     キーのインポートに失敗しました
     Matrix IDで新しいダイレクトメッセージを作成する
     新しいダイレクトメッセージを作成
     部屋作成メニューを閉じる…
     部屋作成メニューを開く
     サーバーの応答に時間がかかりすぎています。おそらく接続が遅すぎるからか、サーバー側でエラーが発生しているからです。後で再試行してください。
+    このルームを%1$sから%2$sにアップグレードします。
+    スライドしてキャンセル
+    音声メッセージを録音しています
+    録音を削除
+    音声メッセージがアクティブの間は返信や編集はできません
+    この投票を削除してよろしいですか?一度削除すると復元することはできません。
 
\ No newline at end of file

From 28d0daf8755eb5ec314f0033daeea36358466fbb Mon Sep 17 00:00:00 2001
From: Joe Sagawa 
Date: Sat, 19 Feb 2022 14:13:51 +0000
Subject: [PATCH 464/581] Translated using Weblate (Japanese)

Currently translated at 80.2% (2236 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index a4957dc68e4..c9718663c3f 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2416,7 +2416,7 @@
     ルームを作成し設定しました。
     QRコードを読み取り、新しいダイレクトメッセージを作成
     キーのインポートに失敗しました
-    Matrix IDで新しいダイレクトメッセージを作成する
+    Matrix IDで新しいダイレクトメッセージを作成
     新しいダイレクトメッセージを作成
     部屋作成メニューを閉じる…
     部屋作成メニューを開く
@@ -2427,4 +2427,6 @@
     録音を削除
     音声メッセージがアクティブの間は返信や編集はできません
     この投票を削除してよろしいですか?一度削除すると復元することはできません。
+    共有データをの取扱に失敗しました
+    回転とクロップ
 
\ No newline at end of file

From da8d102ccad8d14c9d5262b495e8d621a294cfbb Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Sat, 19 Feb 2022 14:13:24 +0000
Subject: [PATCH 465/581] Translated using Weblate (Japanese)

Currently translated at 80.2% (2236 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index c9718663c3f..12c446428cf 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2429,4 +2429,15 @@
     この投票を削除してよろしいですか?一度削除すると復元することはできません。
     共有データをの取扱に失敗しました
     回転とクロップ
+    ルームを探す
+    既存のルームとスペースを追加
+    スレッドのメッセージを有効にする
+    おすすめに追加
+    おすすめから除外
+    ルームとスペースを管理
+    ホームに全てのルームを表示
+    おすすめ
+    実験したい気分ですか?
+\n既存のスペースを別のスペースに追加できます。
+    あなたのホームサーバーはまだスペースをサポートしていないようです
 
\ No newline at end of file

From 002246801839dc63557c3c29f66ff62332a690f9 Mon Sep 17 00:00:00 2001
From: oksya8and8 
Date: Sat, 19 Feb 2022 14:11:06 +0000
Subject: [PATCH 466/581] Translated using Weblate (Japanese)

Currently translated at 80.2% (2236 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 12c446428cf..3bc7a1593a0 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2440,4 +2440,5 @@
     実験したい気分ですか?
 \n既存のスペースを別のスペースに追加できます。
     あなたのホームサーバーはまだスペースをサポートしていないようです
+    画像を追加
 
\ No newline at end of file

From dc6fba26d87a6c081227ea2bf1366267cd545aed Mon Sep 17 00:00:00 2001
From: Joe Sagawa 
Date: Sat, 19 Feb 2022 14:20:50 +0000
Subject: [PATCH 467/581] Translated using Weblate (Japanese)

Currently translated at 80.6% (2247 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 3bc7a1593a0..fb9f2fdf2f2 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2441,4 +2441,14 @@
 \n既存のスペースを別のスペースに追加できます。
     あなたのホームサーバーはまだスペースをサポートしていないようです
     画像を追加
+    このコンテンツは不適切な投稿として報告されています。
+\n
+\nこのユーザーのコンテンツをこれ以上見たくなければ、ユーザーを無視してそのメッセージを非表示にできます。
+    このコンテンツはスパムとして報告されています。
+\n
+\nこのユーザのコンテンツをこれ以上見たくなければ、ユーザーを無視してそのメッセージを非表示にできます。
+    このコンテンツが報告されています。
+\n
+\nこのユーザーのコンテンツをこれ以上見たくなければ、ユーザーを無視してそのメッセージを非表示にできます。
+    %1$s%2$s
 
\ No newline at end of file

From cc779b8ce5c3dbc653ce5d7a1647bdfbc24168b6 Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Sat, 19 Feb 2022 14:20:04 +0000
Subject: [PATCH 468/581] Translated using Weblate (Japanese)

Currently translated at 80.6% (2247 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index fb9f2fdf2f2..0a05eb95fec 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2427,7 +2427,7 @@
     録音を削除
     音声メッセージがアクティブの間は返信や編集はできません
     この投票を削除してよろしいですか?一度削除すると復元することはできません。
-    共有データをの取扱に失敗しました
+    共有データの取り扱いに失敗しました
     回転とクロップ
     ルームを探す
     既存のルームとスペースを追加
@@ -2451,4 +2451,12 @@
 \n
 \nこのユーザーのコンテンツをこれ以上見たくなければ、ユーザーを無視してそのメッセージを非表示にできます。
     %1$s%2$s
+    質問あるいはトピック
+    投票の質問あるいはトピック
+    スペースのメンバーが非公開のルームを発見できるよう手伝う
+    少々お待ちください。少し時間がかかるかもしれません。
+    ルームのバージョン 👓
+    不安定
+    安定
+    スポイラー
 
\ No newline at end of file

From b9558bdf910858bd8a430d42d5d58a9d722d99fa Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Sat, 19 Feb 2022 14:34:15 +0000
Subject: [PATCH 469/581] Translated using Weblate (Japanese)

Currently translated at 81.8% (2279 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 37 ++++++++++++++++++++---
 1 file changed, 32 insertions(+), 5 deletions(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 0a05eb95fec..7649295cd1a 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -314,7 +314,7 @@
     招待された人のみ
     ルームのリンクを知っている人なら誰でも(ゲストユーザーを除く)
     ルームのリンクを知っている人なら誰でも(ゲストユーザーを含む)
-    再入室禁止されたメンバー
+    ブロックされたユーザー
     拡張設定
     このルームのサーバー内識別ID
     ラボ
@@ -347,7 +347,7 @@
     発言更新を確認
     暗号解除されたソースコードを表示
     名前変更
-    切断中
+    オフライン
     会話を開始
     音声通話を開始
     ビデオ通話を開始
@@ -2418,8 +2418,8 @@
     キーのインポートに失敗しました
     Matrix IDで新しいダイレクトメッセージを作成
     新しいダイレクトメッセージを作成
-    部屋作成メニューを閉じる…
-    部屋作成メニューを開く
+    ルーム作成メニューを閉じる…
+    ルーム作成メニューを開く
     サーバーの応答に時間がかかりすぎています。おそらく接続が遅すぎるからか、サーバー側でエラーが発生しているからです。後で再試行してください。
     このルームを%1$sから%2$sにアップグレードします。
     スライドしてキャンセル
@@ -2446,7 +2446,7 @@
 \nこのユーザーのコンテンツをこれ以上見たくなければ、ユーザーを無視してそのメッセージを非表示にできます。
     このコンテンツはスパムとして報告されています。
 \n
-\nこのユーザのコンテンツをこれ以上見たくなければ、ユーザーを無視してそのメッセージを非表示にできます。
+\nこのユーザーのコンテンツをこれ以上見たくなければ、ユーザーを無視してそのメッセージを非表示にできます。
     このコンテンツが報告されています。
 \n
 \nこのユーザーのコンテンツをこれ以上見たくなければ、ユーザーを無視してそのメッセージを非表示にできます。
@@ -2459,4 +2459,31 @@
     不安定
     安定
     スポイラー
+    生体認証を有効にする
+    %1$sによりブロック
+    招待を取り消す
+    サーバーのバージョン
+    サーバー名
+    電話番号が正しくありません。確認してください
+    カスタムホームサーバーを選択
+    Element Matrix Servicesを選択
+    再送信
+    コードを入力
+    電話番号
+    退席中
+    オフライン
+    オンライン
+    一般
+    転送
+    信頼済
+    検証済
+    未送信のメッセージを削除
+    カスタムイベントを送信
+    ルームの状態を調べる
+    開封確認メッセージを表示
+    通知しない
+    ファイルから鍵をインポート
+    未確認
+    制限は不明です。
+    サーバーのファイルアップロードの制限
 
\ No newline at end of file

From 376057b7989e8782e7c25474c318f623538459e7 Mon Sep 17 00:00:00 2001
From: Joe Sagawa 
Date: Sat, 19 Feb 2022 14:31:14 +0000
Subject: [PATCH 470/581] Translated using Weblate (Japanese)

Currently translated at 81.8% (2279 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 7649295cd1a..75d5a5341fb 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2458,7 +2458,7 @@
     ルームのバージョン 👓
     不安定
     安定
-    スポイラー
+    ネタバレ
     生体認証を有効にする
     %1$sによりブロック
     招待を取り消す
@@ -2486,4 +2486,11 @@
     未確認
     制限は不明です。
     サーバーのファイルアップロードの制限
+    自分の会話は自分のものにしましょう。
+    %1$sが部屋を招待されたユーザーにのみアクセスできるように設定しました。
+    %1$sが部屋をリンクを持っているユーザーにアクセスできるように設定しました。
+    選択したメッセージをネタバレとして送信
+    ${app_name} がE2E暗号鍵をディスクに保存する許可を要求しています。
+\n
+\n暗号鍵を手動でエクスポートできるように、次のポップアップでアクセスを許可してください。
 
\ No newline at end of file

From 4dc15f99a86e04b33eb8d288e4c323a15d67a71e Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Sat, 19 Feb 2022 14:34:18 +0000
Subject: [PATCH 471/581] Translated using Weblate (Japanese)

Currently translated at 81.8% (2279 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 75d5a5341fb..fd2e153059c 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2493,4 +2493,5 @@
     ${app_name} がE2E暗号鍵をディスクに保存する許可を要求しています。
 \n
 \n暗号鍵を手動でエクスポートできるように、次のポップアップでアクセスを許可してください。
+    %1$sがルームをリンクを持っているユーザーにアクセスできるように設定しました。
 
\ No newline at end of file

From 1a5b0d2e825d8144c3d29342a230ea541cbee964 Mon Sep 17 00:00:00 2001
From: metatek 
Date: Sat, 19 Feb 2022 14:56:44 +0000
Subject: [PATCH 472/581] Translated using Weblate (Japanese)

Currently translated at 82.1% (2289 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index fd2e153059c..d83ddcdd309 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2494,4 +2494,5 @@
 \n
 \n暗号鍵を手動でエクスポートできるように、次のポップアップでアクセスを許可してください。
     %1$sがルームをリンクを持っているユーザーにアクセスできるように設定しました。
+    まず、設定でIDサーバーの条件に同意してください。
 
\ No newline at end of file

From 21401430b22e9f1fd90839a249f158cd42a1b8ad Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Sat, 19 Feb 2022 14:56:23 +0000
Subject: [PATCH 473/581] Translated using Weblate (Japanese)

Currently translated at 82.1% (2289 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 20 +++++++++++++++-----
 1 file changed, 15 insertions(+), 5 deletions(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index d83ddcdd309..525e481b683 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2316,7 +2316,7 @@
     私たちは情報を第三者と共有することはありません
     私たちはアカウントのデータを記録したり分析したりすることはありません
     Elementの改善を手伝う
-    リンクを知っている人を対象に、このルームを公開しました。
+    リンクを知っている人がアクセスできるようにこのルームを設定しました。
     どのユーザーも無視していません
     キーワードを入力するとリアクションを検索できます。
     変更を加えませんでした
@@ -2366,8 +2366,8 @@
     誰でもこのスペースを発見し参加できます
     法的情報
     ユーザーに関する情報を表示します
-    この部屋においてのみアバターを変更します
-    この部屋においてのみ表示名を変更します
+    このルームにおいてのみアバターを変更します
+    このルームにおいてのみ表示名を変更します
     ユーザーの無視を解除し、これからのメッセージを表示します
     続行するには%sを入力してください
     リカバリーパスフレーズ
@@ -2487,12 +2487,22 @@
     制限は不明です。
     サーバーのファイルアップロードの制限
     自分の会話は自分のものにしましょう。
-    %1$sが部屋を招待されたユーザーにのみアクセスできるように設定しました。
+    %1$sは、このルームを招待者のみ参加可能に設定しました。
     %1$sが部屋をリンクを持っているユーザーにアクセスできるように設定しました。
     選択したメッセージをネタバレとして送信
     ${app_name} がE2E暗号鍵をディスクに保存する許可を要求しています。
 \n
 \n暗号鍵を手動でエクスポートできるように、次のポップアップでアクセスを許可してください。
-    %1$sがルームをリンクを持っているユーザーにアクセスできるように設定しました。
+    %1$sはリンクを知っている人がアクセスできるようにこのルームを設定しました。
     まず、設定でIDサーバーの条件に同意してください。
+    初めにIDサーバーを設定してください。
+    
+        %1$d個の投票があります。結果を見るには投票してください
+    
+    信頼済としてマーク
+    未検証の端末で暗号化
+    メッセージを紙吹雪と共に送る
+    メッセージを降雪と共に送る
+    紙吹雪🎉を送る
+    降雪❄️を送る
 
\ No newline at end of file

From c6e712ee7b15f54b2e2cfb0cfd5a0de80eae0dca Mon Sep 17 00:00:00 2001
From: Joe Sagawa 
Date: Sat, 19 Feb 2022 14:49:25 +0000
Subject: [PATCH 474/581] Translated using Weblate (Japanese)

Currently translated at 82.1% (2289 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 525e481b683..c0b1141a727 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2505,4 +2505,11 @@
     メッセージを降雪と共に送る
     紙吹雪🎉を送る
     降雪❄️を送る
+    あなたのチームのメッセージングに。
+    エンドツーエンド暗号化され、電話番号不要。広告やデータマイニング無し。
+    会話の保存先を自分で決められ、自分で管理できる独立したコミュニケーション。Matrixを基に。
+    お宅での対面会話と同じぐらいのプライバシーを提供する、セキュアで独立したコミュニケーション。
+    チームワークを効率よくしましょう。
+    セキュアメッセージング
+    管理権を握るのはあなたです。
 
\ No newline at end of file

From 7083d7dd7263ee788d16943944f39c4278fcea1c Mon Sep 17 00:00:00 2001
From: Vladyslav Stepanov 
Date: Fri, 11 Feb 2022 18:54:32 +0000
Subject: [PATCH 475/581] Translated using Weblate (Russian)

Currently translated at 100.0% (2785 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ru/
---
 vector/src/main/res/values-ru/strings.xml | 650 +++++++++++-----------
 1 file changed, 325 insertions(+), 325 deletions(-)

diff --git a/vector/src/main/res/values-ru/strings.xml b/vector/src/main/res/values-ru/strings.xml
index 1ec34777dbc..f164afc11bf 100644
--- a/vector/src/main/res/values-ru/strings.xml
+++ b/vector/src/main/res/values-ru/strings.xml
@@ -298,14 +298,14 @@
     Поделиться
     Позже
     Переслать
-    Копировать ссылку
+    Постоянная ссылка
     Просмотр исходного кода
     Просмотр расшифрованного исходного кода
     Удалить
     Переименовать
     Пожаловаться на содержимое
     Активный вызов
-    Ongoing conference call.
+    Текущая конференц-звонок.
 \nПрисоединиться как %1$s или %2$s
     Голос
     Видео
@@ -335,10 +335,10 @@
     Избранные
     Люди
     Комнаты
-    Фильтр названия комнаты
+    Фильтр названий комнат
     Фильтр избранного
     Фильтр людей
-    Фильтр названия комнаты
+    Фильтр названий комнат
     Приглашения
     Маловажные
     Беседы
@@ -348,7 +348,7 @@
     Вы не дали доступ ${app_name} к внутренним контактам
     Нет результатов
     Комнаты
-    Список комнат
+    Каталог комнат
     Нет комнат
     Публичные комнаты недоступны
     
@@ -359,12 +359,12 @@
     
     Отправить логи
     Отправить журналы ошибок
-    Отправить скриншот
+    Отправить снимок экрана
     Сообщить об ошибке
-    Пожалуйста опишите ошибку. Что вы делали? Что вы ожидали получить? Что произошло?
+    Пожалуйста опишите ошибку. Что вы делали\? Что вы хотели получить\? Что на самом деле произошло\?
     Опишите проблему здесь
     Для диагностики проблемы журналы работы приложения будут приложены к отчету об ошибке. Этот отчет, включая журналы и скриншот, не будет доступен публично. Если вы предпочитаете отправить только текст, то снимите отметку:
-    Похоже вы разочарованы, раз трясёте телефоном. Хотите сообщить об ошибке?
+    Вы, наверное, трясете телефон в отчаянии. Хотите открыть экран отчета об ошибке\?
     Отчет об ошибке успешно отправлен
     Сбой отправки отчета об ошибке (%s)
     Прогресс (%s%%)
@@ -386,65 +386,65 @@
     Камера
     Вход
     Создать аккаунт
-    Отправить
+    Подать
     Пропустить
     Выслать письмо для сброса
     Вернуться на экран входа
-    Электронная почта или логин
+    Электронная почта или имя пользователя
     Пароль
     Новый пароль
-    Логин
+    Имя пользователя
     Адрес электронной почты
     Адрес электронной почты (не обязательно)
     Номер телефона
-    Номер телефона (по желанию)
+    Номер телефона (необязательно)
     Повтор пароля
     Подтвердите ваш новый пароль
-    Неверный логин и/или пароль
+    Неправильный логин и/или пароль
     Логин может содержать только буквы, цифры, точки, дефис и подчеркивание
     Короткий пароль (минимум 6 символов)
     Отсутствует пароль
-    Это не похоже на адрес электронной почты
-    Введен некорректный номер телефона
+    Это не похоже на действительный адрес электронной почты
+    Это не похоже на действительный номер телефона
     Этот адрес электронной почты уже используется.
-    Отсутствует email
+    Отсутствует адрес электронной почты
     Отсутствует номер телефона
-    Отсутствует номер телефона или email
+    Отсутствует номер телефона или электронной почты
     Токен не действителен
     Пароли не совпадают
     Забыли пароль?
-    Использовать особые параметры сервера
-    Проверьте email для продолжения регистрации
-    Регистрация одновременно по email и номеру телефона пока не поддерживается. Только номер телефона будет связан с аккаунтом. 
+    Использовать пользовательские параметры сервера(расширенный)
+    Проверьте адрес электронной почты для продолжения регистрации
+    Регистрация одновременно по электронной почте и номеру телефона пока не поддерживается. Только номер телефона будет связан с аккаунтом.
 \n
-\nВы можете добавить свой email в настройках профиля.
+\nВы можете добавить свой электронный адрес в настройках профиля.
     Этот домашний сервер хочет убедиться, что вы не робот
     Логин уже используется
     Домашний сервер:
-    Сервер идентификации:
-    Я проверил мой email адрес
-    Для сброса пароля введите email привязанный к учетной записи:
-    Должен быть введен email привязанный к учетной записи.
+    Сервер опознавания:
+    Я проверил мой адрес электронной почты
+    Для сброса пароля введите адрес электронной почты привязанный к учетной записи:
+    Должен быть введен адрес электронной почты привязанный к учетной записи.
     Должен быть введен новый пароль.
     На адрес %s было отправлено письмо. После перехода по ссылке в письме, нажмите ниже.
-    Не удалось проверить email: убедитесь, что вы перешли по присланной ссылке
-    Ваш пароль сброшен. 
+    Не удалось проверить адрес электронной почты: убедитесь, что вы перешли по ссылке из сообщения
+    Ваш пароль сброшен.
 \n
-\nОсуществлен выход на всех сессиях - вы не будете получать push уведомления. Для включения push уведомлений заново войдите на каждом из ваших устройств.
+\nОсуществлен выход на всех сессиях - вы не будете получать всплывающие уведомления. Для включения всплывающие уведомлений заново войдите на каждом из ваших устройств.
     URL должен начинаться с http[s]://
     Сбой входа: сетевая ошибка
     Сбой входа
     Сбой регистрации: сетевая ошибка
     Сбой регистрации
-    Сбой регистрации: ошибка проверки email
+    Сбой регистрации: ошибка проверки электронного ящика
     Пожалуйста, введите корректный URL
     Неверное имя пользователя или пароль
-    Указанный токен доступа не распознан
-    Поврежденный JSON
-    Не JSON
+    Указанный токен доступа не был распознан
+    Неверный формат JSON
+    Не содержит допустимого JSON
     Отправлено слишком много запросов
     Логин уже используется
-    Вы не перешли по высланной в email ссылке
+    Вы еще не перешли по высланной ссылке
     Чтение списка вступивших
     Отправить как
     Оригинал
@@ -459,21 +459,21 @@
     Сегодня
     Название комнаты
     Тема комнаты
-    Вызов соединён
+    Вызов установлен
     Устанавливается соединение…
-    Вызов закончен
-    Звонок…
+    Вызов завершен
+    Звоним…
     Входящий вызов
     Входящий видеовызов
     Входящий голосовой вызов
     Идёт разговор…
-    Вызываемый абонент не отвечает.
+    Вызываемый абонент не смог ответить.
     Медиавызов не удался
     Невозможно инициализировать камеру
     Звонок принят на другом устройстве
     Снять фото или видео"
     Не удалось записать видео"
-    Element Информация
+    Информация
     ${app_name} нуждается в разрешении на доступ к вашей фото-и видеотеке для отправки и сохранения вложений.
 \n
 \nПожалуйста, разрешите доступ в следующем всплывающем окне, чтобы иметь возможность отправлять файлы с вашего устройства.
@@ -488,7 +488,7 @@
     ${app_name} может проверить Вашу адресную книгу, чтобы найти других пользователей сети по email или телефонному номеру.
 \n
 \nСогласны ли вы поделиться своей адресной книгой для этой цели\?
-    Извините. Действие не выполнено из-за недостаточных разрешений
+    Извините. Действие не выполнено из-за отсутствия на него разрешений
     Сохранено
     Сохранить в загрузки?
     ДА
@@ -501,10 +501,10 @@
     Перейти к непрочитанному
     %s пригласил вас присоединиться к этой комнате
     Приглашение пришло на адрес %s, который не связан с этим аккаунтом.
-\nВозможно, вы захотите войти в систему с другим аккаунтом или добавить этот email в свою учетную запись.
+\nВозможно, вы захотите войти в систему с другим аккаунтом или добавить этот электронный ящик в свою учетную запись.
     Вы пытаетесь получить доступ к %s. Хотите присоединиться к обсуждению?
     комната
-    Это пред. просмотр комнаты. Вы в режиме только чтения.
+    Это предпросмотр комнаты. Вы в режиме только чтения.
     Новый чат
     Добавить участника
     1 пользователь
@@ -516,7 +516,7 @@
     Покой
     АДМИНИСТРИРОВАНИЕ
     ВЫЗОВ
-    ПРЯМЫЕ ЧАТЫ
+    ПРЯМЫЕ СООБЩЕНИЯ
     СЕССИИ
     Пригласить
     Покинуть этот чат
@@ -527,25 +527,25 @@
     Сделать администратором
     Скрыть все сообщения этого пользователя
     Отобразить все сообщения этого пользователя
-    ID пользователя, имя или email
+    ID пользователя, имя или электронный адрес
     Упомянуть
     Отобразить список сессий
     Вы не сможете отменить это действие, поскольку пользователь получит такой же уровень доступа, как и у вас. 
 \nВы уверены\?
     "Вы уверены что хотите пригласить %s в этот чат?"
     Пригласить по ID
-    Локальные Контакты (%d)
-    Только зарегистрированные
+    ЛОКАЛЬНЫЕ КОНТАКТЫ (%d)
+    Только пользователи Matrix
     Пригласить пользователя по ID
-    Пожалуйста, введите один или несколько адресов email или Matrix ID
-    Email или Matrix ID
+    Пожалуйста, введите один или несколько электронных адресов или Matrix ID
+    Электронный почтовый ящик или Matrix ID
     Поиск
     %s печатает…
     %1$s & %2$s печатают…
     %1$s & %2$s & и другие печатают…
     Отправить зашифрованное сообщение…
-    Отправить сообщение…
-    Соединение с сервером потеряно.
+    Отправить сообщение(не зашифрованное)…
+    Связь с сервером утеряна.
     Сообщения не отправлены. %1$s или %2$s сейчас?
     Сообщения, не отправлены из-за присутствия неизвестных сессий. %1$s или %2$s сейчас\?
     Повторить отправку
@@ -553,7 +553,7 @@
     Отправить неотправленные сообщения
     Удалить неотправленные сообщения
     Файл не найден
-    У вас нет прав писать сообщения в этом чате.
+    У вас нет разрешения писать сообщения в этой комнате.
     Доверять
     Не доверять
     Выйти
@@ -561,8 +561,8 @@
     Отпечаток (%s):
     Не удалось проверить сертификат удаленного сервера.
     Это может означать, что кто-то злонамеренно перехватывает ваш трафик или что ваш телефон не доверяет сертификату, предоставленному удаленным сервером.
-    Если администратор сервера сказал, что это ожидается, убедитесь, что отпечаток сертификата ниже соответствует отпечатку сертификата, предоставленному администратором.
-    Сертификат сервера изменился и ваш телефон теперь ему не доверяет. Это ОЧЕНЬ ПОДОЗРИТЕЛЬНО. Рекомендуется, НЕ ДОВЕРЯТЬ этому новому сертификату.
+    Если администратор сервера сказал, что это ожидалось, убедитесь, что отпечаток сертификата ниже совпадает с отпечатком сертификата предоставленным администратором.
+    Сертификат был изменен с того, которому доверял ваш телефон. Это ОЧЕНЬ НЕОБЫЧНО. Рекомендуется НЕ ПРИНИМАТЬ этот новый сертификат.
     Сертификат изменился с ранее доверенного на недействительный. Возможно, сервер обновил свой сертификат. Свяжитесь с администратором сервера для получения ожидаемого отпечатка сертификата.
     Примите сертификат только если администратор сервера опубликовал отпечаток сертификата, который соответствует указанному выше.
     Подробности комнаты
@@ -572,7 +572,7 @@
     Некорректный ID. Используйте email или Matrix ID вида \'@localpart:domain\'
     ПРИГЛАШЕНЫ
     ПРИСОЕДИНИЛИСЬ
-    Причина отчета о контенте
+    Причина жалобы об этом содержимом
     Вы хотите скрыть все сообщения этого пользователя\? 
 \n
 \nУчтите, что это действие перезапустит приложение и может занять некоторое время.
@@ -601,8 +601,8 @@
     Политика конфиденциальности
     Аватар
     Отображаемое имя
-    Email
-    Добавить email
+    Электронный ящик
+    Добавить электронный адрес
     Телефон
     Добавить телефон
     Системные настройки приложения.
@@ -615,7 +615,7 @@
     Когда меня приглашают в комнату
     Вызовы
     Сообщения от бота
-    Синхронизация
+    Фоновая синхронизация
     Включить фоновую синхронизацию
     Таймаут синхронизации
     Задержка между каждой синхронизацией
@@ -635,7 +635,7 @@
     Устройства для уведомлений
     Локальные контакты
     Доступ к контактам
-    Страна для контактов
+    Страна контактов
     Домашний экран
     Прикрепить комнаты с отключенными уведомлениями
     Прикрепить комнаты с непрочитанными сообщениями
@@ -644,16 +644,16 @@
     ID
     Публичное имя
     Обновить публичное имя
-    Последнее подключение
+    Недавно
     %1$s @ %2$s
     Для этой операции требуется дополнительная проверка подлинности. 
 \nЧтобы продолжить, введите пароль.
     Аутентификация
     Пароль:
-    Отправить
+    Подать
     Авторизован как
     Домашний сервер
-    Сервер идентификации
+    Сервер опознавания
     Ожидается подтверждение
     Проверьте электронную почту и перейдите по высланной ссылке. Затем нажмите продолжить.
     Не удалось подтвердить адрес электронной почты. Проверьте электронную почту и нажмите на содержащуюся ссылку. После этого нажмите продолжить.
@@ -699,11 +699,11 @@
     Только участники (с момента выбора этой опции)
     Только участники (с момента приглашения)
     Только участники (с момента присоединения)
-    Для генерации ссылки команда должна иметь адрес.
-    Только приглашенные
+    Чтобы подключиться к комнате, у нее должен быть адрес.
+    Только те, кого пригласили
     Все у кого есть ссылка на комнату, кроме гостей
     Все у кого есть ссылка на комнату, включая гостей
-    Забаненные пользователи
+    Заблокированные пользователи
     Дополнительно
     Внутренний ID комнаты
     Адреса
@@ -770,8 +770,8 @@
     Блокировать
     Разрешить
     Проверить сессию
-    Чтобы убедиться, что этой сессии можно доверять, обратитесь к его владельцу, используя другие способы (например, лично или по телефону), и спросите, соответствует ли ключ, который он видит в настройках для этой сессии:
-    Если совпадает, то нажмите кнопку подтвердить ниже. Если не совпадает, возможно кто-то пытается перехватить сессию и вы захотите добавить его в черный список. В будущем данный процесс будет улучшен.
+    Чтобы убедиться, что этой сессии можно доверять, обратитесь к ее владельцу, используя другие способы (например, лично или по телефону), и спросите, соответствует ли ключ, который он видит в настройках для этой сессии:
+    Если они не совпадают, безопасность вашего общения может быть поставлена под угрозу.
     Я проверил, что ключи совпадают
     Комната содержит неизвестные сессии
     Эта комната содержит неизвестные сессии, которые не были подтверждены. 
@@ -779,7 +779,7 @@
 \nПеред продолжением рекомендуем вам пройти процесс проверки для каждой сессии, но вы можете отправить сообщение повторно, не проверяя.
 \n
 \nНеизвестные сессии:
-    Выбор списка комнат
+    Выбор каталога комнат
     Сервер возможно недоступен или перегружен
     Введите домашний сервер для отображения списка публичных комнат
     Имя сервера
@@ -794,7 +794,7 @@
     3 дня
     1 неделя
     1 месяц
-    Постоянно
+    Навсегда
     Очистить медиа кэш
     Недоступен
     Каталог пользователей
@@ -832,7 +832,7 @@
     Мониторинг событий
     Вызов
     Сообщения, содержащие мое имя пользователя
-    Вы добавили новою сессию \'%s\', запрашивающая ключи шифрования.
+    Вы добавили новою сессию \'%s\', запрашивающую ключи шифрования.
     Ваше непроверенная сессия \'%s\' запрашивает ключи шифрования.
     Сообщения, содержащие мое отображаемое имя
     Начать проверку
@@ -845,7 +845,7 @@
     Аналитика
     Использовать встроенную камеру
     Сообщить об ошибке
-    Внимание!
+    Предупреждение!
     Конференц-связь находится в разработке и может быть ненадежной.
     Ошибка команды
     Нераспознанная команда: %s
@@ -856,7 +856,7 @@
     Загрузка…
     Закрыть приложение
     Сообщества
-    Поиск сообществ
+    Фильтр сообществ
     Пригласить
     Сообщества
     Нет групп
@@ -864,13 +864,13 @@
     Вы уверены, что хотите начать голосовой вызов?
     Вы уверены, что хотите начать видеовызов?
     Список групп
-    Блокировка пользователя удалит его из этой комнаты и не позволит ему присоединиться к ней снова.
+    Блокировка пользователя удалит его из этой комнаты и не позволит ему присоединиться вновь.
     Все сообщения (громко)
     Все сообщения
     Только упоминания
     Без звука
     Добавить на главный экран
-    Предпросмотр URL-адресов
+    Предпросмотр встроенных URL-адресов
     Вибрация при упоминании пользователя
     Уведомления
     Новый ID сообщества (например +foo:matrix.org)
@@ -878,22 +878,22 @@
     \'%s\' недействительный ID сообщества
     Создать
     Создать сообщество
-    Имя сообщества
+    Название сообщества
     Пример
     ID сообщества
     пример
     Начало
     Люди
     Комнаты
-    Нет пользователей
+    Пользователей нет
     Комнаты
     Присоединился
     Приглашен
     Фильтр участников группы
     Фильтр комнат группы
-    Администратор сообщества не предоставил подробного описания этого сообщества.
+    Администратор сообщества не предоставил подробного описания для этого сообщества.
     Вас выгнал %2$s из %1$s
-    Вас забанил %2$s в %1$s
+    Вас заблокировал %2$s в %1$s
     Причина: %1$s
     Присоединиться снова
     Забыть комнату
@@ -966,7 +966,7 @@
     
     Получить аватар
     Заметка аватара
-    Сообщества
+    Чутье
     Эта комната не показывает любые сообщества
     Конфиденциальность уведомлений
     Нормальный
@@ -994,17 +994,17 @@
     Да, я хочу помочь!
     Обязательный параметр отсутствует.
     Параметр недействителен.
-    Для продолжения использования этого сервера %1$s вы должны ознакомиться и принять Условия использования.
-    Ознакомиться сейчас
+    Для продолжения использования %1$s вы должны ознакомиться и согласиться с условиями и правилами использования этого домашнего сервера.
+    Посмотреть сейчас
     Деактивировать аккаунт
-    Пожалуйста, удалите все сообщения, которые я отправил, после деактивации моего аккаунта (предупреждение: будущие участники увидят неполную историю разговоров)
+    Пожалуйста, удалите все сообщения, которые я отправил, после деактивации моего аккаунта (Предупреждение: будущие участники увидят неполную историю разговоров)
     Чтобы продолжить, введите пароль:
     Деактивировать аккаунт
-    Это действие сделает вашу учетную запись непригодной для дальнейшего использования. Вы не сможете войти в систему и никто другой не сможет заново зарегистрировать учетную запись с вашим идентификатором. Также, это приведет к тому, что вы покинете все комнаты, в которых участвовали и данные о вашей учетной записи будут удалены с сервера идентификации. Это действие необратимо. 
+    Это действие сделает вашу учетную запись навсегда непригодной для дальнейшего использования. Вы не сможете войти в систему и никто другой не сможет заново зарегистрировать учетную запись с вашим идентификатором. Также, это приведет к тому, что вы покинете все комнаты, в которых участвовали и данные о вашей учетной записи будут удалены с сервера идентификации. Это действие необратимо.
 \n
-\nПо умолчанию, деактивация вашей учетной записи не удаляет отправленные вами сообщения. Если вы хотите, чтобы мы удалили все ваши сообщения - поставьте отметку в поле ниже. 
+\nПо умолчанию, деактивация вашей учетной записи не удаляет отправленные вами сообщения. Если вы хотите, чтобы мы удалили все ваши сообщения - поставьте отметку в поле ниже.
 \n
-\nВидимость сообщений в Matrix похожа на электронную почту. Удаление ваших сообщений означает, что отправленные вами сообщения не будут показаны новым или не зарегистрированным пользователям, но те пользователи, которые уже получили эти сообщения - по прежнему будут их видеть.
+\nВидимость сообщений в Matrix похожа на электронную почту. Удаление ваших сообщений означает, что отправленные вами сообщения не будут показаны новым или не зарегистрированным пользователям, но те пользователи, которые уже получили эти сообщения - по прежнему будут видеть их копии.
     Лицензии сторонних производителей
     Скачать
     Говорить
@@ -1016,7 +1016,7 @@
     Введите здесь…
     Отправить голосовое сообщение
     продолжить с…
-    К сожалению, для выполнения этого действия не найдено внешнее приложение.
+    К сожалению, внешнее приложение для выполнения этого действия не найдено.
     Отправлять голосовые сообщения
     Пожалуйста, введите ваш пароль.
     Если возможно, добавьте описание на английском языке.
@@ -1024,17 +1024,17 @@
     Отправить ответ (незашифрованный)…
     Предварительный просмотр медиа перед отправкой
     В настоящее время вы не являетесь участником каких-либо сообществ.
-    Использовать клавишу Enter для отправки сообщения
+    Использовать клавишу ввода для отправки сообщения
     Отображает действие
-    Банит пользователя с указанным ID
-    Разбанит пользователя с указанным ID
+    Блокирует пользователя с данным ID
+    Разблокирует пользователя с данным ID
     Определение уровня доступа пользователя
     Сбросить уровень доступа для пользователя с данным ID
     Пригласить пользователя с данным ID в текущую комнату
     Покинуть комнату
     Задать тему комнаты
-    Выкинуть пользователя с заданным ID
-    Изменить ваш псевдоним
+    Выгнать пользователя с заданным ID из этой комнаты
+    Изменить ваше отображаемое имя
     Вкл/выкл markdown
     Эта комната была заменена и больше не активна.
     Этот разговор продолжается здесь
@@ -1113,10 +1113,10 @@
     свернуть
     Всё равно позвонить
     Пароль
-    Пожалуйста ознакомьтесь и подтвердите согласие с политикой этого сервера:
+    Пожалуйста, ознакомьтесь и примите правила этого домашнего сервера:
     Вызовы
     Использовать стандартную мелодию ${app_name} для входящих звонков
-    Мелодия звонка
+    Мелодия входящего вызова
     Выберите мелодию звонка:
     Идёт видеозвонок …
     Удалить из чата
@@ -1130,7 +1130,7 @@
     Произошла ошибка при подтверждении вашего номера телефона.
     Дополнительная информация: %s
     Эта опция требует стороннего приложения для записи сообщений.
-    Команде \"%s\" нужно больше параметров, или некоторые параметры неверны.
+    Команде \"%s\" нужно либо больше параметров, либо некоторые из них неверны.
     Markdown включен.
     Markdown выключен.
     Всегда
@@ -1199,7 +1199,7 @@
     Показывать события аккаунта
     Включает изменения аватара и отображаемого имени.
     Необходимо минимизировать влияние на фоновое соединение для надёжности уведомлений.
-\nНа следующем экране вам будет предложено разрешить ${app_name} всегда работать в фоновом режиме, пожалуйста, примите.
+\nНа следующем экране вам будет предложено разрешить ${app_name} всегда работать в фоновом режиме, пожалуйста, согласитесь.
     Использовать системную камеру вместо камеры Element.
     Показать информацию
     %1$s:
@@ -1207,7 +1207,7 @@
     +%d
     %d+
     Не найден APK сервисов Google Play. Уведомления могут работать неправильно.
-    Не влияет на приглашения, удаления и запреты.
+    Не влияет на приглашения, изгнания и блокировки.
     Резервное копирование ключей
     Используйте резервную копию ключа
     Резервное копирование ключей не завершено, пожалуйста, подождите…
@@ -1253,7 +1253,7 @@
     Чтобы использовать резервную копию ключа в этой сессии, восстановите его с помощью своей парольной фразы или ключа восстановления.
     Резервная копия имеет недействительную подпись из подтвержденной сессии %s
     Резервная копия имеет действительную подпись из неподтвержденной сессии %s
-    Резервная копия имеет действительно подпись из подтверждённой сессии %s.
+    Резервная копия имеет действительную подпись из подтверждённой сессии %s.
     Резервная копия имеет действительную подпись с этой сессии.
     Резервная копия подписана сессией с идентификатором %s.
     Резервные копии ключей этой сессии не сохраняются.
@@ -1261,7 +1261,7 @@
     Резервное копирование ключей успешно настроено для этой сессии.
     Удалить резервную копию
     Восстановить из резервной копии
-    Сессионное шифрование не активировано
+    Шифрование сессии не активировано
     Пожалуйста, введите ключ восстановления
     Разблокировать историю
     Восстановление резервной копии:
@@ -1271,7 +1271,7 @@
     Если вы не знаете вашу парольную фразу для восстановления, вы можете %s.
     используйте ключ восстановления
     Вы можете потерять доступ к сообщениям, если выйдете из системы или потеряете это устройство.
-    Запрашивает версию резервной копии…
+    Получение версии резервной копии…
     Используйте парольную фразу для разблокировки истории зашифрованных сообщений
     Потеряли ключ восстановления? В настройках вы можете создать новый.
     Ошибка сети: проверьте соединение и повторите попытку.
@@ -1281,9 +1281,9 @@
     Не удалось получить последнюю версию ключей восстановления (%s).
     
         %d новый ключ был добавлен к этому устройству.
-        %d новых ключа были добавлены к этому устройству.
-        %d новых ключей были добавлены к этому устройству.
-        
+        %d новых ключа были добавлены к этим устройствам.
+        %d новых ключей были добавлены к этим устройствам.
+        %d новых ключей были добавлены к этим устройствам.
     
     
         Восстановлена резервная копия с %d ключом.
@@ -1291,7 +1291,7 @@
         Восстановлены резервные копии с %d ключами.
     
     Невозможно расшифровать резервную копию с помощью этого ключа восстановления: убедитесь, что вы ввели правильный ключ.
-    Невозможно расшифровать резервную копию с помощью данного пароля: убедитесь, что вы ввели верный пароль.
+    Невозможно расшифровать резервную копию с помощью этого пароля: убедитесь, что вы ввели правильный пароль.
     Ключи шифрования копируются на сервер в фоновом режиме. Первое копирование может занять несколько минут.
     Генерация ключей восстановления с использованием парольной фразы может занять несколько секунд.
     Ключ восстановления был сохранен в \'%s\'. 
@@ -1392,7 +1392,7 @@
     Мне
     Использовать настройку
     Проверить сессию
-    Ваше устройство использует устаревший TLS протокол, уязвимый для атак, для вашей же безопасности вам отказано в подключении
+    Ваше устройство использует устаревший протокол безопасности TLS, уязвимый для атак, в целях безопасности вы не сможете подключиться
     Приложениям не нужно подключаться к домашнему серверу в фоновом режиме, это должно снизить расход заряда батареи
     Клавиша Ввод отправит сообщение вместо переноса строки
     Воспроизвести звук затвора
@@ -1428,7 +1428,7 @@
     В ожидании партнера, чтобы подтвердить …
     Проверено!
     Вы успешно подтвердили эту сессию.
-    Защищенные сообщения от этого пользователя шифруются end-to-end и не могут быть прочитаны третьими лицами.
+    Сообщения от этого пользователя защищены сквозным шифрованием и не могут быть прочитаны третьими лицами.
     Понял
     Ничего не появляется\? Не все клиенты пока поддерживают интерактивную проверку. Используйте устаревшую проверку.
     Используйте устаревшую проверку.
@@ -1440,11 +1440,11 @@
 \nПричина: %s
     Интерактивное подтверждение сессии
     Запрос на подтверждение
-    %s желает подтвердить ваше устройство
+    %s желает подтвердить вашу сессию
     Пользователь отменил проверку
     Время проверки истекло
-    Сессия не знает об этой транзакции
-    Сессия не может договориться о выработке общего ключе, хэше, MAC или методе SAS
+    Сессия не знает об этой сделке
+    Сессия не может договориться о выработке общего ключа, хэша, MAC или метод SAS
     Полученный хеш не соответствует
     SAS не соответствует
     Сессия получила неожиданное сообщение
@@ -1458,18 +1458,18 @@
     Стоп
     Проверка состояния резервного копирования
     Вы вышли из системы из-за недействительных или истекших учетных данных.
-    Редактировать
+    Изменить
     Ответить
     Повторить
-    Присоединитесь к комнате, чтобы начать использовать приложение.
+    Присоединитесь к комнате, чтобы начать пользоваться приложением.
     Отправил вам приглашение
     Приглашен %s
-    Вы в курсе!
+    Вы в теме!
     У вас больше нет непрочитанных сообщений
     Добро пожаловать домой!
-    Узнайте больше о непрочитанных сообщениях здесь
-    Беседа
-    Здесь будут отображаться ваши диалоги. Нажмите + внизу справа, чтобы начать.
+    Следите за непрочитанными сообщениями здесь
+    Беседы
+    Здесь будут отображаться ваши диалоги. Нажмите + внизу справа, чтобы начать что-то.
     Комнаты
     Здесь будут отображаться ваши комнаты. Коснитесь + внизу справа, чтобы найти существующие или создать свои.
     Реакции
@@ -1481,11 +1481,11 @@
     Менеджер интеграции не настроен.
     Реакции
     Событие удалено пользователем
-    Мероприятие, модерируемое администратором помещения
+    Событие модерируется администратором комнаты
     Последнее изменение %1$s %2$s
-    Некорректное событие, не может быть отображено
+    Некорректное событие, не могу отобразить
     Создать новую комнату
-    Сети нет. Пожалуйста, проверьте подключение к Интернету.
+    Нет сети. Пожалуйста, проверьте подключение к Интернету.
     Изменить
     Изменить сеть
     Пожалуйста, подождите…
@@ -1497,18 +1497,18 @@
     СОЗДАТЬ
     Имя
     Публичная
-    Любой сможет присоединиться к этой комнате
-    Каталог номеров
-    Опубликовать эту комнату в каталоге номеров
+    Каждый сможет присоединиться к этой комнате
+    Каталог комнат
+    Опубликовать эту комнату в каталоге комнат
     Произошла ошибка при получении информации о доверии
     Произошла ошибка при получении ключей резервного копирования данных
-    Импорт ключей e2e из файла \"%1$s\".
+    Импорт ключей сквозного шифрования из файла \"%1$s\".
     Версия Matrix SDK
-    Другие уведомления третьих сторон
+    Другие уведомления третьих лиц
     Вы уже просмотрели эту комнату!
-    Быстрое реагирование
+    Быстрые Реакции
     Общее
-    Параметры
+    Предпочтения
     Безопасность и конфиденциальность
     Профессионал
     Правила push-уведомлений
@@ -1525,7 +1525,7 @@
     Пожалуйста, напишите ваше предложение ниже.
     Опишите ваше предложение здесь
     Спасибо, предложение было успешно отправлено
-    Предложение не было отправлено (%s)
+    Не удалось отправить предложение (%s)
     Показать скрытые события в ленте сообщений
     Предварительный просмотр открытой комнаты в ${app_name} пока не поддерживается
     Диалоги
@@ -1537,18 +1537,18 @@
     Файл %1$s загружается…
     Файл %1$s был загружен!
     (изменено)
-    Редактирование сообщений
+    Изменение сообщения
     Изменения не найдены
     Отфильтровывать разговоры…
     Не можете найти то, что ищете\?
     Создать новую комнату
-    Отправить новое прямое сообщение
-    Просмотр список комнат
+    Отправить новое сообщение в диалог
+    Просмотр каталога комнат
     Имя или ID (#example:matrix.org)
     Включить жест смахивания для ответа в ленте сообщений
     Ссылка скопирована в буфер обмена
     Добавить по Matrix ID
-    Создание комнаты…
+    Создаем комнату…
     Результатов не найдено, используйте \"Добавить по matrix ID\" для поиска на сервере.
     Начните печатать, чтобы получить результат
     Фильтр по имени пользователя или ID…
@@ -1556,13 +1556,13 @@
     История изменений
     Обзор
     Отклонить
-    Для продолжения Вам необходимо принять Условия данного сервиса.
+    Для продолжения Вам необходимо принять Условия этого сервиса.
     Правила push-уведомлений не определены
     Нет зарегистрированных push-шлюзов
-    Условия предоставления услуг
-    Условия просмотра
-    Быть доступным для других
-    Используйте ботов, мосты, виджеты и стикеры
+    Условия использования
+    Условия отзыва
+    Быть видимым для других
+    Используйте ботов, мосты, виджеты и наборы стикеров
     Читать в
     Никто
     Отмена
@@ -1587,7 +1587,7 @@
     Предпочтительный интервал синхронизации
     Обнаружение
     Будет использовать %s в качестве помощника, если ваш домашний сервер не предлагает его (ваш IP-адрес будет доступен во время разговора)
-    Добавьте идентификационный сервер в свои настройки, чтобы выполнить это действие.
+    Добавьте сервер обнаружения в свои настройки, чтобы выполнить это действие.
     Режим фоновой синхронизации
     ${app_name} будет синхронизироваться в фоновом режиме таким образом, чтобы сохранить ограниченные ресурсы устройства (батарея).
 \nВ зависимости от состояния ресурса вашего устройства, синхронизация может быть отложена операционной системой.
@@ -1599,35 +1599,35 @@
     Изменить настройки обнаружения.
     Публичное имя (видимое для людей, с которыми вы общаетесь)
     Публичное имя сессии видны людям, с которыми вы общаетесь
-    Вы не используете какой-либо сервер идентификации
-    Идентификационный сервер не настроен, требуется сброс пароля.
+    Вы не используете какой-либо сервер обнаружения
+    Сервер обнаружения не настроен, требуется сброс пароля.
     Похоже, вы пытаетесь подключиться к другому домашнему серверу. Вы хотите выйти\?
-    Сервер идентификации
-    Отключить идентификационный сервер
-    Настроить идентификационный сервер
-    Изменить идентификационный сервер
-    В настоящее время вы используете %1$s для обнаружения и быть найденным вашими контактами.
-    Вы в настоящее время не используете идентификационный сервер. Чтобы обнаружить и быть найденным вашими существующими контактами, настройте один из них ниже.
+    Сервер обнаружения
+    Отключить сервер обнаружения
+    Настроить сервер обнаружения
+    Изменить сервер обнаружения
+    В настоящее время вы используете %1$s для обнаружения и доступны для обнаружения существующими знакомыми вам контактам.
+    Вы в настоящее время не используете сервер обнаружения. Чтобы обнаружить и быть найденным вашими существующими контактами, настройте один из них ниже.
     Видимые адреса электронной почты
-    Доступные номера телефонов
+    Видимые номера телефонов
     В ожидании
-    Введите адрес сервера идентификации
-    Не удалось подключиться к серверу идентификации
-    Пожалуйста, введите URL сервера идентификации
-    Сервер идентификации не имеет условий предоставления услуг
+    Введите адрес сервера обнаружения
+    Не удалось подключиться к серверу обнаружения
+    Пожалуйста, введите URL сервера обнаружения
+    Сервер обнаружения не имеет условий использования
     Параметры обнаружения появятся после добавления электронной почты.
     Параметры поиска появятся после добавления номера телефона.
-    Отключение от сервера идентификации будет означать, что другие пользователи не смогут обнаружить вас, и вы не сможете приглашать других по электронной почте или по телефону.
+    Отключение от сервера обнаружения будет означать, что другие пользователи не смогут обнаружить вас, и вы не сможете приглашать других по электронной почте или по телефону.
     Мы отправили вам электронное письмо с подтверждением на %s, проверьте вашу электронную почту и нажмите на ссылку для подтверждения
-    Выбранный сервер идентификации не имеет условий обслуживания. Продолжить, только если вы доверяете владельцу службы
+    Выбранный сервер обнаружения не имеет условий использования. Продолжайте, только если вы доверяете его владельцу
     Текстовое сообщение отправлено %s. Введите код проверки, который он содержит.
-    В настоящее время вы делитесь адресами электронной почты или телефонными номерами на сервере идентификации %1$s. Вам нужно повторно подключиться к %2$s, чтобы прекратить делиться ими.
-    Примите Условия обслуживания сервера идентификации (%s), чтобы разрешить обнаружение по адресу электронной почты или номеру телефона.
+    В настоящее время вы делитесь адресами электронной почты или телефонными номерами на сервере обнаружения %1$s. Вам нужно повторно подключиться к %2$s, чтобы прекратить делиться ими.
+    Примите Условия использования сервера обнаружения (%s), чтобы разрешить обнаружение по адресу электронной почты или номеру телефона.
     Включить подробные логи.
     Отправить вложение
-    Откройте навигационный ящик
-    Открыть меню создания комнаты
-    Неверный адрес сервера Matrix
+    Откройте панель навигации
+    Откройте меню создания комнаты
+    Это недействительный адрес сервера Matrix
     Подтвердите пароль
     Требуется аутентификация
     Интеграции
@@ -1637,10 +1637,10 @@
     Перезагрузить виджет
     Открыть в браузере
     ID виджета
-    Принять
+    Позволить
     Вы это не можете делать на мобильном ${app_name}
     Этот виджет был добавлен:
-    Ваши тема
+    Ваша тема
     ID комнаты
     Используйте менеджер интеграций чтобы управлять ботами, мостами, виджетами и наборами стикеров.
 \nМенеджеры интеграций получают данные о конфигурации, могут изменять виджеты, отправлять приглашения в комнаты и устанавливать права от вашего имени.
@@ -1667,21 +1667,21 @@
     Это спам
     Игнорировать пользователя
     Все сообщения
-    Только при упоминаниях
+    Только упоминания
     Настройки
     Покинуть комнату
     %1$s сделал(а) комнату доступной для всех, у кого есть ссылка.
     %1$s сделал(а) комнату доступной только по приглашению.
-    Подробные логи помогут разработчикам, предоставив больше информации, когда вы отправляете RageShake. Даже когда они разрешены, приложение не логирует ваши сообщения и другие приватные данные.
-    Отменить создание комнаты…
+    Подробные логи помогут разработчикам, предоставив больше информации, когда вы отправляете ВзмахЯрости. Даже когда они разрешены, приложение не записывает ваши сообщения и другие приватные данные.
+    Закройте меню создание комнаты…
     Вниз
     Контакт
     Стикер
-    Причина жалобы на контент
-    Пожаловаться
+    Причина жалобы на это содержимое
+    ЖАЛОБА
     ИГНОРИРОВАТЬ ПОЛЬЗОВАТЕЛЯ
     Все сообщения (громко)
-    Без звука
+    Заглушить
     Отправить данное сообщение под спойлером
     Спойлер
     Введите ключевые слова, чтобы найти реакцию.
@@ -1689,9 +1689,9 @@
     Непрочитанные сообщения
     Это ваш разговор. Владейте им.
     Общайтесь с людьми напрямую или в группах
-    Начать
+    Начнем
     Выберите сервер
-    Как и у электронной почты, у учётных записей один дом, хотя вы можете общаться с кем угодно
+    Как и у электронной почты, у учётных записей один дом, не смотря на это вы можете общаться с кем угодно
     Премиум-хостинг для организаций
     Узнать больше
     Другой
@@ -1703,12 +1703,12 @@
     Зарегистрироваться
     Войти в систему
     Продолжить с SSO
-    Модульный адрес
+    Element Matrix Services Адрес
     Адрес
     Премиум-хостинг для организаций
-    Введите адрес Modular Element или сервера, который вы хотите использовать
+    Введите адрес Modular Element или Сервер, который вы хотите использовать
     Произошла ошибка при загрузке страницы: %1$s (%2$d)
-    Приложение не может войти на этот сервер, так как он поддерживает следующие типы входа: %1$s.
+    Приложение не может войти на этот домашний сервер. Домашний сервер поддерживает следующие типы входа: %1$s.
 \n
 \nВы хотите войти с помощью веб-клиента\?
     Извините, этот сервер не принимает новые учётные записи.
@@ -1718,15 +1718,15 @@
     Этот адрес электронной почты не связан ни с одной учетной записью.
     Сбросить пароль на %1$s
     Далее
-    Email
+    Электронная почта
     Новый пароль
-    Внимание!
+    Предупреждение!
     Смена пароля приведёт к сбросу всех сквозных ключей шифрования во всех ваших сессиях, что сделает зашифрованную историю разговоров нечитаемой. Настройте резервное копирование ключей или экспортируйте ключи от комнаты из другой сессии, прежде чем сбрасывать пароль.
     Продолжить
-    Данный email не связан ни с одним аккаунтом
+    Данный электронный ящик не связан ни с одним аккаунтом
     Проверьте свою почту
     Письмо с подтверждением было отправлено на %1$s.
-    Нажмите на ссылку, чтобы подтвердить свой новый пароль. Как только вы перейдете по ссылке, которую он содержит, нажмите ниже.
+    Нажмите на ссылку, чтобы подтвердить свой новый пароль. Как только вы перейдете по ссылке нажмите ниже.
     Успешно!
     Ваш пароль был сброшен.
     Вы вышли из всех сессий и больше не будете получать push-уведомления. Чтобы возобновить уведомления, войдите снова на каждом устройстве.
@@ -1737,10 +1737,10 @@
 \nОстановить процесс смены пароля\?
     Задать адрес электронной почты
     Электронная почта
-    Электронная почта (по желанию)
+    Электронная почта (необязательно)
     Далее
     Установить номер телефона
-    Укажите номер своего телефона, чтобы разрешить знакомым найти вас.
+    Укажите номер своего телефона, чтобы разрешить людям найти вас.
     Пожалуйста, используйте международный формат.
     Номер телефона
     Номер телефона (необязательно)
@@ -1751,9 +1751,9 @@
     Отправить повторно
     Далее
     Международные телефонные номера должны начинаться с \'+\'
-    Номер телефона кажется недействительным. Пожалуйста, проверьте его
+    Номер телефона выглядит недействительным. Пожалуйста, проверьте его
     Зарегистрироваться в %1$s
-    Имя пользователя или email
+    Имя пользователя или электронная почта
     Имя пользователя
     Пароль
     Далее
@@ -1764,11 +1764,11 @@
 \nОстановить процесс регистрации\?
     Выбрать matrix.org
     Выбрать Element Matrix Services
-    Выбрать другой сервер
+    Выбрать пользовательский домашний сервер
     Пожалуйста, пройдите проверку капчей
     Примите условия для продолжения
-    Пожалуйста, проверьте ваш email
-    Мы только что отправили email на %1$s.
+    Пожалуйста, проверьте ваш электронный почтовый ящик
+    Мы только что отправили письмо на %1$s.
 \nПожалуйста, нажмите на содержащуюся в нём ссылку, чтобы продолжить создание аккаунта.
     Домашний сервер устарел
     Этот домашний сервер использует слишком старую версию для подключения. Попросите администратора обновить домашний сервер.
@@ -1804,7 +1804,7 @@
     Очистить данные
     Текущая сессия предназначена для пользователя %1$s, а вы предоставляете учётные данные для пользователя %2$s. Это не поддерживается в ${app_name}.
 \nПожалуйста, сначала очистите данные, а затем снова войдите под другим аккаунтом.
-    Ваша ссылка на matrix.to неверна
+    Ваша ссылка на matrix.to была испорчена
     Описание слишком короткое
     Посмотреть все мои сессии
     Дополнительные настройки
@@ -1838,9 +1838,9 @@
     В этой комнате нет медиафайлов
     ФАЙЛЫ
     В этой комнате нет файлов
-    Это недопустимо
+    Это неуместно
     Другая причина…
-    Пожаловаться на контент
+    Пожаловаться на это содержимое
     Безопасность
     Ещё
     QR-код
@@ -1891,30 +1891,30 @@
     Произошла ошибка при загрузке вложения.
     %1$s %2$s
     Жалоба отправлена
-    Жалоба на контент отправлена. 
-\n 
-\nЕсли вы не хотите видеть контент этого пользователя, вы можете его игнорировать, чтобы скрыть его сообщения.
+    Жалоба на контент отправлена.
+\n
+\nЕсли вы больше не хотите видеть от этого пользователя любого содержимого, вы можете его игнорировать, чтобы скрыть его сообщения.
     Отмечено как спам
-    Этот контент был отмечен как спам. 
-\n 
-\nЕсли вы не хотите видеть контент этого пользователя, вы можете его игнорировать, чтобы скрыть его сообщения.
-    Жалоба на неприемлемое содержание отправлена
-    Этот контент был отмечен как неприемлемый. 
-\n 
-\nЕсли вы не хотите видеть контент этого пользователя, вы можете его игнорировать, чтобы скрыть его сообщения.
+    Это содержимое было отмечен как спам.
+\n
+\nЕсли вы больше не хотите видеть любое содержимое этого пользователя, вы можете его игнорировать, чтобы скрыть его сообщения.
+    Жалоба на неуместное содержание отправлена
+    Этот контент был отмечен как неприемлемый.
+\n
+\nЕсли вы больше не хотите видеть от этого пользователя любого содержимого, вы можете его игнорировать, чтобы скрыть его сообщения.
     Они совпадают
     Они не совпадают
-    Закрыть окно бэкапа ключей
+    Закрыть окно резервного копирования ключей
     %s прочитано
     Не удалось обработать данные
-    ${app_name} требуются права для сохранения ваших ключей шифрования на диск.
+    ${app_name} требуются права для сохранения ваших ключей сквозного шифрования на диск.
 \n
 \nПожалуйста, разрешите доступ в следующем всплывающем окне, чтобы экспортировать ключи вручную.
-    Нет подключения к сети
+    Сейчас нет подключения к сети
     Воспроизвести
     Приостановить
     Копировать
-    Выполнено
+    Удачно
     Уведомления
     Звонок не состоялся
     Не удалось установить соединение реального времени.
@@ -1936,7 +1936,7 @@
     Отменить приглашение
     Снизить собственные полномочия\?
     Отклонить
-    Вы не сможете отменить это изменение, поскольку вы понижаете себя в должности, а если вы последний привилегированный пользователь в комнате, то восстановить привилегии будет невозможно.
+    Вы не сможете отменить это изменение, поскольку вы понижаете собственный полномочия, а если вы последний привилегированный пользователь в комнате, то восстановить привилегии будет невозможно.
     Понизить
     Игнорировать пользователя
     Игнорирование этого пользователя приведет к удалению его сообщений из общих комнат.
@@ -1948,13 +1948,13 @@
     Вы уверены, что хотите отменить приглашение для этого пользователя\?
     Выгнать пользователя
     Причина гонения
-    Выгнанный пользователь будет удалён из этой комнаты.
+    Пользователь будет выгнан из этой комнаты.
 \n
-\nЧтобы предотвратить его повторное присоединение, вы должны забанить его.
+\nЧтобы предотвратить его повторное присоединение, вы должны заблокировать его.
     Заблокировать пользователя
     Причина блокировки
     Разблокировать пользователя
-    Разблокирование пользователя позволит ему снова присоединиться к комнате.
+    Разблокирование пользователя позволит ему присоединиться к комнате опять.
     Безопасное резервное копирование
     Управление
     Настройка безопасного резервного копирования
@@ -1977,7 +1977,7 @@
     Безопасное резервное копирование
     Защита от потери доступа к зашифрованным сообщениям и данным
     Настроить безопасное резервное копирование
-    Добавьте специальную вкладку для непрочитанных уведомлений на главном экране.
+    Добавьте отдельную вкладку для непрочитанных уведомлений на главном экране.
     
         Прочитал %d пользователь
         Прочитано %d пользователями
@@ -1991,35 +1991,35 @@
     Вы не игнорируете никаких пользователей
     Вы сделали комнату доступной для всех, у кого есть ссылка.
     Вы сделали комнату только по приглашению.
-    Сохраняйте приватность ваших переписок с помощью шифрования
+    Сохраняйте конфиденциальность разговоров с помощью шифрования
     Расширьте и персонализируйте свой опыт использования
     Присоединяйтесь к миллионам бесплатно на крупнейшем публичном сервере
     Войти в %1$s
     Введите адрес сервера, который вы хотите использовать
-    На ваш почтовый ящик будет отправлено письмо с подтверждением установки нового пароля.
+    На ваш почтовый ящик будет отправлено письмо для подтверждения установки нового пароля.
     Я подтвердил свою электронную почту
     Установите адрес электронной почты для восстановления вашей учетной записи. Позже вы можете дополнительно разрешить людям, которых вы знаете, обнаружить вас по электронной почте.
     Введенный код неверен. Пожалуйста, проверьте.
     Кроме того, если у вас уже есть учетная запись и вы знаете свой идентификатор Matrix и пароль, вы можете использовать этот метод:
-    Войти с помощью Matrix ID
-    Войти с помощью Matrix ID
+    Войти с Matrix ID
+    Войти с Matrix ID
     Если вы создаете учетную запись на домашнем сервере, используйте свой матричный идентификатор (например, @user:domain.com) и пароль ниже.
     Matrix ID
     Если вы не знаете свой пароль, вернитесь, чтобы сбросить его.
     Это недопустимый идентификатор пользователя. Ожидаемый формат: \'@user:homeserver.org\'
     Не удалось найти действительный домашний сервер. Пожалуйста, проверьте свой идентификатор
-    Первичная синхронизация…
-    Rageshake
+    Начальная синхронизация…
+    СотрясениеЯрости
     Порог обнаружения
     Встряхните телефон, чтобы проверить порог обнаружения
-    Тряска зафиксирована!
+    Обнаружено потрясение!
     Показываем только первые результаты, наберите больше букв…
     Раннее падение
     ${app_name} может падать чаще, когда происходит непредвиденная ошибка
     Добавляет смайл ¯\\_(ツ)_/¯ в начало сообщения
-    После включения шифрования оно не может быть отключено.
+    После включения шифрования его нельзя отключить.
     Ваш почтовый домен не имеет права регистрироваться на этом сервере
-    Проверьте этого пользователя, подтвердив, что следующие уникальные смайлики появляются на его экране в том же порядке.
+    Проверьте этого пользователя, подтвердив, что следующие уникальные эмодзи появляются на его экране в том же порядке.
     Для максимальной безопасности используйте другое надежное средство связи или сделайте это лично.
     Ищите зеленый щит, чтобы убедиться, что пользователь доверенный. Доверяйте всем пользователям в комнате, чтобы обеспечить безопасность комнаты.
     Не безопасно
@@ -2027,8 +2027,8 @@
 \n
 \n— Ваш домашний сервер
 \n— Домашний сервер, к которому подключен пользователь, которого вы проверяете
-\n— Ваше или подключение к интернету других пользователей
-\n— Ваше или устройство других пользователей
+\n— Ваше подключение к интернету или других пользователей
+\n— Ваше устройство или других пользователей
     Видео.
     Изображение.
     Аудио
@@ -2044,26 +2044,26 @@
     Подтвердите эту сессию
     Подтверждение вручную
     Вы
-    Сканируйте код с помощью устройства другого пользователя, чтобы надежно проверить друг друга
+    Сканируйте код с помощью устройства другого пользователя, чтобы безопасно проверить друг друга
     Сканировать их код
     Невозможно сканировать
-    Если вы не можете лично, сравните эмодзи в таком случае
+    Если вы не можете лично, сравните вместо этого эмодзи
     Подтвердить при помощи сравнения эмодзи
     Подтверждение с помощью эмодзи
-    Если вы не можете отсканировать приведенный выше код, проверьте это, сравнив короткий уникальный набор эмодзи.
+    Если вы не можете отсканировать приведенный выше код, проверьте сравнивая короткий уникальный набор эмодзи.
     Изображение QR-кода
     Подтверждено %s
     Подтверждённых %s
-    Ожидание для %s…
-    Для дополнительной безопасности проверьте %s, проверив одноразовый код на обоих ваших устройствах.
+    Ожидаем %s…
+    Для особой безопасности проверьте %s, проверив одноразовый код на обоих ваших устройствах.
 \n
 \nДля максимальной безопасности сделайте это лично.
-    Сообщения в этой комнате не шифруются сквозным шифрованием.
-    Сообщения в этой комнате шифруются сквозным шифрованием.
+    Сообщения в этой комнате не защищены сквозным шифрованием.
+    Сообщения в этой комнате защищены сквозным шифрованием.
 \n
-\nВаши сообщения защищены замками, и только у вас и получателя есть уникальные ключи, чтобы разблокировать их.
+\nВаши сообщения защищены замками и только вы и получатель имеете уникальные ключи для их разблокировки.
     Действия Администратора
-    Покинуть комнату…
+    Покидаем комнату…
     Пользовательский
     Приглашения
     Администратор в %1$s
@@ -2080,32 +2080,32 @@
     Посылает сообщение, окрашенное в цвет радуги
     Посылает данную эмоцию, окрашенную в цвет радуги
     Редактор сообщений
-    Включить сквозное шифрование…
+    Включаем сквозное шифрование…
     После включения шифрования оно не может быть отключено.
-    Активировать шифрование\?
-    После включения шифрование для комнаты не может быть отключено. Сообщения, отправленные в зашифрованном помещении, не могут быть замечены сервером, только участниками помещения. Включение шифрования может помешать правильной работе многих ботов и мостов.
-    Активировать шифрование
-    Чтобы быть в безопасности, проверьте %s, проверив одноразовый код.
-    Чтобы обезопасить себя, сделайте это лично или используйте другой способ общения.
+    Включить шифрование\?
+    После включения шифрование для комнаты нельзя отключить. Сообщения отправленные в зашифрованной комнате не будут видны серверу, только участникам комнаты. Включение шифрования может помешать правильной работе многих ботов и мостов.
+    Включить шифрование
+    В целях безопасности, подтвердите %s, проверив одноразовый код.
+    В целях безопасности, сделайте это лично или используйте другой способ общения.
     Сравните уникальные эмодзи, убедившись, что они появились в том же порядке.
     Сравните код с тем, который отображается на экране другого пользователя.
-    Сообщения с этим пользователем полностью зашифрованы и не могут быть прочитаны третьими лицами.
-    Ваша новая сессия теперь подтверждена. Она имеет доступ к вашим зашифрованным сообщениям, и другие пользователи будут считать её надежной.
-    Кросс-подпись
-    Кросс-подпись включена
-\nПриватные ключи хранятся на устройстве.
-    Кросс-подпись включена
-\nКлючи являются надёжными.
-\nПриватные ключи неизвестны
-    Кросс-подпись включена.
-\nКлючи являются ненадёжными
-    Кросс-подпись выключена
+    Сообщения от этого пользователя зашифрованы сквозным шифрованием и не смогут быть прочитаны третьими лицами.
+    Ваша новая сессия подтверждена. У нее есть доступ к вашим зашифрованным сообщениям, а другие пользователи увидят его как доверенное.
+    Перекрестная подпись
+    Перекрестная подпись включена
+\nЛичные ключи хранятся на устройстве.
+    Перекрестная подпись включена
+\nКлючи являются доверенными.
+\nЛичные ключи неизвестны
+    Перекрестная подпись включена.
+\nКлючи не являются доверенными
+    Перекрестная подпись выключена
     Администратор вашего сервера отключил сквозное шифрование по умолчанию в приватных комнатах и диалогах.
     Активные сессии
     Показать все сессии
     Управление сессиями
     Выйти из этой сессии
-    Нет доступного шифрования информации
+    Нет доступной криптографической информации
     
         %d сессия активна
         %d сессии активны
@@ -2129,19 +2129,19 @@
     Сообщения, содержащие @room
     Отладка
     Настройки важности уведомлений для событий
-    Используйте последнюю версию ${app_name} на других ваших устройствах, веб-клиент ${app_name}, ${app_name} для ПК, ${app_name} для iOS, ${app_name} для Android или другой клиент Matrix, поддерживающий кросс-подпись
+    Используйте последнюю версию ${app_name} на других ваших устройствах, веб-клиент ${app_name}, ${app_name} для ПК, ${app_name} для iOS, ${app_name} для Android или другой клиент Matrix, поддерживающий перекрестную подпись
     Используйте последнюю версию ${app_name} на других ваших устройствах:
     Подтвердите новую сессию вашей учетной записи: %1$s
     Настроить безопасное резервное копирование
     Безопасное резервное копирование
-    Эта сессия является доверенной для безопасного обмена сообщениями, потому что вы проверили её:
+    Эта сессия является надежной для безопасного обмена сообщениями, поскольку вы подтвердили ее:
     Подтвердите эту сессию, чтобы пометить её доверенной и предоставить ей доступ к зашифрованным сообщениям. Если вы не входили в эту сессию, ваша учетная запись может быть скомпрометирована:
     Другие пользователи могут не доверять ему
     Завершите настройку безопасности
-    Верифицировать
-    Верифицировано
-    Внимание
-    Ошибка получения списка сессий
+    Проверить
+    Проверено
+    Предупреждение
+    Не удалось получить список сессий
     Сессии
     Доверенные
     Недоверенные
@@ -2150,7 +2150,7 @@
     Создать простой опрос
     Новый вход
     Пока этот пользователь не доверяет этой сессии, сообщения, отправленные в обе стороны, помечаются предупреждениями. Кроме того, вы можете подтвердить сессию вручную.
-    Инициализировать кросс-подпись
+    Начать перекрестную подпись
     Сбросить ключи
     Почти готово! Показывает ли %s галочку\?
     Да
@@ -2159,7 +2159,7 @@
     Не удалось найти данные в хранилище
     Введите парольную фразу для секретного хранилища
     Предупреждение:
-    Вы должны получать доступ к секретному хранилищу только с доверенного устройства
+    Вам следует получать доступ к секретному хранилищу только с доверенного устройства
     Вы хотите отправить это вложение в %1$s\?
     
         Отправить изображение в оригинальном размере
@@ -2168,7 +2168,7 @@
     
     Подтвердите удаление
     Вы уверены, что хотите скрыть (удалить) это событие\? Обратите внимание, что если вы удалите название комнаты или измените тему, это может отменить изменение.
-    Указать причину
+    Укажите причину
     Причина редактирования
     Событие удалено пользователем, Причина: %1$s
     Событие модерируется администратором комнаты, Причина: %1$s
@@ -2179,10 +2179,10 @@
     Нажмите, чтобы просмотреть и проверить
     Это был не я
     Ваш аккаунт может оказаться под угрозой
-    Если вы прервёте процедуру, то не сможете читать зашифрованные сообщения на этом устройстве, а другие пользователи не будут доверять ему
-    Если вы прервёте процедуру, то не сможете читать зашифрованные сообщения на своем новом устройстве, а другие пользователи не будут доверять ему
+    Если вы прервёте процедуру, то не сможете читать зашифрованные сообщения на этом устройстве, а другие пользователи не захотят доверять ему
+    Если вы прервёте процедуру, то не сможете читать зашифрованные сообщения на своем новом устройстве, а другие пользователи не захотят доверять ему
     Если вы прервёте процедуру, пользователь %1$s (%2$s) не будет подтверждён. Начните заново в профиле этого пользователя.
-    Что-то из этого может быть скомпрометировано:
+    Что-то из этого может быть поставлено под угрозу:
 \n
 \n- Ваш пароль
 \n- Ваш домашний сервер
@@ -2212,7 +2212,7 @@
     Синхронизация ключа пользователя
     Синхронизация ключа самоподписи
     Настройка резервного копирования ключей
-    Сохраните на USB-флешку или резервный диск
+    Сохраните на USB-ключ или резервное хранилище
     Скопируйте в персональное облачное хранилище
     Вы не можете сделать это с телефона
     Шифрование этой комнаты не поддерживается
@@ -2227,7 +2227,7 @@
     Посылает сообщение в виде простого текста, не интерпретируя его как разметку
     Неверное имя пользователя и/или пароль. Введенный пароль начинается или заканчивается пробелами, пожалуйста, проверьте.
     Эта учётная запись была деактивирована.
-    Активировать кросс-подпись
+    Включить перекрестную подпись
     Введите %s, чтобы продолжить
     Использовать файл
     Введите %s
@@ -2250,7 +2250,7 @@
 \n${app_name} для ПК
     ${app_name} для iOS
 \n${app_name} для Android
-    или другой, поддерживаемый кросс-подпись Matrix клиент
+    или другой клиент Matrix поддерживающий перекрестную подпись
     Принудительно отбрасывает текущую групповую сессию для отправки сообщений в зашифрованную комнату
     Чтобы продолжить, используйте ваш %1$s или используйте ваш %2$s.
     Используйте ключ восстановления
@@ -2259,7 +2259,7 @@
     Не удалось получить доступ к защищенному хранилищу данных
     Не зашифровано
     Зашифровано неподтверждённой сессией
-    Проверьте, где вы вошли
+    Посмотрите, где вы вошли
     Подтвердите все свои сессии, чтобы убедиться в безопасности вашей учетной записи и сообщений
     Ручная проверка с помощью текста
     Пометить как надежный
@@ -2292,7 +2292,7 @@
     Для вашей приватности, ${app_name} поддерживает отправку адреса электронной почты и номера телефона только в хэшированном виде.
     Привязка не удалась.
     Текущая взаимосвязь с этим идентификатором отсутствует.
-    Ваш домашний сервер (%1$s) предлагает использовать %2$s для вашего сервера идентификации
+    Ваш домашний сервер (%1$s) предлагает использовать %2$s для вашего сервера обнаружения
     Использовать %1$s
     Кроме того, вы можете ввести любой другой URL-адрес сервера идентификации
     Введите URL-адрес сервера идентификации
@@ -2322,7 +2322,7 @@
     Вы успешно изменили настройки комнаты
     У вас нет доступа к этому сообщению
     Расшифровка этого сообщения может занять некоторое время
-    Не удалось расшифровать
+    Не могу расшифровать
     Из-за сквозного шифрования вам, возможно, придется ждать прибытие чьего-либо сообщения, потому что ключи шифрования не были отправлены вам должным образом.
     Вы не можете получить доступ к этому сообщению, потому что вы были заблокированы отправителем
     Нет доступа к этому сообщению, так как отправитель не доверяет вашей сессии
@@ -2358,7 +2358,7 @@
     Для сброса PIN-кода нажмите Забыл(а) PIN-код, чтобы выйти из сессии и сбросить его.
     Подтвердите PIN-код, чтобы отключить PIN-код
     Предотвращение случайных звонков
-    Спрашивать подтверждение перед звонком
+    Запрашивать подтверждение перед началом звонка
     У вас нет разрешения на запуск конференции в этой комнате
     Конференция уже идет!
     Начать видеовстречу
@@ -2390,7 +2390,7 @@
     
     Предупреждение! Последняя оставшаяся попытка перед выходом из системы!
     Слишком много ошибок, вы вышли из системы
-    Этот номер телефона уже определён.
+    Этот номер телефона уже используется.
     В ваш аккаунт не добавлен номер телефона
     Адрес электронной почты
     В ваш аккаунт не добавлен адрес электронной почты
@@ -2410,7 +2410,7 @@
         %d секунд
     
     Показать события статуса участников комнаты
-    Включает в себя события приглашения/ присоединения/выхода/удаления/блокировки и изменение аватара/отображаемого имени.
+    Включает в себя события приглашения/присоединения/выхода/удаления/события блокировки и изменение аватара/изменения отображаемого имени.
     Опрос
     Отреагировал: %s
     Результат проверки
@@ -2441,10 +2441,10 @@
     Сообщения в этой комнате зашифрованы сквозным шифрованием.
     Покинуть
     Настройки
-    Сообщения здесь зашифрованы.
+    Сообщения здесь защищены сквозным шифрованием.
 \n
 \nВаши сообщения защищены замками, и только у вас и получателя есть уникальные ключи для их разблокировки.
-    Сообщения здесь не зашифрованы.
+    Сообщения здесь не прошли сквозного шифрования.
     На этом домашнем сервере работает старая версия. Попросите администратора вашего домашнего сервера выполнить обновление. Вы можете продолжить, но некоторые функции могут работать некорректно.
     Вы сделали доступ только по приглашению.
     %1$s сделал(а) доступ только по приглашению.
@@ -2477,17 +2477,17 @@
     Приложение ожидает push-уведомление
     Тестирование push-уведомлений
     Есть несохраненные изменения. Отменить изменения\?
-    Удалить из низкого приоритета
+    Удалить из маловажного
     Повернуть и обрезать
     Настройки комнаты
-    Тема комнаты (опционально)
+    Тема комнаты (необязательно)
     Отменить изменения
     Комната ещё не создана. Отменить создание комнаты\?
-    Добавить к низкому приоритету
+    Добавить к маловажному
     Добавить изображение из
     Тема
     Название комнаты
-    Вы дали свое согласие на отправку электронных писем и телефонных номеров на этот сервер идентификации для обнаружения других пользователей из ваших контактов.
+    Вы дали свое согласие на отправку электронных писем и телефонных номеров на этот сервер обнаружения для обнаружения других пользователей из ваших контактов.
     Добавить по QR-коду
     Разрешить доступ к вашим контактам.
     Чтобы отсканировать QR-код, вам нужно разрешить доступ к камере.
@@ -2523,12 +2523,12 @@
     Скрыть дополнительные настройки
     Показать дополнительные настройки
     %1$d из %2$d
-    Согласны ли вы на отправку своих контактных данных (номера телефонов и/или электронную почту) на настроенный сервер идентификации (%1$s) для обнаружения контактов\?
+    Согласны ли вы на отправку своих контактных данных (номера телефонов и/или электронной почты) на настроенный сервер идентификации (%1$s) для обнаружения контактов\?
 \n
 \nДля большей конфиденциальности отправленные данные перед отправкой будут хешированы.
     Дать согласие
     Отозвать моё согласие
-    Вы не дали свое согласие на отправку электронной почты и номеров телефонов на этот сервер идентификации для обнаружения других пользователей из ваших контактов.
+    Вы не дали свое согласие на отправку электронной почты и номеров телефонов на этот сервер обнаружения для обнаружения других пользователей из ваших контактов.
     Больше никаких результатов
     Предложения
     Контакты
@@ -2613,8 +2613,8 @@
     У вас нет права доступа на обновление ролей, необходимых для изменения различных параметров комнаты
     Выберите роли, которые смогут менять различные параметры комнаты
     Просматривайте и обновляйте роли, необходимые для изменения различных параметров комнаты.
-    Права доступа
-    Права доступа комнаты
+    Разрешения
+    Разрешения комнаты
     Эта комната не публичная. Вы не сможете повторно присоединиться без приглашения.
     Системная тема
     Не удалось пройти аутентификацию
@@ -2698,14 +2698,14 @@
     Изображение
     Импорт ключа из файла
     Открытые виджеты
-    Скриншот
+    Снимок экрана
     
         %d запись
         %d записи
         %d записей
         %d записей
     
-    Лимит неизвестен.
+    Предел неизвестен.
     Ваш домашний сервер принимает вложения (файлы, медиа и т.д.) размером до %s.
     Лимит загрузки файла сервера
     Версия сервера
@@ -2713,9 +2713,9 @@
     Настройки комнаты
     Покинуть текущую конференцию и перейти к другой\?
     Версия комнаты
-    Показать все комнаты в списке комнат, в том числе с чувствительным содержанием.
-    Показать комнаты с деликатным содержанием
-    Список комнат
+    Показать все комнаты в списке комнат, в том числе с откровенным содержанием.
+    Показать комнаты с откровенным содержанием
+    Каталог комнат
     Новое значение
     Сменить
     Начальная синхронизация:
@@ -2738,18 +2738,18 @@
     Модернизировать публичную комнату
     Обновление
     Пожалуйста, будьте терпеливы, это может занять некоторое время.
-    Присоединиться к изменённой комнате
+    Присоединиться к замещенной комнате
     В настоящее время люди не могут присоединиться к созданным вами приватным комнатам.
 \n
 \nМы улучшим это в рамках бета-версии, но мы просто хотели сообщить вам об этом.
     Пространства для членов команды еще не совсем готовы, но вы все еще можете их попробовать
-    Комната без названия
+    Безымянная Комната
     Некоторые комнаты могут быть скрыты, потому что они приватные, и вам нужно приглашение.
     Некоторые комнаты могут быть скрыты, потому что они приватные, и вам нужно приглашение.
 \nУ вас нет разрешения на добавление комнат.
     В этом пространстве нет комнат
-    Для получения дополнительной информации обратитесь к администратору домашнего сервера
-    Похоже, что ваш домашний сервер пока не поддерживает пространства
+    Для получения дополнительной информации обратитесь к администратору вашего домашнего сервера
+    Похоже, что ваш домашний сервер пока не поддерживает Пространства
     Чувствуете себя экспериментатором\?
 \nВы можете добавить существующие пространства в пространство.
     Управление комнатами и пространствами
@@ -2770,12 +2770,12 @@
     Вы здесь единственный человек. Если вы уйдёте, никто не сможет присоединиться в будущем, включая вас.
     Покинуть пространство
     Добавить комнаты
-    Список комнат
+    Исследуйте комнаты
     
         %d человек, которого вы знаете, уже присоединился
-        %d человека, которых вы знаете, уже присоединились
-        %d человек, которых вы знаете, уже присоединились
-        %d человек, которых вы знаете, уже присоединились
+        %d людей, которых вы знаете, уже присоединились
+        %d людей, которых вы знаете, уже присоединились
+        %d людей, которых вы знаете, уже присоединились
     
     Добро пожаловать в %1$s, %2$s.
     Вы ещё не находитесь ни в одной комнате. Ниже приведены некоторые предлагаемые комнаты, но вы можете посмотреть другие с помощью зелёной кнопки внизу справа.
@@ -2806,7 +2806,7 @@
     Мы создадим для них комнаты. Позже вы сможете добавить и другие.
     Какие обсуждения вы хотите провести в %s\?
     Дайте ему название, чтобы продолжить.
-    Добавьте некоторые детали, чтобы помочь людям идентифицировать его. Вы сможете изменить их в любой момент.
+    Добавьте некоторые детали, чтобы помочь людям распознать его. Вы сможете изменить их в любой момент.
     Добавьте некоторые детали, чтобы помочь ему выделиться. Вы сможете изменить их в любой момент.
     Создание пространства
     Только по приглашениям, лучше для себя или команды
@@ -2859,9 +2859,9 @@
     Сжатие видео %d%%
     Сжатие изображения…
     Дать отзыв
-    Отзыв не отправлен (%s)
+    Не удалось отправить отзыв (%s)
     Спасибо, ваш отзыв успешно отправлен
-    Вы можете связаться со мной, если у вас возникнут какие-либо последующие вопросы
+    Вы можете связаться со мной, если у вас есть дополнительные вопросы
     Вы используете бета-версию пространств. Ваши отзывы помогут при разработке следующих версий. Ваша платформа и имя пользователя будут отмечены, чтобы мы могли максимально использовать ваш отзыв.
     Отзыв
     Извините, при попытке присоединиться к конференции произошла ошибка
@@ -2872,12 +2872,12 @@
     Ваш сервер
     Любой человек в пространстве с этой комнатой может найти её и присоединиться к ней. Только администраторы этой комнаты могут добавить её в пространство.
     Только участники пространства
-    Любой желающий может найти комнату и присоединиться
+    Каждый может найти комнату и присоединиться
     Публичный
     Только приглашенные люди могут найти и присоединиться
     Приватный
     Неизвестная настройка доступа (%s)
-    Любой может постучаться в комнату, участники могут принять или отклонить его
+    Любой может постучаться в комнату, а участники могут принять или отклонить его
     Просмотреть и управлять адресами этого пространства.
     Разрешить гостям присоединяться
     Продолжить в любом случае
@@ -2951,9 +2951,9 @@
     Свяжите этот адрес электронной почты с вашей учетной записью
     Приглашение в эту комнату было отправлено на %s, который не связан с вашей учетной записью
     Приглашение в это пространство было отправлено на %s, который не связан с вашей учетной записью
-    Чтобы помочь участникам пространства найти и присоединиться к приватной комнате, перейдите в настройки этой комнаты, нажав на аватар.
-    Помогите участникам пространства в поиске приватных комнат
-    Это позволяет комнатам оставаться приватными для пространства, в то же время позволяя людям в пространстве находить их и присоединяться к ним. Все новые комнаты в пространстве будут иметь эту опцию.
+    Чтобы помочь участникам пространства найти и присоединиться к частной комнате, перейдите в настройки этой комнаты, нажав на ее аватар.
+    Помогите участникам пространства в поиске частных комнат
+    Это позволяет комнатам оставаться приватными для пространства, в то же время позволяя людям в пространстве находить их и присоединяться к ним. Все новые комнаты в пространстве будут иметь эту возможность.
     Помогите людям в пространствах самим находить и присоединяться к приватным комнатам, без необходимости вручную приглашать всех.
     Новое: Позволяет людям в пространствах находить и присоединяться к приватным комнатам
     Начался групповой вызов
@@ -2963,7 +2963,7 @@
     %1$s Нажмите, чтобы вернуться
     Активный вызов (%1$s) ·
     
-        Активный вызов ·
+        %1$d Активный вызов ·
         %1$d активных вызова ·
         %1$d активных вызовов ·
         %1$d активных вызовов ·
@@ -3016,7 +3016,7 @@
     Завершение настройки
     Приглашение по электронной почте, поиск контактов и многое другое…
     Завершите настройку обнаружения.
-    В настоящее время вы не используете сервер идентификации. Чтобы приглашать членов команды и быть доступным для них, настройте один из них ниже.
+    В настоящее время вы не используете сервер обнаружения. Чтобы приглашать членов команды и быть доступным для них, настройте один из них ниже.
     Приглашение по имени пользователя или по почте
     Убедитесь, что нужные люди имеют доступ к %s. Вы можете пригласить больше людей позже.
     Кто ваши члены команды\?
@@ -3028,36 +3028,36 @@
     Открыть настройки обнаружения
     Поиск по имени, ID или почте
     Создать новое пространство
-    Любой желающий может найти это пространство и присоединиться
+    Каждый может найти это пространство и присоединиться
     Доступ к пространству
     Кто имеет к этому доступ\?
     Включить уведомления по электронной почте для %s
     Чтобы получать уведомления по электронной почте, пожалуйста, привяжите электронную почту к вашей учетной записи Matrix
     Уведомление по эл. почте
-    Обновление пространства
+    Обновить пространство
     Изменить название пространства
     Включить шифрование пространства
     Изменить основной адрес для пространства
     Изменить аватар пространства
-    У вас нет разрешения на обновление ролей, необходимых для изменения различных частей этого пространства
-    Выберите роли, необходимые для изменения различных частей этого пространства
+    У вас нет разрешения на обновление ролей, необходимых для изменения различных параметров этого пространства
+    Выберите роли, необходимые для изменения различных параметров этого пространства
     Просмотр и обновление ролей, необходимых для изменения различных частей пространства.
     Разрешения пространства
-    Разблокирование пользователя позволит ему снова присоединиться к пространству.
-    Блокировка пользователя удалит его из этого пространства и не позволит ему присоединиться снова.
-    Выгнанный пользователь будет удалён из этого пространства.
-\n 
+    Разблокирование пользователя позволит ему присоединиться к пространству опять.
+    Блокировка пользователя удалит его из этого пространства и не позволит ему присоединиться вновь.
+    Пользователь будет выгнан из этого пространства.
+\n
 \nЧтобы предотвратить его повторное присоединение, вы должны заблокировать его.
     Остановить запись
-    Добавляет ( ͡° ͜ʖ ͡°) к текстовому сообщению
+    Добавляет ( ͡° ͜ʖ ͡°) в начало сообщения
     Правила
-    Нет правил, предоставляемых сервером идентификации
-    Скрыть правила сервера идентификации
-    Показать правила сервера идентификации
+    Нет правил, предоставляемых сервером обнаружения
+    Скрыть правила сервера обнаружения
+    Показать правила сервера обнаружения
     Отображает информацию о пользователе
     Изменяет ваш аватар только в этой текущей комнате
     Меняет аватар текущей комнаты
-    Изменяет ваш отображаемый псевдоним только в текущей комнате
+    Изменяет ваш отображаемое имя только в текущей комнате
     Устанавливает имя комнаты
     Прекращение игнорирования пользователя, показывает его сообщения в дальнейшем
     Игнорирует пользователя, скрывая его сообщения от вас
@@ -3071,7 +3071,7 @@
     Загрузить файл
     Отправить изображения и видео
     Открыть камеру
-    Отображать местоположения пользователя на временной шкале
+    Показывать местоположения пользователя в ленте сообщений
     После включения вы сможете отправить свое местоположение в любую комнату
     Включить отправку местоположения
     Открыть с помощью
@@ -3145,27 +3145,27 @@
     Шифрование неправильно настроено
     Изменить цвет ника
     Восстановить шифрование
-    Пожалуйста, свяжитесь с администратором для восстановления шифрования в рабочее состояние.
-    Шифрование неправильно настроено.
+    Обратитесь к администратору, чтобы восстановить шифрование до рабочего состояния.
+    Шифрование настроено неправильно.
     Поделились своим местоположением
     У меня уже есть учетная запись
     Создать учетную запись
-    Общение вашей команды.
-    Со сквозным шифрованием и без требования номера телефона. Без рекламы или анализа данных.
-    Выберите, где общаться, что даст вам контроль и независимость. Подключено через Matrix.
+    Обмен сообщениями для вашей команды.
+    Сквозное шифрование не требующее номера телефона. Нет рекламы или сбора данных.
+    Выбор где хранятся ваши разговоры дает вам власть и независимость. Подключено с помощью Matrix.
     Безопасное и независимое общение, обеспечивающее вам такой же уровень конфиденциальности, как при личном общении в вашем собственном доме.
-    Усовершенствуйте общение в команде.
-    Защищенное общение.
-    Вы контролируете всё.
-    Владейте своими разговорами.
+    Удалите шлак из команд.
+    Безопасный обмен сообщениями.
+    Ваше управление.
+    Ваши собственные разговоры.
     Местоположение
     Вы согласны отправить эту информацию\?
-    Чтобы обнаружить существующие контакты, необходимо отправить контактную информацию (электронную почту и номера телефонов) на сервер идентификации. Мы хэшируем ваши данные перед отправкой для обеспечения конфиденциальности.
-    Чтобы обнаружить существующие контакты, необходимо отправить контактную информацию на сервер идентификации.
+    Чтобы обнаружить существующие контакты, необходимо отправить контактную информацию (электронную почту и номера телефонов) на сервер обнаружения. Мы хешируем ваши данные перед отправкой для обеспечения конфиденциальности.
+    Чтобы обнаружить существующие контакты, необходимо отправить контактную информацию на сервер обнаружения.
 \n
-\nМы хэшируем ваши данные перед отправкой для обеспечения конфиденциальности. Согласны ли вы отправить эту информацию\?
+\nМы хешируем ваши данные перед отправкой для обеспечения конфиденциальности. Вы согласны отправить эту информацию\?
     Отправить электронные адреса и номера телефонов %s
-    Ваши контакты приватны. Чтобы обнаружить пользователей из ваших контактов, нам необходимо ваше разрешение на отправку контактной информации на ваш сервер идентификации.
+    Ваши контакты приватны. Чтобы обнаружить пользователей из ваших контактов, нам необходимо ваше разрешение на отправку контактной информации на ваш сервер обнаружения.
     Системные настройки
     Версии
     Получите помощь в использовании Element
@@ -3174,7 +3174,7 @@
     Правовые положения
     Этот сервер не предоставляет никакой политики.
     Сторонние библиотеки
-    Политика вашего сервера идентификации
+    Политика вашего сервера опознавания
     Политика вашего домашнего сервера
     Политика ${app_name}
     Вы можете отключить это в любое время в настройках
@@ -3185,10 +3185,10 @@
 \n
 \nВы можете ознакомиться со всеми нашими условиями %s.
     Помогите улучшить Element
-    Сессия была завершена!
+    Сессия завершена!
     Комната покинута!
-    Шифрование было неправильно настроено, поэтому вы не можете отправлять сообщения. Нажмите, чтобы открыть настройки.
-    Шифрование было неправильно настроено, поэтому вы не можете отправлять сообщения. Пожалуйста, обратитесь к администратору, чтобы восстановить работу шифрования.
+    Шифрование неправильно настроено, поэтому вы не можете отправлять сообщения. Нажмите, чтобы открыть настройки.
+    Шифрование настроено неправильно, поэтому вы не можете отправлять сообщения. Пожалуйста, обратитесь к администратору, чтобы восстановить работу шифрования.
     Выберите домашний сервер
     Не могу связаться с домашним сервером на URL %s. Пожалуйста, проверьте вашу ссылку или выберите домашний сервер вручную.
     Не сейчас
@@ -3199,7 +3199,7 @@
     Показать все потоки в которых вы участвуете
     Все Потоки
     Просмотр Потоков
-    Вид в комнате
+    Посмотреть в комнате
     Показать всплывающие сообщения
     Не удалось загрузить карту
     Карта
@@ -3215,12 +3215,12 @@
     Мы поможем вам подключится.
     С кем вы будете общаться больше всего\?
     Вы уже просматриваете этот Поток!
-    Вид в Комнате
+    Просмотр в Комнате
     Ответить в Поток
     Команда «%s» распознается, но не поддерживается в потоках.
     Из Потока
     Совет: нажмите и удерживайте сообщение и используйте «%s».
-    Потоки помогают вести ваши разговоры тематически и легко отслеживающимися
+    Потоки помогают хранить ваши разговоры по темам и легко отслеживать их.
     Мои Потоки
     Показать все потоки в текущей комнате
     Фильтр

From 98f339fed91e7770893ccdd8b3b3a10327e76f9d Mon Sep 17 00:00:00 2001
From: Jozef Gaal 
Date: Fri, 18 Feb 2022 18:24:08 +0000
Subject: [PATCH 476/581] Translated using Weblate (Slovak)

Currently translated at 97.7% (2723 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/sk/
---
 vector/src/main/res/values-sk/strings.xml | 189 ++++++++++++++--------
 1 file changed, 123 insertions(+), 66 deletions(-)

diff --git a/vector/src/main/res/values-sk/strings.xml b/vector/src/main/res/values-sk/strings.xml
index 88cf02d87da..dd410031a8c 100644
--- a/vector/src/main/res/values-sk/strings.xml
+++ b/vector/src/main/res/values-sk/strings.xml
@@ -28,7 +28,7 @@
     pre všetkých členov.
     pre každého.
     neznámym (%s).
-    %1$s povolil/a E2E šifrovanie (%2$s)
+    %1$s povolil/a end-to-end šifrovanie (%2$s)
     %1$s požiadal/a o VoIP konferenciu
     Začala sa VoIP konferencia
     Skončila sa VoIP konferencia
@@ -39,7 +39,7 @@
     %1$s pozval/a %2$s vstúpiť do miestnosti
     %1$s prijal/a pozvanie pre %2$s
     ** Nie je možné dešifrovať: %s **
-    Zo zariadenia odosieľateľa nebolo možné získať kľúče potrebné na dešifrovanie tejto správy.
+    Zariadenie odosielateľa nám neposlalo kľúče pre túto správu.
     Nie je možné vymazať
     Nie je možné odoslať správu
     Nepodarilo sa nahrať obrázok
@@ -138,7 +138,7 @@
     Prijali ste hovor.
     Ukončili ste hovor.
     Sprístupnili ste budúcu históriu miestnosti %1$s
-    Povolili ste E2E šifrovanie (%1$s)
+    Povolili ste end-to-end šifrovanie (%1$s)
     Aktualizovali ste túto miestnosť.
     Požiadali ste o VoIP konferenciu
     Odstránili ste názov miestnosti
@@ -191,12 +191,12 @@
     Povolili ste hosťom///návštevníkom prístup do tejto miestnosti.
     %1$s zakázal/a hosťom///návštevníkom prístup do tejto miestnosti.
     Zakázali ste hosťom///návštevníkom prístup do tejto miestnosti.
-    %1$s povolil/a E2E šifrovanie.
-    Povolili ste E2E šifrovanie.
-    %1$s povolil/a E2E šifrovanie (Nerozpoznaný algorytmus %2$s).
-    Povolili ste E2E šifrovanie (Nerozpoznaný algorytmus %1$s).
-    nastavili ste na servery pravidlá ACL tejto miestnosti.
-    %s nastavil(a) na servery pravidlá ACL tejto miestnosti.
+    %1$s povolil/a end-to-end šifrovanie.
+    Zapli ste end-to-end šifrovanie.
+    %1$s zapol end-to-end šifrovanie (nerozpoznaný algoritmus %2$s).
+    Zapli ste end-to-end šifrovanie (nerozpoznaný algoritmus %1$s).
+    Nastavili ste ACL servera pre túto miestnosť.
+    %s nastavil/a na serveri pravidlá ACL pre túto miestnosť.
     Aktualizovali ste sem.
     %s aktualizoval(a) sem.
     %1$s sprístupnil(a) budúce správy %2$s
@@ -297,7 +297,7 @@
     Nahlásiť chybu
     Prosím, Napíšte text hlásenia. Čo ste práve robili? Čo ste očakávali? Čo sa v skutočnosti udialo?
     Tu popíšte váš problém
-    S cieľom lepšej diagnostiky problému sa spolu s vašim hlásením odošlú záznami o činnosti programu Element. Toto hlásenie vrátane priložených záznamov a snímky obrazovky nebude verejne dostupné. Ak si želáte odoslať len text hlásenia, odškrtnite niektoré z nasledujúcich polí:
+    Za účelom diagnostiky problémov budú spolu s týmto hlásením o chybe odoslané záznamy z tohto klienta. Toto hlásenie o chybe, vrátane záznamov a snímku obrazovky, nebude verejne viditeľné. Ak chcete radšej odoslať len vyššie uvedený text, zrušte označenie:
     Zdá sa, že v zlosti trasiete zariadením. Chceli by ste odoslať hlásenie o chybe?
     Posledné spustenie aplikácie skončilo pádom. Chceli by ste odoslať hlásenie o chybe?
     Hlásenie o chybe bolo úspešne odoslané
@@ -364,7 +364,7 @@
     Nepodarilo sa overiť emailovú adresu: Uistite sa, že ste správne klikli na odkaz v emailovej správe
     Vaše heslo bolo obnovené.
 \n
-\nBoli ste odhlásení na všetkých reláciach a nebudete viac dostávať okamžité oznámenia. Oznámenia znovu povolíte tak, že sa opätovne prihlásite na každom zariadení.
+\nBoli ste odhlásení zo všetkých relácií a už nebudete dostávať okamžité oznámenia. Ak chcete opätovne povoliť oznámenia, znovu sa prihláste na každom zariadení.
     URL adresa musí začínať http[s]://
     Nie je možné sa prihlásiť: Chyba siete
     Nie je možné sa prihlásiť
@@ -472,7 +472,7 @@
     Túto zmenu nebudete môcť vrátiť späť, pretože tomuto používateľovi udeľujete rovnakú úroveň moci, akú máte vy. 
 \nSte si istí\?
     Ste si istí, že chcete pozvať používateľa %s do tejto miestnosti?
-    Pozvať zadaním používateľského ID
+    Pozvať pomocou ID
     LOKÁLNE KONTAKTY (%d)
     ADRESÁR POUŽÍVATEĽOV (%s)
     Len Matrix používatelia
@@ -501,15 +501,15 @@
     Odtlačok (%s):
     Nie je možné overiť totožnosť servera.
     Vaše zariadenie nedôveruje certifikátu poskytnutému serverom alebo sa niekto môže pokúšať odpočúvať vašu komunikáciu.
-    Ak vám aj správca servera môže potvrdiť, že certifikát nie je dôverihodný, uistite sa, že odtlačok zobrazený nižšie sa zhoduje s odtlačkom získaným od správcu.
-    Certifikát sa zmenil na taký, ktorý nie je viac dôverovaný vašim zariadením. Takáto zmena je veľmi nezvičajná. Navrhujeme, aby ste NEDÔVEROVALI tomuto novému certifikátu.
-    Vami overený certifikát sa zmenil na taký, ktorý nie je viac dôverovaný vašim zariadením. Je pravdepodobné, že správca servera obnovil certifikát. Na účel ručného overenia si prosím vyžiadajte nový odtlačok.
+    Ak správca servera uviedol, že sa to očakáva, skontrolujte, či sa odtlačok uvedený nižšie zhoduje s odtlačkom, ktorý poskytol.
+    Certifikát sa zmenil na iný, ktorému tento telefón dôveroval. Toto je VEĽMI NEZVYČAJNÉ. Odporúča sa, aby ste tento nový certifikát NEPRIJALI.
+    Certifikát sa zmenil z predtým dôveryhodného na nedôveryhodný. Server mohol obnoviť svoj certifikát. Obráťte sa na správcu servera, aby vám poskytol očakávaný odtlačok.
     Dôverujte certifikátu len v prípade, že správca servera zverejnil odtlačok zhodný s odtlačkom zobrazeným vyššie.
     Podrobnosti o miestnosti
     Ľudia
     Súbory
     Nastavenia
-    Nesprávny formát ID. Mali by ste zadať emailovú adresu alebo Matrix ID ako napríklad \'@lokálnaČasť:doména\'
+    Chybné ID. Mala by to byť emailová adresa alebo Matrix ID ako napríklad \"@lokalnacast:domena\"
     POZVANÍ
     VSTÚPILI
     Dôvod, prečo chcete ohlásiť tento obsah
@@ -555,7 +555,7 @@
     Správy obsahujúce moje používateľské meno
     Správy v priamych konverzáciách
     Správy v skupinových konverzáciách
-    Pozvania vstúpiť do miestnosti
+    Keď ma pozvú do miestnosti
     Audio / Video hovory
     Správy odosielané robotmi
     Spustiť po reštarte zariadenia
@@ -623,11 +623,11 @@
 \nUpozorňujeme, že táto akcia spôsobí reštart aplikácie a môže chvíľu trvať.
     Ste si istí, že chcete odstrániť tento cieľ oznámení?
     Ste si istí, že chcete odstrániť %1$s %2$s?
-    Vyberte krajnu
-    Krajnu
-    Prosím, vyberte krajnu
+    Vyberte si krajinu
+    Krajina
+    Prosím, vyberte si krajinu
     Telefónne číslo
-    Zadané číslo nie je správne pre vybratú krajnu
+    Neplatné telefónne číslo pre vybranú krajinu
     Overenie telefónneho čísla
     Odoslali sme vám SMS správu, ktorá obsahuje overovací kód. Prosím zadajte ho nižšie.
     Zadajte aktivačný kód
@@ -665,11 +665,11 @@
     Adresy
     Experimenty
     Tieto funkcie sú experimentálne a môžu sa nečakane pokaziť. Pri používaní buďte opatrní.
-    E2E šifrovanie
-    E2E šifrovanie je povolené
+    End-to-end šifrovanie
+    End-to-end šifrovanie je povolené
     Ak si želáte povoliť šifrovanie, musíte sa odhlásiť a následne prihlásiť znovu.
-    Šifrovať len známym reláciam
-    Nikdy neposielajte šifrované správy neovereným reláciam v tejto miestnosti z tejto relácie.
+    Šifrovať len pre overené relácie
+    Z tejto relácie nikdy neposielať šifrované správy neovereným reláciám v tejto miestnosti.
     Pre túto miestnosť nie sú žiadne lokálne adresy
     Nová adresa (napr. #foo:matrix.org)
     Nesprávny formát aliasu
@@ -680,18 +680,18 @@
     Zrušiť nastavenie ako hlavnej adresy
     Kopírovať ID miestnosti
     Kopírovať adresu miestnosti
-    V tejto miestnosti je povolené šifrovanie.
-    V tejto miestnosti nie je povolené šifrovanie.
+    V tejto miestnosti je zapnuté šifrovanie.
+    V tejto miestnosti vypnuté šifrovanie.
     Povoliť šifrovanie 
 \n(Pozor: nie je možné ho znova vypnúť!)
     Adresár
     Vzhľad
     %s sa pokúšal zobraziť konkrétny bod v histórii tejto miestnosti, no zodpovedajúcu udalosť sa nepodarilo nájsť.
-    Informácie o E2E šifrovaní
+    Informácie o end-to-end šifrovaní
     Informácie o udalosti
-    Používateľské id
+    ID používateľa
     Kľúč totožnosti curve25519
-    Údajne kľúč s odtlačkom prsta Ed25519
+    Deklarovaný kľúč odtlačku Ed25519
     Algoritmus
     ID relácie
     Chyba dešifrovania
@@ -701,7 +701,7 @@
     ID relácie
     Kľúč relácie
     Overenie
-    Odtlačok prsta Ed25519
+    Odtlačok Ed25519
     Exportovať šifrovacie kľúče miestnosti
     Exportovať kľúče miestnosti
     Exportovať kľúče do lokálneho súboru
@@ -715,8 +715,8 @@
     Importovať kľúče miestnosti
     Importovať kľúče z lokálneho súboru
     Importovať
-    Šifrovať len známym reláciam
-    Nikdy neposielať šifrované správy neovereným reláciam z tejto relácie.
+    Šifrovať len pre overené relácie
+    Nikdy neposielať šifrované správy neovereným reláciám z tejto relácie.
     Neoverené
     Overené
     Na čiernej listine
@@ -989,7 +989,7 @@
     Vykáže zadaného používateľa z miestnosti
     Mení vaše zobrazované meno / prezývku
     Zapnutie/vypnutie formátovanie textu markdown
-    Užitočné na opravu spravovania aplikácií matrix
+    Užitočné na opravu spravovania Matrix aplikácií
     
         1 člen
         %d členovia
@@ -1095,7 +1095,7 @@
     Deaktivovať obmedzenia
     Optimalizácia batérie
     Chod ${app_name} nie je ovplyvnený nastavením optimalizácie batérie.
-    Ak používateľ na nejaký čas ponechá zariadenie s vypnutou obrazovkou odložené odpojené od napájania, na zariadení sa použije režim Doze. Toto aplikáciám zabráni pristupovať k sieti, pozastaví ich naplánované úlohy, synchronizáciu aj bežné signály.
+    Ak používateľ nechá zariadenie na určitý čas odpojené od siete a nehybné s vypnutou obrazovkou, zariadenie prejde do režimu Doze. Tým sa aplikáciám zabráni v prístupe k sieti a oddialia sa ich úlohy, synchronizácia a štandardné alarmy.
     Ignorovať optimalizáciu
     Zobraziť náhľady odkazov v konverzáciách (ak sú podporované domovským serverom).
     Odosielať oznámenia pri písaní
@@ -1166,7 +1166,7 @@
     Vybrať farbu upozornení LED, vibrácie, zvuky…
     Správa šifrovacích kľúčov
     Odoslanie správy pomocou enter
-    Stlačením klávesu enter na dotykovej klávesnici odošlete správu namiesto odriadkovania
+    Tlačidlo Enter na softvérovej klávesnici odošle správu namiesto pridania zalomenia riadku
     Režim šetrenia údajov aplikuje filter, ktorý potlačí aktualizácie prítomnosti a oznámenia pri písaní.
     Aktualizovať heslo
     Heslo nie je platné
@@ -1201,7 +1201,7 @@
     Žiadny
     Zrušiť
     Odpojiť sa
-    Náhlad
+    Skontrolovať
     Odmietnuť
     Nebol nakonfigurovaný server totožnosti.
     Hovor zlyhal z dôvodu nesprávne nastaveného servera
@@ -1283,14 +1283,14 @@
     ${app_name} sa bude synchronizovať na pozadí s cieľom ušetriť limitované zdroje (batériu).
 \nV závislosti od kapacity prostriedkov zariadenia môže byť synchronizácia odložená operačným systémom na neskôr.
     Optimalizovaný na používanie v reálnom čase
-    ${app_name} sa bude pravideľne synchronizovať na pozadí v presne stanovenom čase (nastaviteľné).
-\nToto ovplyvní využívanie batérie a prenos údajov, v oznamovacej oblasti sa bude neustále zobrazovať upozornenie, že ${app_name} spracúva udalosti.
+    ${app_name} sa bude pravidelne synchronizovať na pozadí v presne stanovenom čase (nastaviteľné).
+\nBude to mať vplyv na používanie rádia a batérie, bude sa zobrazovať trvalé oznámenie, že ${app_name} počúva udalosti.
     Žiadna synchronizácia na pozadí
     Nebudete dostávať oznámenia o prichádzajúcich správach, keď aplikácia pracuje na pozadí.
     Nepodarilo sa aktualizovať nastavenia.
     Uprednostňovaný interval synchronizácie
     %s
-\nSynchronizácia môže byť odložená vzávislosti od kapacity zariadenia (batéria a režim spánku).
+\nSynchronizácia môže byť odložená v závislosti od zdrojov (batéria) alebo stavu zariadenia (režim spánok).
     Integrácie
     Použite správcu integrácií na nastavenie botov, premostení, widgetov a balíčkov s nálepkami.
 \nSprávcovia integrácie dostávajú konfiguračné údaje a môžu vo vašom mene upravovať widgety, posielať pozvánky do miestnosti a nastavovať úrovne oprávnení.
@@ -1368,7 +1368,7 @@
     Zadajte prosím prístupovú frázu
     Prístupová fráza je príliš slabá
     Ak chcete, aby ${app_name} vygeneroval kľúč na obnovenie, odstráňte prístupovú frázu.
-    Nie je dostupná žiadna relácia matrix
+    Nie je dostupná žiadna relácia Matrix
     Nikdy nepríďte o šifrované správy
     Správy v šifrovaných miestnostiach sú zabezpečené end-to-end šifrovaním. Kľúče na čítanie týchto správ máte len vy a príjemca (príjemcovia).
 \n
@@ -1447,12 +1447,12 @@
     Zálohovanie kľúčov nie je aktívne v tejto relácii.
     Kľúče z tejto relácie nie sú zálohované.
     Záloha je podpísaná kľúčom z neznámej relácie, ID %s.
-    Záloha je podpísana platným kľúčom z tejto relácie.
-    Záloha je podpísana platným kľúčom z overenej relácie %s.
-    Záloha je podpísana platným kľúčom z neoverenej relácie %s
-    Záloha je podpísana neplatným kľúčom z overenej relácie %s
-    Záloha je podpísana neplatným kľúčom z neoverenej relácie %s
-    Nepodarilo sa zistiť stav dôverihodnosti zálohy (%s).
+    Záloha má platný podpis z tejto relácie.
+    Záloha má platný podpis z overenej relácie %s.
+    Záloha má platný podpis z neoverenej relácie %s
+    Záloha má neplatný podpis z overenej relácie %s
+    Záloha má neplatný podpis z neoverenej relácie %s
+    Nepodarilo sa získať informácie o dôveryhodnosti pre zálohu (%s).
     Ak chcete použiť zálohovanie kľúčov v tejto relácii, obnovte ju teraz pomocou svojej prístupovej frázy alebo pomocou kľúča na obnovenie.
     Mazanie zálohy…
     Nepodarilo sa vymazať zálohu (%s)
@@ -1460,9 +1460,9 @@
     Vymazať zálohu
     Vymazať vaše zálohované šifrovacie kľúče z domovského servera\? Na čítanie histórie zašifrovaných správ už nebudete môcť použiť kľúč na obnovenie.
     Nová záloha kľúčov
-    Bola zistená nová bezpečná záloha šifrovacích kľúčov.
+    Bola zistená nová záloha kľúča zabezpečenej správy.
 \n
-\nAk ste si nenastavili nový spôsob obnovenia kľúčov, útočník sa môže pokúšať pristupovať k vášmu účtu. V nastaveniach si prosím hneď zmente prihlasovacie heslo účtu a nastavte zálohovanie kľúčov.
+\nAk ste nenastavili nový spôsob obnovy, útočník sa môže pokúsiť získať prístup k vášmu účtu. Okamžite zmeňte vaše heslo k účtu a nastavte novú metódu obnovy v Nastaveniach.
     Bol(a) som to ja
     Nikdy neprídete o šifrované správy
     Začnite používať zálohovanie kľúčov
@@ -1493,7 +1493,7 @@
     Z dôvodu dodržania maximálnej bezpečnosti odporúčame, aby ste toto urobili osobne alebo použili iný dôveryhodný komunikačný kanál.
     Spustiť overenie
     Prichádzajúca žiadosť o overenie
-    Overte túto reláciu, aby ste ju mohli označiť za dôveryhodnú. Dôverovanie reláciám vašich komunikačných partnerov vám pridáva pokoj na duši pri používaní E2E šifrovaných správ.
+    Overte túto reláciu a označte ju ako dôveryhodnú. Dôveryhodnosť relácií partnerov vám poskytuje pokoj na duši pri používaní end-to-end šifrovaných správ.
     Overením tejto relácie si ju označíte ako dôveryhodnú a tiež označíte vašu reláciu ako dôveryhodnú pre protistranu.
     Overte túto reláciu potvrdením, že na obrazovke protistrany sa zobrazia nasledujúce emoji
     Overte túto reláciu potvrdením, že na obrazovke protistrany sa zobrazia nasledujúce čísla
@@ -1502,7 +1502,7 @@
     Čakanie na potvrdenie protistrany…
     Overené!
     Úspešne ste overili túto reláciu.
-    Šifrované správy s týmto používateľom sú zabezpečené E2E šifrou a nik okrem vás ich nemôže čítať.
+    Zabezpečené správy s týmto používateľom sú end-to-end šifrované a tretie strany ich nemôžu čítať.
     Rozumiem
     Nič sa neobjavuje\? Ešte nie všetci klienti podporujú interaktívne overenie. Použite pôvodný spôsob overenia.
     Použiť starší spôsob overenia.
@@ -1578,7 +1578,7 @@
 \n%s
     Pri získavaní stavu dôveryhodnosti nastala chyba
     Pri získavaní údajov o zálohe kľúčov nastala chyba
-    Importovať E2E kľúče zo súboru \"%1$s\".
+    Importovať šifrovacie kľúče zo súboru \"%1$s\".
     Verzia Matrix SDK
     Ďalšie poznámky tretích strán
     Už máte túto miestnosť zobrazenú!
@@ -1621,21 +1621,21 @@
     Vytvoriť novú miestnosť
     Poslať priamu správu
     Zobraziť adresár miestností
-    Názov alebo ID (#priklad:matrix.org)
+    Názov alebo ID (#napriklad:matrix.org)
     Povoliť posunutím odpovedať na časovej osi
     Zobrazovať záložku oznámenia na hlavnej obrazovke.
     Odkaz skopírovaný do schránky
-    Pridať matrix ID
+    Pridať pomocou Matrix ID
     Vytváranie miestnosti…
-    Nenájdený žiadny výsledok, ak chcete hľadať na servery, klepnite na pridať Matrix ID.
+    Nenašiel sa žiadny výsledok, na vyhľadávanie na serveri použite Pridať pomocou Matrix ID.
     Začnite písať a uvidíte výsledky hneď
-    Filtrovať zadaním používateľskeho mena alebo ID…
+    Filtrovať pomocou používateľského mena alebo ID…
     Vstupovanie do miestnosti…
     Zobraziť históriu úprav
     Podmienky poskytovania služby
     Prečítať podmienky
     Umožnite ostatným vás nájsť
-    Používajte botov, mosty, widgety a balíčky s nálepkami
+    Použiť boty, premostenia, widgety a balíčky s nálepkami
     Prečítajte si na adrese
     Server totožností
     Odpojiť server totožností
@@ -1835,7 +1835,7 @@
     Zverejniť túto adresu
     Ďalšie zverejnené adresy:
     Nemôžete napísať priamu správu sebe!
-    Pridať tému
+    Pridajte tému
     %1$d z %2$d
     Žiadne ďalšie výsledky
     Téma miestnosti (voliteľné)
@@ -2109,7 +2109,7 @@
     Nemáte oprávnenie na začatie hovoru v tejto miestnosti
     Nemáte oprávnenie na začatie konferenčného hovoru
     Chýbajúce oprávnenia
-    Abyste mohli posielať hlasové správy, udeľte prosím oprávnenie používať mikrofón.
+    Aby ste mohli posielať hlasové správy, udeľte prosím oprávnenie používať mikrofón.
     Na vykonanie tejto akcie, prosím udeľte oprávnenie na používanie fotoaparátu cez systémové nastavenia.
     Chýbajú niektoré oprávnenia potrebné na vykonanie tejto akcie, prosím udeľte ich cez systémové nastavenia.
     Čakanie na upozornenia
@@ -2236,8 +2236,8 @@
     Udržujte konverzácie súkromné pomocou šifrovania
     Komunikujte s ľuďmi priamo alebo v skupinách
     Je to vaša konverzácia. Vlastnite ju.
-    Vytvorili ste miestnosť len pre pozvaných.
-    %1$s vytvoril miestnosť len na pozvanie.
+    Nastavili ste miestnosť len pre pozvaných.
+    %1$s nastavil miestnosť len na pozvanie.
     Miestnosť ste zverejnili pre každého, kto pozná odkaz.
     %1$s zverejnil miestnosť pre každého, kto pozná odkaz.
     Dlhým kliknutím na miestnosť zobrazíte ďalšie možnosti
@@ -2284,8 +2284,8 @@
     Je nám ľúto, ale tento server neprijíma nové účty.
     Prihlásiť sa cez %s
     Prihlásiť sa cez %s
-    Ste ju vytvorili len na pozvanie.
-    %1$s ju vytvoril len na pozvanie.
+    Ste ju nastavili len na pozvanie.
+    %1$s ju nastavil/a len na pozvanie.
     Otočiť a orezať
     Pridať obrázok z
     Súbor je príliš veľký na odoslanie.
@@ -2490,7 +2490,7 @@
     Kvôli end-to-end šifrovaniu sa môže stať, že budete musieť chvíľu počkať, kým vám od niekoho príde správa, pretože vám neboli správne odoslané šifrovacie kľúče.
     Správy v tejto miestnosti sú šifrované od vás až k príjemcovi.
     Správy v tejto miestnosti sú end-to-end šifrované. Zistite viac a overte používateľov v ich profile.
-    Správca vášho servera predvolene vypol šifrovanie end-to-end v súkromných miestnostiach a priamych správach.
+    Správca vášho servera predvolene vypol end-to-end šifrovanie v súkromných miestnostiach a v priamych správach.
     Správy s týmto používateľom sú end-to-end šifrované a tretie strany ich nemôžu čítať.
     Správy sú tu end-to-end šifrované .
 \n
@@ -2557,7 +2557,7 @@
     Nastaviť nové heslo k účtu…
     Nepoužívajte heslo k svojmu účtu.
     Zadajte svoje %s pre pokračovanie.
-    Ak ste si vytvorili účet na domovskom serveri, použite svoje Matrix ID (napr. @user:domain.com) a heslo uvedené nižšie.
+    Ak ste si vytvorili účet na domovskom serveri, použite svoje Matrix ID (napr. @pouzivatel:domena.sk) a heslo.
     Ak už máte účet a poznáte svoj identifikátor Matrix a heslo, môžete použiť aj túto metódu:
     Tento e-mail nie je prepojený so žiadnym účtom
     Vyzerá to, že váš domovský server zatiaľ nepodporuje Priestory
@@ -2828,7 +2828,7 @@
     Nemáte oprávnenie povoliť šifrovanie v tejto miestnosti.
     Odošle daný emotív sfarbený ako dúha
     Odošle danú správu vo farbe dúhy
-    Táto relácia nemôže zdieľať toto overenie s ostatnými reláciami.
+    Táto relácia nemôže zdieľať toto overenie s vašimi ostatnými reláciami.
 \nOverenie bude uložené lokálne a zdieľané v budúcej verzii aplikácie.
     Akcie správcu
     V záujme zvýšenia bezpečnosti overte %s kontrolou jednorazového kódu na oboch zariadeniach.
@@ -3070,4 +3070,61 @@
     Vyzváňanie hovoru…
     Kopírovať odkaz na vlákno
     Zobraziť v miestnosti
+    Zobraziť potvrdenia o prečítaní
+    Importovať kľúč zo súboru
+    Odmietli ste tento hovor %s
+    Do galérie sa nepodarilo pridať mediálny súbor
+    Kľúč obnovy zálohy kľúča
+    Získavanie kľúča krivky
+    Adresa služby Element Matrix Services
+    Spojler
+    Odošle danú správu ako spojler
+    Nahlásené ako nevhodné
+    
+        %d používateľ prečítal
+        %d používatelia prečítali
+        %d používatelia prečítali
+    
+    %1$s a %2$s prečítali
+    %1$s, %2$s a %3$s prečítali
+    
+        %1$s, %2$s a %3$d ďalší prečítal
+        %1$s, %2$s a %3$d ďalší prečítali
+        %1$s, %2$s a %3$d ďalší prečítali
+    
+    Ste pozvaní
+    Dôveryhodná úroveň
+    Predvolená úroveň dôveryhodnosti
+    Niektoré správy neboli odoslané
+    Existujú neuložené zmeny. Chcete zrušiť tieto zmeny\?
+    Odkaz na Matrix
+    Túto miestnosť nie je možné nájsť. Uistite sa, že existuje.
+    Nemôžete otvoriť miestnosť, do ktorej máte zákaz vstupu.
+    Nie je možné nájsť tajné údaje v úložisku
+    Tento obsah bol nahlásený ako nevhodný.
+\n
+\nAk nechcete vidieť ďalší obsah od tohto používateľa, môžete ho ignorovať a skryť jeho správy.
+    Známi používatelia
+    Túto miestnosť nie je možné zobraziť v náhľade. Chcete sa k nej pripojiť\?
+    Otvoriť výber emotikonov
+    Zatvoriť výber emotikonov
+    Správa nebola odoslaná kvôli chybe
+    Oznámiť bez zvuku
+    Neoznamovať
+    Obsah udalosti
+    Žiadny obsah
+    Udalosť odoslaná!
+    ${app_name} nespracováva správy typu \'%1$s\'
+    ${app_name} nespracováva udalosti typu \'%1$s\'
+    Hľadať názov
+    Spätnú väzbu sa nepodarilo odoslať (%s)
+    Zobraziť informácie o ladení chýb na obrazovke
+    Nahradiť farbu prezývky
+    Chcete sa pripojiť k existujúcemu serveru\?
+    Udalosť bola upravená administrátorom miestnosti, dôvod: %1$s
+    Táto miestnosť je momentálne neprístupná.
+\nSkúste to neskôr alebo požiadajte správcu miestnosti, aby skontroloval, či máte prístup.
+    Presmerovať na používateľa %1$s
+    Počas presmerovania hovoru došlo k chybe
+    Presmerovať
 
\ No newline at end of file

From ed3c04ab6725e70c9f9d8dc4808f26eb6febdec7 Mon Sep 17 00:00:00 2001
From: Ernesto AV 
Date: Thu, 17 Feb 2022 05:58:06 +0000
Subject: [PATCH 477/581] Translated using Weblate (Spanish)

Currently translated at 95.6% (2665 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/es/
---
 vector/src/main/res/values-es/strings.xml | 57 ++++++++++++++++-------
 1 file changed, 40 insertions(+), 17 deletions(-)

diff --git a/vector/src/main/res/values-es/strings.xml b/vector/src/main/res/values-es/strings.xml
index 42054e3c0e8..6392d243fe2 100644
--- a/vector/src/main/res/values-es/strings.xml
+++ b/vector/src/main/res/values-es/strings.xml
@@ -62,14 +62,14 @@
     Mensaje eliminado [motivo: %1$s]
     Mensaje eliminado por %1$s [motivo: %2$s]
     %1$s ha revocado la invitación a unirse a la sala para %2$s
-    Sincronización Inicial
+    Sincronización inicial
 \nImportando cuenta…
-    Sincronización Inicial:
-\nImportando Salas
-    Sincronización Inicial:
-\nImportando Comunidades
-    Sincronización Inicial:
-\nImportando Datos de la Cuenta
+    Sincronización inicial:
+\nImportando salas
+    Sincronización inicial:
+\nImportando comunidades
+    Sincronización inicial:
+\nImportando datos de la cuenta
     Enviando mensaje…
     Borrar cola de envío
     %1$s ha invitado a %2$s. Razón: %3$s
@@ -82,14 +82,15 @@
     %1$s ha aceptado la invitación para %2$s. Razón: %3$s
     %1$s ha eliminado la dirección principal para esta sala.
     %s ha actualizado la sala.
-    Sincronización Inicial:
+    Sincronización inicial:
 \nImportando criptografía
     Sincronización Inicial:
-\nImportando Salas a las que te has unido
+\nCargando tus conversaciones
+\nEsto puede tomar un tiempo si te has unido a muchas salas
     Sincronización Inicial:
-\nImportando Salas a las que has sido invitada
-    Sincronización Inicial:
-\nImportando Salas Abandonadas
+\nImportando salas a las que te han invitado
+    Sincronización inicial:
+\nImportando salas abandonadas
     Invitación de %1$s. Razón: %2$s
     %1$s ha desbaneado a %2$s. Razón: %3$s
     %1$s envió una invitación a %2$s para que se una a la sala. Razón: %3$s
@@ -504,7 +505,7 @@ Por favor permite el acceso en la próxima ventana emergente para descubrir usua
     SESIONES
     Invitar
     Salir de esta sala
-    Vetar
+    Banear
     Quitar Veto
     Restablecer a usuario normal
     Convertir a moderador
@@ -848,7 +849,7 @@ Por favor permite el acceso en la próxima ventana emergente para descubrir usua
     ¿Seguro que quieres iniciar una llamada de voz?
     ¿Seguro que quieres iniciar una llamada de vídeo?
     Lista de Grupos
-    El usuario baneado lo echará de esta sala y evitará que se unan nuevamente.
+    Vetar un usuario lo echará de esta sala y evitará que se una nuevamente.
     Todos los mensajes (ruidoso)
     Todos los mensajes
     Solo menciones
@@ -1185,7 +1186,7 @@ Por favor permite el acceso en la próxima ventana emergente para descubrir usua
     Mostrar recibos de lectura
     Hacer click en los recibos de lectura para mostrar una lista detallada.
     Mostrar notificaciones de entrada y salida
-    Invitaciones, expulsiones y prohibiciones no se ven afectadas.
+    Invitaciones, expulsiones y vetos no se ven afectadas.
     Mostrar notificaciones de la cuenta
     Incluye cambios en el avatar y en el nombre.
     Enviar mensaje con intro
@@ -2413,7 +2414,7 @@ Por favor permite el acceso en la próxima ventana emergente para descubrir usua
         %d invitación
         %d invitaciones
     
-    Incluye invitar/unirse/expulsar/prohibir/mostrar cambios de nombre.
+    Incluye los eventos de invitar/unirse/abandonar/eliminar/vetar y los cambios de avatar/nombre.
     Mostrar eventos de los miembros de la sala
     ¡La notificación ha sido cliqueada!
     Tema del sistema
@@ -2921,7 +2922,7 @@ Por favor permite el acceso en la próxima ventana emergente para descubrir usua
     Vea y actualice los roles necesarios para cambiar varias partes del espacio.
     Permisos de espacio
     Quitar la prohibición al usuario le permitirá unirse al espacio nuevamente.
-    La prohibición del usuario lo expulsara de este espacio y evitará que se una nuevamente.
+    Vetar un usuario lo expulsará de este espacio y evitará que se una nuevamente.
     Patear al usuario lo eliminará de este espacio.
 \n
 \nPara evitar que se unan nuevamente, debe prohibirlos.
@@ -2970,4 +2971,26 @@ Por favor permite el acceso en la próxima ventana emergente para descubrir usua
     Todas las salas en las que se encuentra se mostraran en inicio.
     Mostrar todas las salas en inicio
     Agregar un espacio a cualquier espacio que administre.
+    Añadir espacios existentes
+    ¡Se ha cerrado la sesión!
+    ¡Se ha abandonado la sala!
+    Consejo: Pulse prolongadamente un mensaje y use \"%s\" .
+    Mantén las conversaciones organizadas con hilos
+    Muestra todos los hilos en que has participado
+    Mis Hilos
+    Muestra todos los hilos de la sala actual
+    Todos los Hilos
+    Hilos
+    Hilo
+    Filtrar Hilos en la sala
+    El cifrado está mal configurado por lo que no puedes enviar mensajes. Haz clic para abrir los ajustes.
+    El cifrado está mal configurado, por lo que no puedes enviar mensajes. Por favor, contacta un administrador para que restablezca el cifrado a un estado válido.
+    Elegir servidor doméstico
+    No se puede acceder al servidor con la URL %s. Por favor revisa el enlace o elige otro servidor manualmente.
+    Ahora no
+    Ver Hilos
+    Captando notificaciones
+    Ver en la sala
+    Habilitar
+    No estás autorizado a unirte a esta sala
 
\ No newline at end of file

From 3bae8c9d7a501bd743a24e8131da0fcb2b77403c Mon Sep 17 00:00:00 2001
From: Retired Lawyer 
Date: Thu, 17 Feb 2022 22:59:09 +0000
Subject: [PATCH 478/581] Translated using Weblate (Turkish)

Currently translated at 76.1% (2122 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/tr/
---
 vector/src/main/res/values-tr/strings.xml | 64 +++++++++++++++++++++++
 1 file changed, 64 insertions(+)

diff --git a/vector/src/main/res/values-tr/strings.xml b/vector/src/main/res/values-tr/strings.xml
index e7e9fd0e8c3..d42e2532a1d 100644
--- a/vector/src/main/res/values-tr/strings.xml
+++ b/vector/src/main/res/values-tr/strings.xml
@@ -2322,4 +2322,68 @@
     Yalnızca geçerli odadaki görünen takma adınızı değiştirir
     Oda adını ayarlar
     Bir kullanıcıyı görmezden gelmeyi durdurur, mesajlarını ileriye doğru gösterir
+    Yine de Katıl
+    Alana Katıl
+    Alan Oluştur
+    Şimdilik atla
+    Sadece bu odaya
+    Link paylaş
+    Kullanıcı adı veya mail ile davet et
+    Kullanıcı adı ile davet et
+    Mail ile davet et
+    %syi davet et
+    İnsanları davet et
+    Alanınıza insanları davet edin
+    Açıklama
+    Alan Oluşturuluyor…
+    Rastgele
+    Genel
+    Takım arkadaşların kimler\?
+    Alan oluştur
+    Yalnızca davet edin, kendiniz veya ekipleriniz için en iyisi
+    Özel
+    Herkese açık, topluluklar için en iyisi
+    Halka açık
+    Ben ve takım arkadaşlarım
+    Yalnızca ben
+    Kiminle çalışıyorsun\?
+    Konum paylaş
+    Bu odadaki mesajlar uçtan uca şifrelenir.
+\n
+\nMesajlarınız şifrelerle korunur ve yalnızca siz ve alıcı, mesajların kilidini açmak için benzersiz anahtarlara sahip olursunuz.
+    Buradaki mesajlar uçtan uca şifrelenmez.
+    Bu odadaki mesajlar uçtan uca şifrelenmez.
+    Ekstra güvenlik için, her iki cihazınızda da tek seferlik bir kodu kontrol ederek %s\'yi doğrulayın.
+\n
+\nMaksimum güvenlik için bunu bizzat yapın.
+    %s bekleniyor…
+    %s doğrulandı
+    %s\'yi doğrula
+    QR kod resmi
+    Yukarıdaki kodu tarayamıyorsanız, kısa, benzersiz bir emoji seçimini karşılaştırarak doğrulayın.
+    Emojiyi doğrula
+    Emojileri karşılaştırarak doğrulayın
+    Sunucuya bağlan
+    Mevcut bir sunucuya katılmak mı istiyorsun\?
+    bu soruyu atla
+    Henüz emin değil misin\? %s yapabilirsin
+    Topluluklar
+    Takımlar
+    Arkadaşlar ve aile
+    Bağlanmanıza yardımcı olacağız.
+    Bu konuyu zaten görüntülüyorsun!
+    Odada Görüntüle
+    Bir Konudan
+    İpucu: Bir mesaja uzun dokunun ve “%s” kullanın.
+    Katıldığın tüm konuları göster
+    Konularım
+    Geçerli odadaki tüm konuları göster
+    Tüm Konular
+    Filtre
+    Konular
+    Konu
+    Odadaki Konuları Filtrele
+    Konu bağlantısını kopyala
+    Odada görüntüle
+    Konuları Görüntüle
 
\ No newline at end of file

From 81706b2f7fd6470cbdcc8351aae7f63915fe68cd Mon Sep 17 00:00:00 2001
From: lvre <7uu3qrbvm@relay.firefox.com>
Date: Sat, 12 Feb 2022 18:38:25 +0000
Subject: [PATCH 479/581] Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (2785 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/pt_BR/
---
 vector/src/main/res/values-pt-rBR/strings.xml | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/vector/src/main/res/values-pt-rBR/strings.xml
index 973a82ebfb8..9eabb09e697 100644
--- a/vector/src/main/res/values-pt-rBR/strings.xml
+++ b/vector/src/main/res/values-pt-rBR/strings.xml
@@ -324,7 +324,7 @@
     Filtrar pessoas
     Filtrar nomes de salas
     Convites
-    Baixa prioridade
+    Prioridade baixa
     Conversas
     Agenda de endereços local
     Contatos de Matrix somente
@@ -673,7 +673,7 @@
     Etiqueta da Sala
     Etiquetada como:
     Favoritar
-    Baixa prioridade
+    Prioridade baixa
     Nenhuma
     Acesso e visibilidade
     Listar esta sala em diretório de salas
@@ -2404,8 +2404,8 @@
         %d convite
         %d convites
     
-    Remover de baixa prioridade
-    Adicionar a baixa prioridade
+    Remover de prioridade baixa
+    Adicionar a prioridade baixa
     Adicionar imagem de
     Descartar mudanças
     Existem mudanças não-salvas. Descartar as mudanças\?
@@ -2896,7 +2896,7 @@
     Configurações de conta
     Você pode gerenciar notificações em %1$s.
     Por favor note que notificações de menções & palavrachave não estão disponíveis em salas encriptadas no celular.
-    Notifique-me por
+    Notifique-me para
     Você não vai ter notificações para menções & palavrachaves em salas encriptadas no celular.
     Palavrachaves
     \@room

From 05690c66e01e2018758aede76a7d53cb464441eb Mon Sep 17 00:00:00 2001
From: LinAGKar 
Date: Tue, 15 Feb 2022 16:11:41 +0000
Subject: [PATCH 480/581] Translated using Weblate (Swedish)

Currently translated at 99.9% (2784 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/sv/
---
 vector/src/main/res/values-sv/strings.xml | 34 +++++++++++------------
 1 file changed, 17 insertions(+), 17 deletions(-)

diff --git a/vector/src/main/res/values-sv/strings.xml b/vector/src/main/res/values-sv/strings.xml
index c0d4b757e4a..45e3b550071 100644
--- a/vector/src/main/res/values-sv/strings.xml
+++ b/vector/src/main/res/values-sv/strings.xml
@@ -76,7 +76,7 @@
     Meddelande borttaget av %1$s
     Meddelande borttaget [anledning: %1$s]
     Meddelande borttaget av %1$s [anledning: %2$s]
-    %1$s uppdaterade sim profil %2$s
+    %1$s uppdaterade sin profil %2$s
     Du uppdaterade din profil %1$s
     %1$s bjöd in %2$s att gå med i rummet
     Du bjöd in %1$s att gå med i rummet
@@ -582,7 +582,7 @@
     Fortsätt
     Ta bort
     Gå med
-    Förhandsvisning
+    Förhandsgranskning
     Avslå
     Lista medlemmar
     Öppna rubrik
@@ -593,7 +593,7 @@
 \nDu kanske vill logga in på ett annat konto eller lägga till den här e-postadressen till det här kontot.
     Du försöker komma åt %s. Skulle du vilja gå med för att delta i diskussionen\?
     ett rum
-    Det här är en förhandsvisning av det här rummet. Rumsinteraktioner har blivit inaktiverade.
+    Det här är en förhandsgranskning av det här rummet. Rumsinteraktioner har blivit inaktiverade.
     Lägg till en identitetsserver i dina inställningar för att utföra den här handlingen.
     Ny chatt
     Lägg till medlem
@@ -740,7 +740,7 @@
     Lagra din återställningsnyckel på något säkert ställe, t.ex. en lösenordshanterare (eller ett kassaskåp)
     En ny säker säkerhetskopia för meddelandenycklar har hittats.
 \n
-\nOm det inte var du som satte upp den nya återställningsmetoden så kan det vara en angripare som försöker komma åt ditt konto. Ändra ditt kontolösenord och sätt en ny återställningsmetod omedelbart i inställningarna.
+\nOm det inte var du som satte upp den nya återställningsmetoden så kan det vara en angripare som försöker komma åt ditt konto. Byt ditt kontolösenord och sätt en ny återställningsmetod omedelbart i inställningarna.
     Ingen identitetsserver är konfigurerad, vilket krävs för att återställa ditt lösenord.
     Byt nätverk
     Alla gemenskaper
@@ -1469,7 +1469,7 @@
     Om du har skapat ett konto på en hemserver så kan du använda ditt Matrix-ID (t.ex. @användare:domän.com) och lösenord nedan.
     Detta kan bero på flera orsaker:
 \n
-\n• Du har ändrat ditt lösenord från en annan session.
+\n• Du har bytt ditt lösenord från en annan session.
 \n
 \n• Du har tagit bort den här sessionen från en annan session.
 \n
@@ -1533,7 +1533,7 @@
 \n- Den här enheten, eller den andra enheten
 \n- Internetuppkopplingen en av enheterna använder
 \n
-\nVi rekommenderar att du ändrar ditt lösenord och återställningsnyckel i inställningarna omedelbart.
+\nVi rekommenderar att du byter ditt lösenord och återställningsnyckel i inställningarna omedelbart.
     Sätter upp nyckelsäkerhetskopiering
     Spara den på ett USB-minne eller en säkerhetskopieringsenhet
     Nästan färdigt! Visar den andra enheten en bock\?
@@ -1771,7 +1771,7 @@
     Går med i rummet med den angivna adressen
     Sätt ett rumsämne
     Sparkar ut användaren med det angivna ID:t
-    Ändrar ditt visningsnamn
+    Byter ditt visningsnamn
     För att fixa Matrixapphantering
     Skapa
     Skapa gemenskap
@@ -2034,9 +2034,9 @@
     Ditt lösenord har återställts.
     Tillbaka till inloggning
     Varning
-    Ditt lösenord har inte ändrats än.
+    Ditt lösenord har inte bytts än.
 \n
-\nVill du avbryta återställningsprocessen\?
+\nStoppa lösenordsbytesprocessen\?
     Nästa
     Sätt telefonnummer
     Vänligen använd internationellt format.
@@ -2179,7 +2179,7 @@
         Skicka bilder i originalstorlek
     
     Bekräfta borttagning
-    Är du säker på att du vill ta bort (radera) den här händelsen\? Observera att om du raderar ett rumsnamn eller ämnesändring så kan det upphäva ändringen.
+    Är du säker på att du vill ta bort (radera) den här händelsen\? Observera att om du raderar ett rumsnamns- eller ämnesbyte så kan det upphäva ändringen.
     Inkludera en anledning
     Anledning för borttagning
     Händelsen raderades av användaren, anledning: %1$s
@@ -2429,7 +2429,7 @@
     Ogiltig QR-kod (ogiltig URI)!
     Kan inte DMa dig själv!
     Dela med text
-    Ändra din nuvarande PIN-kod
+    Byt din nuvarande PIN-kod
     Ändra PIN-kod
     Sök efter kontakter på Matrix
     Sätt avatar
@@ -2520,7 +2520,7 @@
     Visa emojitangentbord
     Använda /confetti för att skicka meddelanden som innehåller ❄️ eller 🎉
     Visa chatteffekter
-    Ändra ämnet
+    Byta ämnet
     Uppgradera rummet
     Skicka m.room.server_acl-händelser
     Ändra behörigheter
@@ -2645,7 +2645,7 @@
     Skickar
     Rumskatalog
     Meddelande skickat
-    Lägg till några detaljer för att hjälpa det satt sticka ut. Du kan ändra dessa när som helst.
+    Lägg till några detaljer för att hjälpa det att sticka ut. Du kan ändra dessa när som helst.
     Skapa ett utrymme
     Endast inbjudan, bäst för dig själv eller grupper
     Privat
@@ -2782,7 +2782,7 @@
     För att utföra detta, vänligen ge kameraåtkomst från systeminställningarna.
     Vissa behörigheter saknas för att utföra detta, vänligen ge behörighet från systeminställningarna.
     Observera att uppgradering kommer att göra en ny version av rummet. Alla nuvarande meddelanden kommer att vara kvar i det här arkiverade rummet.
-    em som helst i föräldrautrymmet kommer kunna hitta och gå med i det här rummen - du behöver inte bjuda in alla. Du kommer kunna ändra detta i rumsinställningarna när som helst.
+    Vem som helst i föräldrautrymmet kommer kunna hitta och gå med i det här rummen - du behöver inte bjuda in alla. Du kommer kunna ändra detta i rumsinställningarna när som helst.
     Vem som helst i %s kommer kunna hitta och gå med i det här rummen - du behöver inte bjuda in alla. Du kommer kunna ändra detta i rumsinställningarna när som helst.
     Röstmeddelande (%1$s)
     Kan inte svara eller redigera när röstmeddelande är aktivt
@@ -2947,10 +2947,10 @@
     För att få e-post med aviseringar, vänligen associera en e-postadress med ditt Matrixkonto
     E-postavisering
     Uppgradera utrymmet
-    Byt utrymmets namn
+    Byta utrymmets namn
     Aktivera utrymmeskryptering
-    Byt huvudadress för det här utrymmet
-    Byt utrymmets avatar
+    Byta huvudadress för det här utrymmet
+    Byta utrymmets avatar
     Du är inte behörig att uppdatera rollerna som krävs för att ändra diverse delar av det här utrymmet
     Välj de roller som krävs för att ändra diverse delar av det här utrymmet
     Se och uppdatera rollerna som krävs för att ändra diverse delar av utrymmet.

From a62365d2b0ae04d5d69d27e69dbc58e4d447a2ff Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Sat, 19 Feb 2022 15:01:18 +0000
Subject: [PATCH 481/581] Translated using Weblate (Japanese)

Currently translated at 82.1% (2289 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index c0b1141a727..25d5a0517d8 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -1167,7 +1167,7 @@
     未読メッセージ
     タイムラインでのスワイプによる返信を有効にする
     タイムラインで非表示のイベントを表示
-    QR コードをスキャン
+    QRコードをスキャン
     QR コード画像
     QR コード
     QR コードによる追加
@@ -2494,7 +2494,7 @@
 \n
 \n暗号鍵を手動でエクスポートできるように、次のポップアップでアクセスを許可してください。
     %1$sはリンクを知っている人がアクセスできるようにこのルームを設定しました。
-    まず、設定でIDサーバーの条件に同意してください。
+    まず、設定画面でIDサーバーの利用規約に同意してください。
     初めにIDサーバーを設定してください。
     
         %1$d個の投票があります。結果を見るには投票してください

From cdea53b096acfbfc8f58e48a8884a536279feead Mon Sep 17 00:00:00 2001
From: JokerGermany 
Date: Fri, 18 Feb 2022 12:59:23 +0000
Subject: [PATCH 482/581] Translated using Weblate (German)

Currently translated at 99.7% (2779 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/de/
---
 vector/src/main/res/values-de/strings.xml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/vector/src/main/res/values-de/strings.xml b/vector/src/main/res/values-de/strings.xml
index 622db6324d8..ab83dd07b97 100644
--- a/vector/src/main/res/values-de/strings.xml
+++ b/vector/src/main/res/values-de/strings.xml
@@ -2900,8 +2900,8 @@
     Auf deinem Mobilgerät wirst du keine Benachrichtigungen für Erwähnungen und Schlüsselwörter in verschlüsselten Räumen erhalten.
     Existierende Spaces hinzufügen
     Existierende Räume hinzufügen
-    Ausgewählte Räume oder Spaces verlassen…
-    Keine Räume und Spaces verlassen
+    Ausgewählte Räume oder Subspaces verlassen…
+    Keine Räume und Subspaces verlassen
     Du wirst alle Räume und Spaces in %s verlassen.
     Alle Räume und Spaces verlassen
     Willst du %s wirklich verlassen\?

From 6ef8b71d911885c32ca184a70ef052d6f14cc3be Mon Sep 17 00:00:00 2001
From: noantiq 
Date: Sat, 12 Feb 2022 13:08:05 +0000
Subject: [PATCH 483/581] Translated using Weblate (German)

Currently translated at 99.7% (2779 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/de/
---
 vector/src/main/res/values-de/strings.xml | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/vector/src/main/res/values-de/strings.xml b/vector/src/main/res/values-de/strings.xml
index ab83dd07b97..5852e2de317 100644
--- a/vector/src/main/res/values-de/strings.xml
+++ b/vector/src/main/res/values-de/strings.xml
@@ -154,7 +154,7 @@
     %1$s hat das %2$s Widget modifiziert
     Du hast das %1$s Widget modifiziert
     Admin
-    Moderator
+    Moderation
     Standard
     Benutzerdefiniert (%1$d)
     Benutzerdefiniert
@@ -505,8 +505,8 @@
     Verbannen
     Verbannung aufheben
     Zum normalen Benutzer herabstufen
-    Zum Moderator machen
-    Zum Admin machen
+    Moderationsrechte vergeben
+    Administrationsrechte vergeben
     Blockieren
     Alle Nachrichten dieses Nutzers anzeigen
     Nutzer-ID, Name oder E-Mail-Adresse
@@ -845,7 +845,7 @@
     Sicher, dass du einen Sprachanruf starten möchtest\?
     Sicher, dass du einen Videoanruf starten möchtest\?
     Gruppenliste
-    Ein Bann schließt die Person aus dem Raum aus und verhindert einen erneuten Beitritt.
+    Die Verbannung einer Person entfernt sie aus diesem Raum und hindert sie am erneuten Beitritt.
     Alle Nachrichten (laut)
     Alle Nachrichten
     Nur Erwähnungen
@@ -1076,7 +1076,7 @@
     Formatiere Nachrichten mittels Markdown-Syntax, bevor sie gesendet werden. Dies erlaubt erweiterte Formatierungen wie Sternchen (*), um kursiven Text anzuzeigen.
     Lesebestätigungen zeigen
     Klicke auf die Lesebestätigungen für eine detailliertere Liste.
-    Einladungen, Kicks und Banns werden weiterhin angezeigt.
+    Einladungen, Entfernungen und Verbannungen bleiben sichtbar.
     Passwort
     Starte die System-Kamera anstelle der angepassten Kamera.
     Diese Option erfordert eine externe Anwendung um Sprachnachrichten aufzuzeichnen.
@@ -1864,7 +1864,7 @@
     Raum verlassen
     Verlasse den Raum…
     Administratoren
-    Moderatoren
+    Moderationen
     Benutzerdefiniert
     Eingeladen
     Nutzer
@@ -2273,7 +2273,7 @@
     Einladung zurücknehmen
     Einladung zu %1$s zurücknehmen\?
     Gebannt von %1$s
-    Aufheben des Banns fehlgeschlagen
+    Aufhebung der Verbannung fehlgeschlagen
     Push-Benachrichtigungen sind deaktiviert
     Gehe zu deinen Einstellungen um Push-Benachrichtigungen zu aktivieren
     Nutze eine PIN für mehr Sicherheit
@@ -2337,7 +2337,7 @@
         %d Sekunden
     
     Status-Ereignisse der Raummitglieder zeigen
-    Bezieht Einladungs-/Beitritts-/Verlassen-/Entfernen-/Verbannen-Ereignisse und Avatar-/Anzeigenamen-Wechsel mit ein.
+    Bezieht Einladungs-/Beitritts-/Verlassen-/Entfernungs-/Verbannungs-Ereignisse und Wechsel des Avatar/Anzeigenamen mit ein.
     Umfrage
     Reagierte mit: %s
     Der Link war fehlerhaft
@@ -2926,7 +2926,7 @@
     Du hast nicht die Berechtigung, Rollenrechte zu bearbeiten
     Space-Berechtigungen
     Wenn du die Person entbannst, kann sie wieder beitreten.
-    Wenn du eine Person bannst, kann sie nicht erneut beitreten.
+    Die Verbannung einer Person entfernt sie aus diesem Space und hindert sie am erneuten Beitritt.
     Kicken entfernt die Person aus dem Space
 \n
 \nUm sie für immer zu entfernen, solltest du sie bannen.

From e42945518364de6ecb147cb1321933f7b8e5cf93 Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Fri, 18 Feb 2022 15:36:43 +0000
Subject: [PATCH 484/581] Translated using Weblate (Japanese)

Currently translated at 38.7% (19 of 49 strings)

Translation: Element Android/Element Android Store
Translate-URL: https://translate.element.io/projects/element-android/element-store/ja/
---
 .../android/ja-JP/changelogs/40100100.txt     |  2 +-
 .../android/ja-JP/changelogs/40100110.txt     |  2 +-
 .../android/ja-JP/changelogs/40100120.txt     |  2 +-
 .../android/ja-JP/changelogs/40100130.txt     |  2 +-
 .../android/ja-JP/changelogs/40100140.txt     |  2 +-
 .../android/ja-JP/changelogs/40100150.txt     |  2 +-
 .../android/ja-JP/changelogs/40100160.txt     |  2 +-
 .../android/ja-JP/changelogs/40100170.txt     |  2 +-
 .../android/ja-JP/changelogs/40101000.txt     |  2 +-
 .../android/ja-JP/changelogs/40101010.txt     |  2 +-
 .../android/ja-JP/changelogs/40101020.txt     |  2 +-
 .../android/ja-JP/changelogs/40101030.txt     |  2 +-
 .../android/ja-JP/changelogs/40101160.txt     |  2 +-
 .../android/ja-JP/changelogs/40103160.txt     |  2 +
 .../android/ja-JP/changelogs/40103170.txt     |  2 +
 .../android/ja-JP/changelogs/40103180.txt     |  2 +
 .../android/ja-JP/full_description.txt        | 41 ++++++++++---------
 17 files changed, 41 insertions(+), 32 deletions(-)
 create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40103160.txt
 create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40103170.txt
 create mode 100644 fastlane/metadata/android/ja-JP/changelogs/40103180.txt

diff --git a/fastlane/metadata/android/ja-JP/changelogs/40100100.txt b/fastlane/metadata/android/ja-JP/changelogs/40100100.txt
index 8359a12964e..48af96d2160 100644
--- a/fastlane/metadata/android/ja-JP/changelogs/40100100.txt
+++ b/fastlane/metadata/android/ja-JP/changelogs/40100100.txt
@@ -1,2 +1,2 @@
 今回の新バージョンでは、主にバグの修正と改善が行われています。メッセージの送信がより速くなりました。
-すべての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.0.10
+全ての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.0.10
diff --git a/fastlane/metadata/android/ja-JP/changelogs/40100110.txt b/fastlane/metadata/android/ja-JP/changelogs/40100110.txt
index c93db421aff..b8b9798fcdc 100644
--- a/fastlane/metadata/android/ja-JP/changelogs/40100110.txt
+++ b/fastlane/metadata/android/ja-JP/changelogs/40100110.txt
@@ -1,2 +1,2 @@
 今回の新バージョンでは、主にUI(ユーザーインターフェース)とUX(ユーザーエクスペリエンス)の向上が図られています。友達を招待したり、QRコードを読み取って素早くDMを作成できるようになりました。
-すべての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.0.11
+全ての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.0.11
diff --git a/fastlane/metadata/android/ja-JP/changelogs/40100120.txt b/fastlane/metadata/android/ja-JP/changelogs/40100120.txt
index aace2ef79fd..01c33c5d525 100644
--- a/fastlane/metadata/android/ja-JP/changelogs/40100120.txt
+++ b/fastlane/metadata/android/ja-JP/changelogs/40100120.txt
@@ -1,2 +1,2 @@
 このバージョンの主な変更点: URLプレビュー、新しい絵文字、新しいルーム設定機能、それにクリスマスには雪が!
-すべての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.0.12
+全ての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.0.12
diff --git a/fastlane/metadata/android/ja-JP/changelogs/40100130.txt b/fastlane/metadata/android/ja-JP/changelogs/40100130.txt
index 97633621c5a..941a0522398 100644
--- a/fastlane/metadata/android/ja-JP/changelogs/40100130.txt
+++ b/fastlane/metadata/android/ja-JP/changelogs/40100130.txt
@@ -1,2 +1,2 @@
 このバージョンの主な変更点: URLプレビュー、新しい絵文字、新しいルーム設定機能、それにクリスマスには雪が!
-すべての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.0.13
+全ての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.0.13
diff --git a/fastlane/metadata/android/ja-JP/changelogs/40100140.txt b/fastlane/metadata/android/ja-JP/changelogs/40100140.txt
index c3406631270..6dc536cdcf1 100644
--- a/fastlane/metadata/android/ja-JP/changelogs/40100140.txt
+++ b/fastlane/metadata/android/ja-JP/changelogs/40100140.txt
@@ -1,2 +1,2 @@
 このバージョンの主な変更点: 部屋の許可、自動のテーマ切替、そして多くのバグを修正しました。
-すべての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.0.14
+全ての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.0.14
diff --git a/fastlane/metadata/android/ja-JP/changelogs/40100150.txt b/fastlane/metadata/android/ja-JP/changelogs/40100150.txt
index 42f28c7bea7..caded1b8ed0 100644
--- a/fastlane/metadata/android/ja-JP/changelogs/40100150.txt
+++ b/fastlane/metadata/android/ja-JP/changelogs/40100150.txt
@@ -1,2 +1,2 @@
 このバージョンの主な変更点: ソーシャルログインに対応しました。
-すべての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.0.15
+全ての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.0.15
diff --git a/fastlane/metadata/android/ja-JP/changelogs/40100160.txt b/fastlane/metadata/android/ja-JP/changelogs/40100160.txt
index 8b5196998a5..1b1a2092b0d 100644
--- a/fastlane/metadata/android/ja-JP/changelogs/40100160.txt
+++ b/fastlane/metadata/android/ja-JP/changelogs/40100160.txt
@@ -1,2 +1,2 @@
 このバージョンの主な変更点: パフォーマンスの向上とバグの修正!
-すべての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.0.15 and https://github.com/vector-im/element-android/releases/tag/v1.0.16
+全ての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.0.15 and https://github.com/vector-im/element-android/releases/tag/v1.0.16
diff --git a/fastlane/metadata/android/ja-JP/changelogs/40100170.txt b/fastlane/metadata/android/ja-JP/changelogs/40100170.txt
index 586b01cb2b9..a0cc7b107d6 100644
--- a/fastlane/metadata/android/ja-JP/changelogs/40100170.txt
+++ b/fastlane/metadata/android/ja-JP/changelogs/40100170.txt
@@ -1,2 +1,2 @@
 このバージョンの主な変更点: バグの修正!
-すべての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.0.17
+全ての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.0.17
diff --git a/fastlane/metadata/android/ja-JP/changelogs/40101000.txt b/fastlane/metadata/android/ja-JP/changelogs/40101000.txt
index 25bbd7ab876..d0900f38c2b 100644
--- a/fastlane/metadata/android/ja-JP/changelogs/40101000.txt
+++ b/fastlane/metadata/android/ja-JP/changelogs/40101000.txt
@@ -1,2 +1,2 @@
 このバージョンの主な変更点: パフォーマンスの向上とバグの修正!
-すべての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.1.0
+全ての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.1.0
diff --git a/fastlane/metadata/android/ja-JP/changelogs/40101010.txt b/fastlane/metadata/android/ja-JP/changelogs/40101010.txt
index 35ba933069e..cb204e56965 100644
--- a/fastlane/metadata/android/ja-JP/changelogs/40101010.txt
+++ b/fastlane/metadata/android/ja-JP/changelogs/40101010.txt
@@ -1,2 +1,2 @@
 このバージョンの主な変更点: パフォーマンスの向上とバグの修正!
-すべての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.1.1
+全ての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.1.1
diff --git a/fastlane/metadata/android/ja-JP/changelogs/40101020.txt b/fastlane/metadata/android/ja-JP/changelogs/40101020.txt
index 88e3c79ca8c..bb6ab665259 100644
--- a/fastlane/metadata/android/ja-JP/changelogs/40101020.txt
+++ b/fastlane/metadata/android/ja-JP/changelogs/40101020.txt
@@ -1,2 +1,2 @@
 このバージョンの主な変更点: パフォーマンスの向上とバグの修正!
-すべての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.1.2
+全ての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.1.2
diff --git a/fastlane/metadata/android/ja-JP/changelogs/40101030.txt b/fastlane/metadata/android/ja-JP/changelogs/40101030.txt
index 87d191b2269..e7ecc05a0f6 100644
--- a/fastlane/metadata/android/ja-JP/changelogs/40101030.txt
+++ b/fastlane/metadata/android/ja-JP/changelogs/40101030.txt
@@ -1,2 +1,2 @@
 このバージョンの主な変更点: パフォーマンスの向上とバグの修正!
-すべての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.1.3
+全ての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.1.3
diff --git a/fastlane/metadata/android/ja-JP/changelogs/40101160.txt b/fastlane/metadata/android/ja-JP/changelogs/40101160.txt
index a498487f46c..985ea105104 100644
--- a/fastlane/metadata/android/ja-JP/changelogs/40101160.txt
+++ b/fastlane/metadata/android/ja-JP/changelogs/40101160.txt
@@ -1,2 +1,2 @@
 このバージョンの主な変更点:ルームにて誰かがログアウトした際に発生するエラーを修正しました。
-すべての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.1.16
+全ての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.1.16
diff --git a/fastlane/metadata/android/ja-JP/changelogs/40103160.txt b/fastlane/metadata/android/ja-JP/changelogs/40103160.txt
new file mode 100644
index 00000000000..54758286235
--- /dev/null
+++ b/fastlane/metadata/android/ja-JP/changelogs/40103160.txt
@@ -0,0 +1,2 @@
+このバージョンの主な変更点:ルームへのロケーションの送信。投票機能の変更。
+更新履歴:https://github.com/vector-im/element-android/releases/tag/v1.3.16
diff --git a/fastlane/metadata/android/ja-JP/changelogs/40103170.txt b/fastlane/metadata/android/ja-JP/changelogs/40103170.txt
new file mode 100644
index 00000000000..61b91b54c0a
--- /dev/null
+++ b/fastlane/metadata/android/ja-JP/changelogs/40103170.txt
@@ -0,0 +1,2 @@
+このバージョンの主な変更点:ルームへのロケーションの送信。投票機能の変更。
+更新履歴:https://github.com/vector-im/element-android/releases/tag/v1.3.17
diff --git a/fastlane/metadata/android/ja-JP/changelogs/40103180.txt b/fastlane/metadata/android/ja-JP/changelogs/40103180.txt
new file mode 100644
index 00000000000..ae4f1a0a0fd
--- /dev/null
+++ b/fastlane/metadata/android/ja-JP/changelogs/40103180.txt
@@ -0,0 +1,2 @@
+このバージョンの主な変更点:ルームへのロケーションの送信。投票機能の変更。
+更新履歴:https://github.com/vector-im/element-android/releases/tag/v1.3.18
diff --git a/fastlane/metadata/android/ja-JP/full_description.txt b/fastlane/metadata/android/ja-JP/full_description.txt
index 4e7b01cce36..6014938cce3 100644
--- a/fastlane/metadata/android/ja-JP/full_description.txt
+++ b/fastlane/metadata/android/ja-JP/full_description.txt
@@ -1,39 +1,42 @@
-Elementはセキュアなメッセンジャーであると同時に、リモートワークでのグループチャットにも最適です。エンドツーエンドの暗号化を使用して、強力なビデオ会議、ファイル共有、音声通話を提供します。
+Elementは、安全なメッセンジャー、リモートワーク中のグループチャットに適したチームコラボレーションアプリです。エンドツーエンドの暗号化を使用して、強力なビデオ会議、ファイル共有、音声通話を提供します。
 
 Elementの特徴
 - 高度なオンラインコミュニケーションツール
-- 完全に暗号化されたメッセージ
+- 完全に暗号化されたメッセージにより、リモートワーカーでも、より安全な企業コミュニケーションが可能
 - Matrixオープンソースフレームワークをベースにした分散型のチャット
 - プロジェクトを管理しながら、暗号化されたデータで安全にファイル共有
 - Voice over IPによるビデオチャットと画面共有
-- お気に入りのオンラインコラボレーションツール、プロジェクト管理ツール、VoIPサービス、その他のチームメッセージングアプリと統合可能
+- お気に入りのオンラインコラボレーションツール、プロジェクト管理ツール、VoIPサービス、その他のチームメッセージングアプリと簡単に統合可能
 
-Elementは他のメッセージングアプリやコラボレーションアプリとは異なります。安全なメッセージングと分散型(非中央集権)コミュニケーションのためのオープンネットワークであるMatrixで動作します。また、ユーザーが自分のデータやメッセージを最大限にコントロールできるように、セルフホスティングも可能です。
+Elementは他のメッセージングアプリやコラボレーションアプリとは全く異なります。安全なメッセージングと分散型(非中央集権)コミュニケーションのためのオープンネットワークであるMatrixで動作します。ユーザーが自分のデータやメッセージを最大限にコントロールできるように、セルフホスティングも可能です。
 
-プライバシーと暗号化されたやりとり
-Elementは、望ましくない広告、データマイニング、ウォールドガーデンからユーザーを保護します。また、エンド・ツー・エンドの暗号化と相互署名されたデバイスの検証により、すべてのデータ、1対1のビデオおよび音声通信を保護します。
+プライバシーと暗号化されたコミュニケーション
+Elementは、望ましくない広告、データマイニング、ウォールドガーデンからユーザーを保護します。また、エンド・ツー・エンドの暗号化と相互署名された端末の検証により、全てのデータ、1対1のビデオおよび音声通信を保護します。
 
-Elementは、Slackなどのアプリと統合することで、Matrixネットワーク上の誰とでも安全にコミュニケーションをとることができると同時に、プライバシーをコントロールすることができます。
+Elementは、Slackなどのアプリと統合することで、Matrixネットワーク上の誰とでも安全にコミュニケーションを取ることができると同時に、プライバシーをコントロールすることができます。
 
 Elementはセルフホスティングが可能
-機密データや会話の管理を強化するために、Elementはセルフホスティングが可能で、オープンソースの分散型コミュニケーションの標準であるマトリックスベースのホストを選択することもできます。Elementは、プライバシー、セキュリティコンプライアンス、および統合の柔軟性を提供します。
+機密データや会話の管理を強化するために、Elementはセルフホスティングが可能です。または、オープンソースの分散型コミュニケーションの標準であるMatrixベースのホストを選択することもできます。Elementは、プライバシー、セキュリティーコンプライアンス、および統合の柔軟性を提供します。
 
-データを所有する
-データやメッセージをどこに保管するかは、お客様が決めることができます。データマイニングやサードパーティからのアクセスされません。
+自分のデータを所有する
+データやメッセージをどこに保管するかは、ユーザー自身が決めることができます。データマイニングやサードパーティからのアクセスのリスクはありません。
 
-Elementではどのサーバーを使うか決めることができます。さまざまな方法で選択できます。
+Elementでは、どのサーバーを使うかを、ご自身で決めることができます。
 1. 開発者がホストする matrix.org のパブリックサーバーで無料アカウントを取得するか、ボランティアがホストしているパブリックサーバーから選択する。
-2. 自分でサーバを実行することにより、アカウントをセルフホストする。
-3. Element Matrix Servicesのホスティングプラットフォームに加入しカスタムサーバー上でアカウントを作る。
+2. あなた自身がサーバーを運営し、アカウントを管理する。
+3. Element Matrix Servicesのホスティングプラットフォームに加入し、カスタムサーバー上でアカウントを作る。
 
 オープンなメッセージングとコラボレーション
-Matrixネットワーク上の誰とでも、相手がElementを使っているか、他のMatrixアプリを使っていてもコミュニケーションすることができます。
+Matrixネットワーク上の誰とでも、相手がElementや他のMatrixアプリを使っているか、さらには他のメッセージングアプリを使っているかに関わらず、チャットをすることができます。
 
-すごく安全
-本物のエンド・ツー・エンドの暗号化(会話に参加している人だけがメッセージを復号化できる)と、相互署名されたデバイスの検証を行います。
+非常に安全
+本物のエンド・ツー・エンドの暗号化(会話に参加している人だけがメッセージを復号化できる)と、相互署名された端末の検証を行います。
 
 包括的なコミュニケーションと統合
-メッセージング、音声およびビデオ通話、ファイル共有、画面共有、その他多くの統合、ボット、ウィジェットを提供します。部屋やコミュニティを作り、連絡を取り合い、物事を成し遂げることができます。
+メッセージング、音声およびビデオ通話、ファイル共有、画面共有、その他多くのインテグレーション、ボット、ウィジェットを提供します。ルームやコミュニティーを立ち上げて連絡を取り合い、物事をスムーズに成し遂げることができます。
 
-中断からの再開は
-すべてのデバイスとウェブで完全に同期されたメッセージにより、どこにいても連絡を取り合うことができます。https://app.element.io
+中断からの再開
+メッセージの履歴は全ての端末とウェブ(https://app.element.io)で完全に同期されるので、どこからでも連絡を取り合うことができます。
+
+オープンソース
+Element AndroidはGitHubで開発されているオープンソースのプロジェクトです。 バグの報告や開発への貢献は https://github.com/vector-im/element-android にて受け付けています。

From d5bb908f032afc9e8d0a3a364bbe64726ca992a0 Mon Sep 17 00:00:00 2001
From: Linerly 
Date: Fri, 18 Feb 2022 15:46:17 +0000
Subject: [PATCH 485/581] Translated using Weblate (Indonesian)

Currently translated at 100.0% (49 of 49 strings)

Translation: Element Android/Element Android Store
Translate-URL: https://translate.element.io/projects/element-android/element-store/id/
---
 fastlane/metadata/android/id/changelogs/40100100.txt | 2 +-
 fastlane/metadata/android/id/changelogs/40100110.txt | 2 +-
 fastlane/metadata/android/id/changelogs/40100120.txt | 2 +-
 fastlane/metadata/android/id/changelogs/40100130.txt | 2 +-
 fastlane/metadata/android/id/changelogs/40100140.txt | 2 +-
 fastlane/metadata/android/id/changelogs/40100150.txt | 2 +-
 fastlane/metadata/android/id/changelogs/40100160.txt | 2 +-
 fastlane/metadata/android/id/changelogs/40100170.txt | 2 +-
 fastlane/metadata/android/id/changelogs/40101000.txt | 2 +-
 fastlane/metadata/android/id/changelogs/40101010.txt | 2 +-
 fastlane/metadata/android/id/changelogs/40101020.txt | 2 +-
 fastlane/metadata/android/id/changelogs/40101030.txt | 2 +-
 fastlane/metadata/android/id/changelogs/40101040.txt | 2 +-
 fastlane/metadata/android/id/changelogs/40101050.txt | 2 +-
 fastlane/metadata/android/id/changelogs/40101060.txt | 2 +-
 fastlane/metadata/android/id/changelogs/40101070.txt | 2 +-
 fastlane/metadata/android/id/changelogs/40101080.txt | 2 +-
 fastlane/metadata/android/id/changelogs/40101090.txt | 2 +-
 fastlane/metadata/android/id/changelogs/40101100.txt | 2 +-
 fastlane/metadata/android/id/changelogs/40101110.txt | 2 +-
 fastlane/metadata/android/id/changelogs/40101120.txt | 2 +-
 fastlane/metadata/android/id/changelogs/40101130.txt | 2 +-
 fastlane/metadata/android/id/changelogs/40101140.txt | 2 +-
 fastlane/metadata/android/id/changelogs/40101150.txt | 2 +-
 fastlane/metadata/android/id/changelogs/40101160.txt | 2 +-
 fastlane/metadata/android/id/changelogs/40102000.txt | 2 +-
 fastlane/metadata/android/id/changelogs/40102010.txt | 2 +-
 fastlane/metadata/android/id/changelogs/40103000.txt | 2 +-
 fastlane/metadata/android/id/changelogs/40103020.txt | 2 +-
 fastlane/metadata/android/id/changelogs/40103030.txt | 2 +-
 fastlane/metadata/android/id/changelogs/40103040.txt | 2 +-
 fastlane/metadata/android/id/changelogs/40103050.txt | 2 +-
 fastlane/metadata/android/id/changelogs/40103060.txt | 2 +-
 fastlane/metadata/android/id/changelogs/40103100.txt | 2 +-
 fastlane/metadata/android/id/changelogs/40103110.txt | 2 +-
 fastlane/metadata/android/id/changelogs/40103120.txt | 2 +-
 fastlane/metadata/android/id/changelogs/40103130.txt | 2 +-
 fastlane/metadata/android/id/changelogs/40103140.txt | 2 +-
 fastlane/metadata/android/id/changelogs/40103150.txt | 2 +-
 fastlane/metadata/android/id/changelogs/40103160.txt | 4 ++--
 fastlane/metadata/android/id/changelogs/40103170.txt | 4 ++--
 fastlane/metadata/android/id/changelogs/40103180.txt | 4 ++--
 42 files changed, 45 insertions(+), 45 deletions(-)

diff --git a/fastlane/metadata/android/id/changelogs/40100100.txt b/fastlane/metadata/android/id/changelogs/40100100.txt
index 96a8f506b38..d4294758d89 100644
--- a/fastlane/metadata/android/id/changelogs/40100100.txt
+++ b/fastlane/metadata/android/id/changelogs/40100100.txt
@@ -1,2 +1,2 @@
 Versi baru ini terutama berisi perbaikan bug dan peningkatan. Mengirim pesan sekarang jauh lebih cepat.
-Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.0.10
+Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.0.10
diff --git a/fastlane/metadata/android/id/changelogs/40100110.txt b/fastlane/metadata/android/id/changelogs/40100110.txt
index 9f86005d8b1..07e969581c7 100644
--- a/fastlane/metadata/android/id/changelogs/40100110.txt
+++ b/fastlane/metadata/android/id/changelogs/40100110.txt
@@ -1,2 +1,2 @@
 Versi baru ini terutama berisi antarmuka pengguna dan peningkatan pengalaman pengguna. Sekarang Anda dapat mengundang teman, dan membuat sebuah DM sangat cepat dengan memindai kode QR.
-Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.0.11
+Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.0.11
diff --git a/fastlane/metadata/android/id/changelogs/40100120.txt b/fastlane/metadata/android/id/changelogs/40100120.txt
index 3067b6367d7..18adabfdcd7 100644
--- a/fastlane/metadata/android/id/changelogs/40100120.txt
+++ b/fastlane/metadata/android/id/changelogs/40100120.txt
@@ -1,2 +1,2 @@
 Perubahan utama dalam versi ini: Pratinjau URL, keyboard Emoji baru, kemampuan pengaturan ruangan baru, dan salju untuk Natal!
-Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.0.12
+Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.0.12
diff --git a/fastlane/metadata/android/id/changelogs/40100130.txt b/fastlane/metadata/android/id/changelogs/40100130.txt
index df52988b6c3..f94db4d7cdf 100644
--- a/fastlane/metadata/android/id/changelogs/40100130.txt
+++ b/fastlane/metadata/android/id/changelogs/40100130.txt
@@ -1,2 +1,2 @@
 Perubahan utama dalam versi ini: Pratinjau URL, keyboard Emoji baru, kemampuan pengaturan ruangan baru, dan salju untuk Natal!
-Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.0.13
+Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.0.13
diff --git a/fastlane/metadata/android/id/changelogs/40100140.txt b/fastlane/metadata/android/id/changelogs/40100140.txt
index 5243adc1a80..e1ef504cd95 100644
--- a/fastlane/metadata/android/id/changelogs/40100140.txt
+++ b/fastlane/metadata/android/id/changelogs/40100140.txt
@@ -1,2 +1,2 @@
 Perubahan utama dalam versi ini: Edit izin ruangan, tema cahaya/gelap otomatis, dan banyak perbaikan bug.
-Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.0.14
+Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.0.14
diff --git a/fastlane/metadata/android/id/changelogs/40100150.txt b/fastlane/metadata/android/id/changelogs/40100150.txt
index 54c307b9b67..eaf8e1a7152 100644
--- a/fastlane/metadata/android/id/changelogs/40100150.txt
+++ b/fastlane/metadata/android/id/changelogs/40100150.txt
@@ -1,2 +1,2 @@
 Perubahan utama dalam versi ini: Dukungan login sosial.
-Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.0.15
+Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.0.15
diff --git a/fastlane/metadata/android/id/changelogs/40100160.txt b/fastlane/metadata/android/id/changelogs/40100160.txt
index 3e357db3523..0a6d42f8f62 100644
--- a/fastlane/metadata/android/id/changelogs/40100160.txt
+++ b/fastlane/metadata/android/id/changelogs/40100160.txt
@@ -1,2 +1,2 @@
 Perubahan utama dalam versi ini: Dukungan login sosial.
-Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.0.15 dan https://github.com/vector-im/element-android/releases/tag/v1.0.16
+Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.0.15 dan https://github.com/vector-im/element-android/releases/tag/v1.0.16
diff --git a/fastlane/metadata/android/id/changelogs/40100170.txt b/fastlane/metadata/android/id/changelogs/40100170.txt
index 77f638a7fdc..a2bd48f2c33 100644
--- a/fastlane/metadata/android/id/changelogs/40100170.txt
+++ b/fastlane/metadata/android/id/changelogs/40100170.txt
@@ -1,2 +1,2 @@
 Perubahan utama dalam versi ini: perbaikan bug!
-Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.0.17
+Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.0.17
diff --git a/fastlane/metadata/android/id/changelogs/40101000.txt b/fastlane/metadata/android/id/changelogs/40101000.txt
index acfe6613541..737f9b63ac4 100644
--- a/fastlane/metadata/android/id/changelogs/40101000.txt
+++ b/fastlane/metadata/android/id/changelogs/40101000.txt
@@ -1,2 +1,2 @@
 Perubahan utama dalam versi ini: perbaikan VoIP (panggilan audio dan video dalam DM) dan perbaikan bug!
-Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.1.0
+Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.1.0
diff --git a/fastlane/metadata/android/id/changelogs/40101010.txt b/fastlane/metadata/android/id/changelogs/40101010.txt
index a9903a90bd4..be22d21c32e 100644
--- a/fastlane/metadata/android/id/changelogs/40101010.txt
+++ b/fastlane/metadata/android/id/changelogs/40101010.txt
@@ -1,2 +1,2 @@
 Perubahan utama dalam versi ini: peningkatan kinerja dan perbaikan bug!
-Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.1.1
+Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.1.1
diff --git a/fastlane/metadata/android/id/changelogs/40101020.txt b/fastlane/metadata/android/id/changelogs/40101020.txt
index d654bda4feb..394f48c1719 100644
--- a/fastlane/metadata/android/id/changelogs/40101020.txt
+++ b/fastlane/metadata/android/id/changelogs/40101020.txt
@@ -1,2 +1,2 @@
 Perubahan utama dalam versi ini: peningkatan kinerja dan perbaikan bug!
-Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.1.2
+Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.1.2
diff --git a/fastlane/metadata/android/id/changelogs/40101030.txt b/fastlane/metadata/android/id/changelogs/40101030.txt
index 283c201b2f3..aa1e725b46c 100644
--- a/fastlane/metadata/android/id/changelogs/40101030.txt
+++ b/fastlane/metadata/android/id/changelogs/40101030.txt
@@ -1,2 +1,2 @@
 Perubahan utama dalam versi ini: peningkatan kinerja dan perbaikan bug!
-Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.1.3
+Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.1.3
diff --git a/fastlane/metadata/android/id/changelogs/40101040.txt b/fastlane/metadata/android/id/changelogs/40101040.txt
index fdb94db53df..97eddf643d8 100644
--- a/fastlane/metadata/android/id/changelogs/40101040.txt
+++ b/fastlane/metadata/android/id/changelogs/40101040.txt
@@ -1,2 +1,2 @@
 Perubahan utama dalam versi ini: peningkatan kinerja dan perbaikan bug!
-Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.1.4
+Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.1.4
diff --git a/fastlane/metadata/android/id/changelogs/40101050.txt b/fastlane/metadata/android/id/changelogs/40101050.txt
index 856530c703f..fe745e06d11 100644
--- a/fastlane/metadata/android/id/changelogs/40101050.txt
+++ b/fastlane/metadata/android/id/changelogs/40101050.txt
@@ -1,2 +1,2 @@
 Perubahan utama dalam versi ini: perbaikan hot-fix untuk 1.1.4
-Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.1.5
+Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.1.5
diff --git a/fastlane/metadata/android/id/changelogs/40101060.txt b/fastlane/metadata/android/id/changelogs/40101060.txt
index 1810ecc3aa4..2e01d640f7d 100644
--- a/fastlane/metadata/android/id/changelogs/40101060.txt
+++ b/fastlane/metadata/android/id/changelogs/40101060.txt
@@ -1,2 +1,2 @@
 Perubahan utama dalam versi ini: perbaikan hot-fix untuk 1.1.5
-Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.1.6
+Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.1.6
diff --git a/fastlane/metadata/android/id/changelogs/40101070.txt b/fastlane/metadata/android/id/changelogs/40101070.txt
index 0087d51703c..7449ef81c29 100644
--- a/fastlane/metadata/android/id/changelogs/40101070.txt
+++ b/fastlane/metadata/android/id/changelogs/40101070.txt
@@ -1,2 +1,2 @@
 Perubahan utama dalam versi ini: dukungan beta untuk Spaces. Kompres video sebelum mengirim.
-Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.1.7
+Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.1.7
diff --git a/fastlane/metadata/android/id/changelogs/40101080.txt b/fastlane/metadata/android/id/changelogs/40101080.txt
index cb987964499..737298a0811 100644
--- a/fastlane/metadata/android/id/changelogs/40101080.txt
+++ b/fastlane/metadata/android/id/changelogs/40101080.txt
@@ -1,2 +1,2 @@
 Perubahan utama dalam versi ini: perbaikan untuk Spaces.
-Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.1.8
+Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.1.8
diff --git a/fastlane/metadata/android/id/changelogs/40101090.txt b/fastlane/metadata/android/id/changelogs/40101090.txt
index f6f535fe642..040cbac3b09 100644
--- a/fastlane/metadata/android/id/changelogs/40101090.txt
+++ b/fastlane/metadata/android/id/changelogs/40101090.txt
@@ -1,2 +1,2 @@
 Perubahan utama dalam versi ini: menambahkan dukungan untuk jaringan gitter.im.
-Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.1.9
+Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.1.9
diff --git a/fastlane/metadata/android/id/changelogs/40101100.txt b/fastlane/metadata/android/id/changelogs/40101100.txt
index 121d84ca50c..2c133146101 100644
--- a/fastlane/metadata/android/id/changelogs/40101100.txt
+++ b/fastlane/metadata/android/id/changelogs/40101100.txt
@@ -1,2 +1,2 @@
 Perubahan utama dalam versi ini: pembaruan tema dan gaya dan fitur-fitur baru untuk Spaces.
-Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.1.10
+Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.1.10
diff --git a/fastlane/metadata/android/id/changelogs/40101110.txt b/fastlane/metadata/android/id/changelogs/40101110.txt
index 63c97253c4a..7930471cb93 100644
--- a/fastlane/metadata/android/id/changelogs/40101110.txt
+++ b/fastlane/metadata/android/id/changelogs/40101110.txt
@@ -1,2 +1,2 @@
 Perubahan utama dalam versi ini: pembaruan tema dan gaya dan fitur baru untuk spaces (perbaikan bug untuk 1.1.10)
-Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.1.11
+Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.1.11
diff --git a/fastlane/metadata/android/id/changelogs/40101120.txt b/fastlane/metadata/android/id/changelogs/40101120.txt
index b8f23c530bc..6fd0e255028 100644
--- a/fastlane/metadata/android/id/changelogs/40101120.txt
+++ b/fastlane/metadata/android/id/changelogs/40101120.txt
@@ -1,2 +1,2 @@
 Perubahan utama dalam versi ini: pembaruan tema dan gaya dan perbaiki crash setelah panggilan video
-Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.1.12
+Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.1.12
diff --git a/fastlane/metadata/android/id/changelogs/40101130.txt b/fastlane/metadata/android/id/changelogs/40101130.txt
index 51c532725b9..0fca5b3563d 100644
--- a/fastlane/metadata/android/id/changelogs/40101130.txt
+++ b/fastlane/metadata/android/id/changelogs/40101130.txt
@@ -1,2 +1,2 @@
 Perubahan utama dalam versi ini: terutama pembaruan stabilitas dan perbaikan bug.
-Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.1.13
+Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.1.13
diff --git a/fastlane/metadata/android/id/changelogs/40101140.txt b/fastlane/metadata/android/id/changelogs/40101140.txt
index af1e203dde5..60b041b8e71 100644
--- a/fastlane/metadata/android/id/changelogs/40101140.txt
+++ b/fastlane/metadata/android/id/changelogs/40101140.txt
@@ -1,2 +1,2 @@
 Perubahan utama dalam versi ini: memperbaiki masalah tentang pesan terenkripsi.
-Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.1.14
+Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.1.14
diff --git a/fastlane/metadata/android/id/changelogs/40101150.txt b/fastlane/metadata/android/id/changelogs/40101150.txt
index f3aec557d0e..2411b62b5f8 100644
--- a/fastlane/metadata/android/id/changelogs/40101150.txt
+++ b/fastlane/metadata/android/id/changelogs/40101150.txt
@@ -1,2 +1,2 @@
 Perubahan utama dalam versi ini: implementasi pesan suara dalam pengaturan labs.
-Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.1.15
+Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.1.15
diff --git a/fastlane/metadata/android/id/changelogs/40101160.txt b/fastlane/metadata/android/id/changelogs/40101160.txt
index 19209bacf2d..0a829a92627 100644
--- a/fastlane/metadata/android/id/changelogs/40101160.txt
+++ b/fastlane/metadata/android/id/changelogs/40101160.txt
@@ -1,2 +1,2 @@
 Perubahan utama dalam versi ini: Memperbaiki kesalahan saat mengirim pesan terenkripsi jika seseorang yang ada di ruangan keluar.
-Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.1.16
+Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.1.16
diff --git a/fastlane/metadata/android/id/changelogs/40102000.txt b/fastlane/metadata/android/id/changelogs/40102000.txt
index f7d93e2e4ff..745c46dc184 100644
--- a/fastlane/metadata/android/id/changelogs/40102000.txt
+++ b/fastlane/metadata/android/id/changelogs/40102000.txt
@@ -1,2 +1,2 @@
 Perubahan utama dalam versi ini: Pesan Suara diaktifkan secara default
-Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.2.0
+Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.2.0
diff --git a/fastlane/metadata/android/id/changelogs/40102010.txt b/fastlane/metadata/android/id/changelogs/40102010.txt
index e77f0327b06..e68a571226f 100644
--- a/fastlane/metadata/android/id/changelogs/40102010.txt
+++ b/fastlane/metadata/android/id/changelogs/40102010.txt
@@ -1,2 +1,2 @@
 Perubahan utama di versi ini: Banyak perbaikan di VoIP dan Space (masih beta).
-Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.2.1
+Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.2.1
diff --git a/fastlane/metadata/android/id/changelogs/40103000.txt b/fastlane/metadata/android/id/changelogs/40103000.txt
index bf7b5d8d5db..7192c3ba305 100644
--- a/fastlane/metadata/android/id/changelogs/40103000.txt
+++ b/fastlane/metadata/android/id/changelogs/40103000.txt
@@ -1,2 +1,2 @@
 Perubahan utama di versi ini: Organisir ruangan Anda menggunakan sebuah Space!
-Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.3.0
+Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.3.0
diff --git a/fastlane/metadata/android/id/changelogs/40103020.txt b/fastlane/metadata/android/id/changelogs/40103020.txt
index 4f46881d68f..2eb358b9807 100644
--- a/fastlane/metadata/android/id/changelogs/40103020.txt
+++ b/fastlane/metadata/android/id/changelogs/40103020.txt
@@ -1,2 +1,2 @@
 Perubahan utama dalam versi ini: Penambahan dukungan untuk Android Auto. Banyak perbaikan bug!
-Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.3.2
+Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.3.2
diff --git a/fastlane/metadata/android/id/changelogs/40103030.txt b/fastlane/metadata/android/id/changelogs/40103030.txt
index 630593a1074..9324b44a3ad 100644
--- a/fastlane/metadata/android/id/changelogs/40103030.txt
+++ b/fastlane/metadata/android/id/changelogs/40103030.txt
@@ -1,2 +1,2 @@
 Perubahan utama dalam versi ini: Membuat kebijakan server identitas terlihat di pengaturan. Menghilangkan dukungan Android Auto untuk sementara.
-Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.3.3
+Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.3.3
diff --git a/fastlane/metadata/android/id/changelogs/40103040.txt b/fastlane/metadata/android/id/changelogs/40103040.txt
index 0641f72ffdd..00987013ac6 100644
--- a/fastlane/metadata/android/id/changelogs/40103040.txt
+++ b/fastlane/metadata/android/id/changelogs/40103040.txt
@@ -1,2 +1,2 @@
 Perubahan utama di versi ini: Tambahkan dukungan presensi, untuk ruangan Pesan Langsung (diingat bahwa presensi dinonaktifkan di matrix.org). Tambahkan lagi dukungan Android Auto.
-Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.3.4
+Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.3.4
diff --git a/fastlane/metadata/android/id/changelogs/40103050.txt b/fastlane/metadata/android/id/changelogs/40103050.txt
index ec7c9423bf4..dbca8cc0dba 100644
--- a/fastlane/metadata/android/id/changelogs/40103050.txt
+++ b/fastlane/metadata/android/id/changelogs/40103050.txt
@@ -1,2 +1,2 @@
 Perubahan utama dalam versi ini: Tambahkan dukungan presensi, untuk ruangan Pesan Langsung (diingat bahwa presensi dinonaktifkan di matrix.org). Tambahkan lagi dukungan Android Auto.
-Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.3.5
+Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.3.5
diff --git a/fastlane/metadata/android/id/changelogs/40103060.txt b/fastlane/metadata/android/id/changelogs/40103060.txt
index 4265699d2f1..18f81838ffb 100644
--- a/fastlane/metadata/android/id/changelogs/40103060.txt
+++ b/fastlane/metadata/android/id/changelogs/40103060.txt
@@ -1,2 +1,2 @@
 Perubahan utama dalam versi ini: Tambahkan dukungan presensi, untuk ruangan Pesan Langsung (catatan: presensi dinonaktifkan di matrix.org). Tambahkan lagi dukungan Android Auto.
-Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.3.6
+Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.3.6
diff --git a/fastlane/metadata/android/id/changelogs/40103100.txt b/fastlane/metadata/android/id/changelogs/40103100.txt
index 39d127cd93f..0254c176ee4 100644
--- a/fastlane/metadata/android/id/changelogs/40103100.txt
+++ b/fastlane/metadata/android/id/changelogs/40103100.txt
@@ -1,2 +1,2 @@
 Perubahan utama dalam versi ini: Dukungan untuk fitur poll (dalam Uji Coba), dan desain tampilan URL baru.
-Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.3.10
+Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.3.10
diff --git a/fastlane/metadata/android/id/changelogs/40103110.txt b/fastlane/metadata/android/id/changelogs/40103110.txt
index 725e58d957a..3b325f7f03a 100644
--- a/fastlane/metadata/android/id/changelogs/40103110.txt
+++ b/fastlane/metadata/android/id/changelogs/40103110.txt
@@ -1,2 +1,2 @@
 Perubahan utama dalam versi ini: Perbaikan bug!
-Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.3.11
+Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.3.11
diff --git a/fastlane/metadata/android/id/changelogs/40103120.txt b/fastlane/metadata/android/id/changelogs/40103120.txt
index 9a5dc8026c6..c765575ad71 100644
--- a/fastlane/metadata/android/id/changelogs/40103120.txt
+++ b/fastlane/metadata/android/id/changelogs/40103120.txt
@@ -1,2 +1,2 @@
 Perubahan utama dalam versi ini: Perbaikan bug!
-Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.3.12
+Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.3.12
diff --git a/fastlane/metadata/android/id/changelogs/40103130.txt b/fastlane/metadata/android/id/changelogs/40103130.txt
index de10de53d56..2b81855beb2 100644
--- a/fastlane/metadata/android/id/changelogs/40103130.txt
+++ b/fastlane/metadata/android/id/changelogs/40103130.txt
@@ -1,2 +1,2 @@
 Perubahan utama dalam versi ini: Perubahan pertama di layar permulaan, termasuk analitik opt-in. Dukungan untuk Peristiwa dengan Matematika ditambahkan di Uji Coba.
-Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.3.13
+Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.3.13
diff --git a/fastlane/metadata/android/id/changelogs/40103140.txt b/fastlane/metadata/android/id/changelogs/40103140.txt
index dfefff307f0..cabf0750e04 100644
--- a/fastlane/metadata/android/id/changelogs/40103140.txt
+++ b/fastlane/metadata/android/id/changelogs/40103140.txt
@@ -1,2 +1,2 @@
 Perubahan utama dalam versi ini: Perubahan pertama di layar permulaan, termasuk analitik opt-in. Dukungan untuk Peristiwa dengan Matematika ditambahkan di Uji Coba.
-Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.3.14
+Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.3.14
diff --git a/fastlane/metadata/android/id/changelogs/40103150.txt b/fastlane/metadata/android/id/changelogs/40103150.txt
index c46e661d475..27fc93215bc 100644
--- a/fastlane/metadata/android/id/changelogs/40103150.txt
+++ b/fastlane/metadata/android/id/changelogs/40103150.txt
@@ -1,2 +1,2 @@
 Perubahan utama dalam versi ini: Perubahan pertama di layar permulaan, termasuk analitik opt-in. Dukungan untuk Peristiwa dengan Matematika ditambahkan di Uji Coba.
-Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.3.15
+Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.3.15
diff --git a/fastlane/metadata/android/id/changelogs/40103160.txt b/fastlane/metadata/android/id/changelogs/40103160.txt
index 8a6e62be0cd..418853bb2c5 100644
--- a/fastlane/metadata/android/id/changelogs/40103160.txt
+++ b/fastlane/metadata/android/id/changelogs/40103160.txt
@@ -1,2 +1,2 @@
-Perubahan utama dalam versi ini: Kirim lokasi Anda ke ruangan apa saja. Edit poll.
-Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.3.16
+Perubahan utama dalam versi ini: Kirim lokasi Anda ke ruangan apa saja. Pengeditan pemungutan suara.
+Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.3.16
diff --git a/fastlane/metadata/android/id/changelogs/40103170.txt b/fastlane/metadata/android/id/changelogs/40103170.txt
index 04197b674d2..eebdcf5858f 100644
--- a/fastlane/metadata/android/id/changelogs/40103170.txt
+++ b/fastlane/metadata/android/id/changelogs/40103170.txt
@@ -1,2 +1,2 @@
-Perubahan utama dalam versi ini: kirim lokasi Anda ke ruangan apa saja. Edit poll.
-Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.3.17
+Perubahan utama dalam versi ini: kirim lokasi Anda ke ruangan apa saja. Pengeditan pemungutan suara.
+Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.3.17
diff --git a/fastlane/metadata/android/id/changelogs/40103180.txt b/fastlane/metadata/android/id/changelogs/40103180.txt
index 655fbd562aa..6ec4c2c1bc2 100644
--- a/fastlane/metadata/android/id/changelogs/40103180.txt
+++ b/fastlane/metadata/android/id/changelogs/40103180.txt
@@ -1,2 +1,2 @@
-Perubahan utama dalam versi ini: kirim lokasi Anda ke ruangan apa saja. Edit poll.
-Changelog lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.3.18
+Perubahan utama dalam versi ini: kirim lokasi Anda ke ruangan apa saja. Pengeditan pemungutan suara.
+Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.3.18

From 162973c8df79c021f3f7c174dafb7a8b733c3160 Mon Sep 17 00:00:00 2001
From: Edward Gera 
Date: Wed, 16 Feb 2022 15:09:02 +0000
Subject: [PATCH 486/581] Translated using Weblate (Hebrew)

Currently translated at 73.7% (2055 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/he/
---
 vector/src/main/res/values-iw/strings.xml | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/vector/src/main/res/values-iw/strings.xml b/vector/src/main/res/values-iw/strings.xml
index 50cace23483..a245b14b9db 100644
--- a/vector/src/main/res/values-iw/strings.xml
+++ b/vector/src/main/res/values-iw/strings.xml
@@ -2408,4 +2408,7 @@
     %1$s שלח מדבקה.
     %1$s שלח תמונה‮.
     %1$s: %2$s
+    כל חברי החדר, מהנקודה בה הם הוזמנו.
+    את/ה הפכת הודעות עתידיות לגלויות בפני %1$s
+    %1$s הפך הודעות עתידיות לגלויות בפני %2$s
 
\ No newline at end of file

From 3731fdce0e1dd8a81e2ef7c3a81f4679756bcae9 Mon Sep 17 00:00:00 2001
From: Edward Gera 
Date: Wed, 16 Feb 2022 15:00:11 +0000
Subject: [PATCH 487/581] Translated using Weblate (Hebrew)

Currently translated at 10.2% (5 of 49 strings)

Translation: Element Android/Element Android Store
Translate-URL: https://translate.element.io/projects/element-android/element-store/he/
---
 fastlane/metadata/android/iw-IL/changelogs/40103180.txt | 2 ++
 fastlane/metadata/android/iw-IL/full_description.txt    | 3 +++
 2 files changed, 5 insertions(+)
 create mode 100644 fastlane/metadata/android/iw-IL/changelogs/40103180.txt

diff --git a/fastlane/metadata/android/iw-IL/changelogs/40103180.txt b/fastlane/metadata/android/iw-IL/changelogs/40103180.txt
new file mode 100644
index 00000000000..9649c6839cc
--- /dev/null
+++ b/fastlane/metadata/android/iw-IL/changelogs/40103180.txt
@@ -0,0 +1,2 @@
+שינויים עיקריים בגרסה זו: שלח את המיקום שלך לכל חדר. ערוך סקר.
+יומן שינויים מלא: https://github.com/vector-im/element-android/releases/tag/v1.3.18
diff --git a/fastlane/metadata/android/iw-IL/full_description.txt b/fastlane/metadata/android/iw-IL/full_description.txt
index fe3bc166611..1545634b332 100644
--- a/fastlane/metadata/android/iw-IL/full_description.txt
+++ b/fastlane/metadata/android/iw-IL/full_description.txt
@@ -28,3 +28,6 @@
  תקשורת מלאה : הודעות, שיחות קול ווידאו, שיתוף קבצים, שיתוף מסך וחבורה שלמה של אינטגרציות, בוטים ווידג'טים. לבנות חדרים, קהילות, לשמור על קשר ולעשות דברים.
 
  בכל מקום שאתה נמצא : הישאר בקשר בכל מקום שאתה נמצא עם היסטוריית הודעות מסונכרנת לחלוטין בכל המכשירים שלך באינטרנט בכתובת https://app.element.io.
+
+קוד פתוח: 
+Element Android הוא פרויקט קוד פתוח, המתארח על ידי GitHub. נא לדווח על באגים ו/או לתרום לפיתוחה בכתובת https://github.com/vector-im/element-android

From e8b79e6e3b8176c28e7c473c4baeced235374aa3 Mon Sep 17 00:00:00 2001
From: Linerly 
Date: Fri, 18 Feb 2022 16:06:41 +0000
Subject: [PATCH 488/581] Translated using Weblate (Indonesian)

Currently translated at 100.0% (2785 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/id/
---
 vector/src/main/res/values-in/strings.xml | 36 +++++++++++------------
 1 file changed, 18 insertions(+), 18 deletions(-)

diff --git a/vector/src/main/res/values-in/strings.xml b/vector/src/main/res/values-in/strings.xml
index afe1638b0fe..5677c283a45 100644
--- a/vector/src/main/res/values-in/strings.xml
+++ b/vector/src/main/res/values-in/strings.xml
@@ -43,7 +43,7 @@
     Direktori ruang
     Kirim log
     Deskripsikan kendala Anda di sini
-    Laporan bug telah berhasil dikirimkan
+    Laporan kutu telah berhasil dikirimkan
     Baca
     Daftar
     Masuk
@@ -75,7 +75,7 @@
     Tidak dapat memulai panggilan, coba lagi nanti
     Tidak dapat memulai panggilan
     Keluar
-    Offline
+    Luring
     Pencarian global
     Tandai semua sudah dibaca
     Orang
@@ -86,7 +86,7 @@
     Prioritas rendah
     Hanya kontak Matrix
     Ruangan
-    Laporan bug
+    Laporan kutu
     Aplikasi gagal saat terakhir digunakan. Apakah Anda ingin membuka halaman laporan kegagalan\?
     Gabung di Ruangan
     URL server identitas
@@ -136,7 +136,7 @@
     Nanti
     Kirim Saja
     ${app_name} belum diizinkan untuk mengakses kontak lokal
-    Kirim log gangguan
+    Kirim catat gangguan
     Raksasa
     Kecil
     Normal
@@ -169,10 +169,10 @@
         %d pengguna
     
     Kirim tampilan layar
-    Mohon uraikan bug tersebut. Apa yang Anda lakukan? Apa yang Anda harapkan terjadi? Apa yang sebenarnya terjadi?
-    Log dari klien akan dikirim bersama laporan gangguan ini untuk mendalami kendala yang Anda temukan. Laporan gangguan ini, termasuk log dan rekalayar, tidak akan dilihat oleh khalayak umum. Jika Anda hanya ingin mengirimkan tulisan di atas, silahkan hapus centang:
-    Sepertinya Anda mengguncang telepon akibat frustrasi. Apakah Anda ingin membuka halaman laporan bug?
-    Pengiriman laporan bug gagal (%s)
+    Mohon uraikan kutu tersebut. Apa yang Anda lakukan\? Apa yang Anda harapkan terjadi\? Apa yang sebenarnya terjadi\?
+    Catat dari klien akan dikirim bersama laporan gangguan ini untuk mendalami kendala yang Anda temukan. Laporan gangguan ini, termasuk catat dan tangkapan layar, tidak akan terlihat secara umum. Jika Anda hanya ingin mengirimkan tulisan di atas, silakan hapus centang:
+    Sepertinya Anda mengguncang ponsel akibat frustrasi. Apakah Anda ingin membuka halaman laporan kutu\?
+    Pengiriman laporan kutu gagal (%s)
     Kemajuan (%s%%)
     Kirim ke
     Nama Pengguna
@@ -320,8 +320,8 @@ Ijinkan akses lewat halaman selanjutnya untuk menemukan pengguna ${app_name} yan
     Tinggalkan ruang
     Apa benar Anda ingin meninggalkan ruangan ini\?
     Buat
-    Online
-    Offline
+    Daring
+    Luring
     Berdiam Diri
     %1$s sekarang
     %1$s %2$s yang lalu
@@ -795,9 +795,9 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
     Hasil diagnosa pemeriksaan keadaan
     Lansungkan Ujicoba
     Berlangsung… (%1$d of %2$d)
-    Diagnosa dasar berlangsung lancar. Apabila Anda masih belum dapat menerima pemberitahuan, mohon kirim laporan bug untuk kami selidiki.
+    Diagnosa dasar berlangsung lancar. Apabila Anda masih belum dapat menerima pemberitahuan, mohon kirim laporan kutu untuk kami selidiki.
     Satu atau beberapa ujicoba gagal, coba sugesti yang kami tawarkan.
-    Satu atau beberapa ujicoba gagal, mohon kirim laporan bug untuk kami selidiki.
+    Satu atau beberapa ujicoba gagal, mohon kirim laporan kutu untuk kami selidiki.
     Pengaturan Sistem.
     Pemberitahuan diperbolehkan dalam pengaturan sistem.
     Notifikasi dinonaktifkan dalam pengaturan sistem.
@@ -1710,8 +1710,8 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
     Kirim lampiran
     Sepertinya server terlalu lama merespons, hal ini dapat disebabkan oleh konektivitas yang buruk atau kesalahan pada server. Silakan coba lagi dalam beberapa saat.
     Silakan coba lagi setelah Anda menerima syarat dan ketentuan homeserver Anda.
-    Log verbose akan membantu pengembang dengan menyediakan lebih banyak log saat Anda mengirim RageShake. Bahkan ketika diaktifkan, aplikasi tidak mencatat isi pesan atau data pribadi lainnya.
-    Aktifkan log verbose.
+    Log verbose akan membantu pengembang dengan menyediakan lebih banyak catat saat Anda mengirim RageShake. Bahkan ketika diaktifkan, aplikasi tidak mencatat isi pesan atau data pribadi lainnya.
+    Aktifkan catat verbose.
     Kode verifikasi salah.
     Kode
     Sebuah pesan teks telah dikirim ke %s. Silakan masukkan kode verifikasi yang ada di dalamnya.
@@ -2330,7 +2330,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
     Otomatis memperbarui induk space
     Otomatis undang pengguna
     Anda akan meningkatkan ruangan ini dari %1$s ke %2$s.
-    Meningkatkan ruangan adalah aksi lanjutan dan hanya disarankan ketika ruangan tidak stabil karena bug yang ada, fitur yang kurang atau rentanan keamanan.
+    Meningkatkan ruangan adalah aksi lanjutan dan hanya disarankan ketika ruangan tidak stabil karena kutu yang ada, fitur yang kurang atau rentanan keamanan.
 \nIni biasanya hanya mempengaruhi bagaimana ruangan itu diproses di servernya.
     Tingkatkan ruangan privat
     Tingkatkan ruangan publik
@@ -2924,8 +2924,8 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
     Mengubah avatar Anda di ruangan saat ini saja
     Menampilkan
     Tidak Tersedia
-    Offline
-    Online
+    Luring
+    Daring
     Pilih homeserver
     Tidak dapat menjangkau homeserver di URL %s. Silakan periksa tautan Anda atau pilih sebuah homeserver secara manual.
     Mendengarkan notifikasi
@@ -2995,7 +2995,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
     Mulai ulang aplikasi ini untuk menerapkan perubahan.
     Aktifkan matematika LaTeX
     Anda tidak diperbolehkan untuk bergabung ke ruangan ini
-    Sistem Anda akan mengirimkan log secara otomatis ketika sebuah kesalahan dekripsi terjadi
+    Sistem Anda akan mengirimkan catat secara otomatis ketika sebuah kesalahan dekripsi terjadi
     Buat poll
     Buka kontak
     Kirim stiker

From d43b699e1af63761170a903d35e57c166ab5252c Mon Sep 17 00:00:00 2001
From: Retired Lawyer 
Date: Thu, 17 Feb 2022 22:35:58 +0000
Subject: [PATCH 489/581] Translated using Weblate (Turkish)

Currently translated at 59.1% (29 of 49 strings)

Translation: Element Android/Element Android Store
Translate-URL: https://translate.element.io/projects/element-android/element-store/tr/
---
 fastlane/metadata/android/tr-TR/changelogs/40103000.txt | 2 ++
 fastlane/metadata/android/tr-TR/changelogs/40103010.txt | 2 ++
 fastlane/metadata/android/tr-TR/changelogs/40103020.txt | 2 ++
 fastlane/metadata/android/tr-TR/changelogs/40103030.txt | 2 ++
 4 files changed, 8 insertions(+)
 create mode 100644 fastlane/metadata/android/tr-TR/changelogs/40103000.txt
 create mode 100644 fastlane/metadata/android/tr-TR/changelogs/40103010.txt
 create mode 100644 fastlane/metadata/android/tr-TR/changelogs/40103020.txt
 create mode 100644 fastlane/metadata/android/tr-TR/changelogs/40103030.txt

diff --git a/fastlane/metadata/android/tr-TR/changelogs/40103000.txt b/fastlane/metadata/android/tr-TR/changelogs/40103000.txt
new file mode 100644
index 00000000000..bb66b40193a
--- /dev/null
+++ b/fastlane/metadata/android/tr-TR/changelogs/40103000.txt
@@ -0,0 +1,2 @@
+Bu sürümdeki ana değişiklikler: Spaces kullanarak odalarınızı düzenleyin!
+Tam değişiklik günlüğü: https://github.com/vector-im/element-android/releases/tag/v1.3.0
diff --git a/fastlane/metadata/android/tr-TR/changelogs/40103010.txt b/fastlane/metadata/android/tr-TR/changelogs/40103010.txt
new file mode 100644
index 00000000000..3fea80054eb
--- /dev/null
+++ b/fastlane/metadata/android/tr-TR/changelogs/40103010.txt
@@ -0,0 +1,2 @@
+Bu sürümdeki ana değişiklikler: Spaces kullanarak odalarınızı düzenleyin! v1.3.1, v1.3.0'da meydana gelen bir kilitlenme düzeltildi.
+Tam değişiklik günlüğü: https://github.com/vector-im/element-android/releases/tag/v1.3.1
diff --git a/fastlane/metadata/android/tr-TR/changelogs/40103020.txt b/fastlane/metadata/android/tr-TR/changelogs/40103020.txt
new file mode 100644
index 00000000000..ef8271c6c26
--- /dev/null
+++ b/fastlane/metadata/android/tr-TR/changelogs/40103020.txt
@@ -0,0 +1,2 @@
+Bu sürümdeki ana değişiklikler: Android Auto desteği eklendi. Birçok hata düzeltmesi!
+Tam değişiklik günlüğü: https://github.com/vector-im/element-android/releases/tag/v1.3.2
diff --git a/fastlane/metadata/android/tr-TR/changelogs/40103030.txt b/fastlane/metadata/android/tr-TR/changelogs/40103030.txt
new file mode 100644
index 00000000000..b771e53f96f
--- /dev/null
+++ b/fastlane/metadata/android/tr-TR/changelogs/40103030.txt
@@ -0,0 +1,2 @@
+Bu sürümdeki ana değişiklikler: Ayarlarda kimlik sunucusu politika(lar)ını görünür yapın. Android Auto desteğini geçici olarak kaldırın.
+Tam değişiklik günlüğü: https://github.com/vector-im/element-android/releases/tag/v1.3.3

From b133de8961a86362e01554aa7c6a4c248cbd1202 Mon Sep 17 00:00:00 2001
From: Arusekk 
Date: Sun, 13 Feb 2022 19:28:44 +0000
Subject: [PATCH 490/581] Translated using Weblate (Polish)

Currently translated at 95.1% (2651 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/pl/
---
 vector/src/main/res/values-pl/strings.xml | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/vector/src/main/res/values-pl/strings.xml b/vector/src/main/res/values-pl/strings.xml
index f25ad75c73f..f3b3888a205 100644
--- a/vector/src/main/res/values-pl/strings.xml
+++ b/vector/src/main/res/values-pl/strings.xml
@@ -2425,7 +2425,7 @@
     Kto powinien mieć dostęp \?
     Ustawienia konta
     Możesz zarządzać notyfikacjami w %1$s.
-    Proszę zwrócić uwagę, że notyfikacje o wzmiankach i słowach kluczowych nie są dostępne w zaszyfrowanych pokojach na urządzeniach mobilnych.
+    Proszę zwrócić uwagę, że powiadomienia o wzmiankach i słowach kluczowych nie są dostępne w zaszyfrowanych pokojach na urządzeniach mobilnych.
     Powiadamiaj mnie o
     Zresetuj bezpieczną kopię zapasową
     Skonfiguruj bezpieczną kopię zapasową
@@ -2440,7 +2440,7 @@
     Powiadomienie email
     Żadne
     Tylko wzmianki i słowa kluczowe
-    Oczekiwanie na notyfikacje
+    Oczekiwanie na powiadomienia
     %1$s zmienił(a) adresy tego pokoju.
     Zmieniłeś(aś) głowny i alternatywny adres tego pokoju.
     %1$s zmienił(a) główny i alternatywny adres tego pokoju.
@@ -3072,8 +3072,8 @@
     
         Jedno aktywne połączenie (%1$s) - Jedno wstrzymane połączenie
         Jedno aktywne połączenie (%1$s) - Kilka wstrzymanych połączeń
-        
-        
+        
+        
     
     Odkrywanie (%s)
     Dokończ konfigurację odkrywania.

From b595fa061e591f0bd39131a313c9138733a41425 Mon Sep 17 00:00:00 2001
From: notramo 
Date: Sun, 13 Feb 2022 14:02:42 +0000
Subject: [PATCH 491/581] Translated using Weblate (Hungarian)

Currently translated at 100.0% (2785 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/hu/
---
 vector/src/main/res/values-hu/strings.xml | 10 ++++------
 1 file changed, 4 insertions(+), 6 deletions(-)

diff --git a/vector/src/main/res/values-hu/strings.xml b/vector/src/main/res/values-hu/strings.xml
index 0a6b6d9a976..5a21a5a5eba 100644
--- a/vector/src/main/res/values-hu/strings.xml
+++ b/vector/src/main/res/values-hu/strings.xml
@@ -1225,9 +1225,7 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze
     Aláírás
     A Kulcs Mentés munkamenetben való felhasználáshoz, állítsd vissza jelmondattal vagy Visszaállítási Kulccsal.
     Új Kulcs Mentés
-    Új biztonságos üzenet kulcs mentés észlelve.
-
-Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó próbál hozzáférni a fiókodhoz. Azonnal cseréld le a felhasználói fiókod jelszavát és állítsál be új visszaállítási metódust a Beállításokban.
+    Egy új biztonságos kulcs mentés észlelve. Ha nem te állítottad be a visszaállítási módot, akkor egy támadó próbál hozzáférni a fiókodhoz. Azonnal cseréld le a felhasználói fiókod jelszavát és állíts be új visszaállítási módot a Beállításokban.
     Én voltam
     Visszaállítási kulcs kiszámítása…
     Kulcsok letöltése…
@@ -2656,7 +2654,7 @@ Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó pró
 \n
 \nAz üzeneteidet zárolással vannak biztosítva és csak neked és a címzetteknek van meg a kulcs hozzá.
     Itt az üzenetek nincsenek végponttól végpontig titkosítva.
-    Azonosítás eredménye
+    Hitelesítés eredménye
     Szavazás
     Szoba létrehozása…
     Néhány karakter nem engedélyezett
@@ -3068,8 +3066,8 @@ Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó pró
     A beállítás után bármelyik szobában megoszthatod a földrajzi helyzetedet
     Földrajzi hely megosztás engedélyezése
     Megnyitás ezzel
-    ${app_name} nem fér hozzá a helyadatodhoz. Próbáld újra később.
-    ${app_name} nem fér hozzá a földrajzi helyzetedhez
+    Az ${app_name} nem fér hozzá a tartózkodási helyedhez. Próbáld újra később.
+    Az ${app_name} nem tudott hozzáférni a tartózkodási helyedhez
     Tartózkodási hely megosztása
     Tartózkodási hely megosztása
     Földrajzi helyzet

From 491233b6f7090b351d9ab9277c62db2ae0547bc2 Mon Sep 17 00:00:00 2001
From: Szimszon 
Date: Sun, 13 Feb 2022 09:25:57 +0000
Subject: [PATCH 492/581] Translated using Weblate (Hungarian)

Currently translated at 100.0% (2785 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/hu/
---
 vector/src/main/res/values-hu/strings.xml | 33 +++++++++++++++++++++++
 1 file changed, 33 insertions(+)

diff --git a/vector/src/main/res/values-hu/strings.xml b/vector/src/main/res/values-hu/strings.xml
index 5a21a5a5eba..b2067441198 100644
--- a/vector/src/main/res/values-hu/strings.xml
+++ b/vector/src/main/res/values-hu/strings.xml
@@ -3094,4 +3094,37 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze
     Földrajzi helyzet
     A titkosítás beállítása hibás így nem lehet üzenetet küldeni. Kattints a beállításokért.
     A titkosítás beállítása hibás így nem lehet üzenetet küldeni. Kérjük vedd fel a kapcsolatot az adminisztrátorral a titkosítás helyreállításához.
+    Üzenet buborékok megjelenítése
+    Térkép betöltése sikertelen
+    Térkép
+    Figyelem: az alkalmazás újraindul
+    Üzenetszálak engedélyezése
+    Szerverhez csatlakozás
+    Csatlakoznál egy már meglévő szerverhez\?
+    kérdés kihagyása
+    Még nem vagy biztos\? Tudhatsz ilyent: %s
+    Közösségek
+    Csoportok
+    Barátok és család
+    Segítünk a kapcsolatteremtésben.
+    Kivel beszélgetnék leginkább\?
+    Már nézed ezt az üzenetszálat!
+    Megjelenítés szobában
+    Válasz az üzenetszálban
+    „%s” parancs ismert, de üzenetszálban nem támogatott.
+    Az üzenetszálból
+    Tipp: Koppints hosszan az üzenetre és használd ezt: %s.
+    Az üzenetszálak segítenek a különböző témájú beszélgetések figyelemmel kísérésében.
+    Beszélgetések üzenetszálakba való rendezése
+    Minden üzenetszál megjelenítése ahol szerepel
+    Üzenetszálaim
+    A szobában lévő összes szál mutatása
+    Minden üzenetszál
+    Szűrés
+    Üzenetszálak
+    Üzenetszál
+    Üzenetszálak szűrése a szobában
+    Üzenetszálra mutató hivatkozás másolása
+    Megjelenítés szobában
+    Üzenetszálak megtekintése
 
\ No newline at end of file

From 539d2e732299e0785ad75ae67cb63e1aa39ac9e2 Mon Sep 17 00:00:00 2001
From: Johan Smits 
Date: Tue, 15 Feb 2022 18:42:04 +0000
Subject: [PATCH 493/581] Translated using Weblate (Dutch)

Currently translated at 100.0% (2785 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/nl/
---
 vector/src/main/res/values-nl/strings.xml | 33 +++++++++++++++++++++++
 1 file changed, 33 insertions(+)

diff --git a/vector/src/main/res/values-nl/strings.xml b/vector/src/main/res/values-nl/strings.xml
index abe647b39ef..819958feb35 100644
--- a/vector/src/main/res/values-nl/strings.xml
+++ b/vector/src/main/res/values-nl/strings.xml
@@ -3097,4 +3097,37 @@
     Locatie
     De versleuteling is verkeerd geconfigureerd, zodat u geen berichten kunt verzenden. Klik om instellingen te openen.
     De versleuteling is verkeerd geconfigureerd, zodat u geen berichten kunt verzenden. Neem contact op met een beheerder om de versleuteling in een geldige staat te herstellen.
+    Toon bericht bubbels
+    Kan kaart niet laden
+    Kaart
+    Let op: app wordt opnieuw gestart
+    Discussieberichten inschakelen
+    Verbinding maken met server
+    Wilt u lid worden van een bestaande server\?
+    sla deze vraag over
+    Nog niet zeker\? U kunt %s
+    Gemeenschappen
+    Teams
+    Vrienden en familie
+    We helpen u om verbinding te maken.
+    Met wie gaat u het meest chatten\?
+    U bekijkt deze discussie al!
+    Bekijk in kamer
+    Reageren in discussie
+    Het commando \"%s\" wordt herkend maar niet ondersteund in discussies.
+    Van een discussie
+    Tip: Tik lang op een bericht en gebruik \"%s\".
+    Discussies helpen je gesprekken on-topic te houden en gemakkelijk bij te houden.
+    Houd discussies georganiseerd met discussielijnen
+    Toont alle discussies waaraan u heeft deelgenomen
+    Mijn discussies
+    Toont alle discussies van de huidige kamer
+    Alle discussies
+    Filter
+    Discussies
+    Discussie
+    Discussies in de kamer filteren
+    Kopieer link naar discussie
+    Bekijk in kamer
+    Discussies bekijken
 
\ No newline at end of file

From b80ab1e5190fa09061baadd959d36e9385cf677f Mon Sep 17 00:00:00 2001
From: Joe Sagawa 
Date: Sat, 19 Feb 2022 18:10:23 +0000
Subject: [PATCH 494/581] Translated using Weblate (Japanese)

Currently translated at 82.3% (2294 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 25d5a0517d8..73e326956b5 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2512,4 +2512,6 @@
     チームワークを効率よくしましょう。
     セキュアメッセージング
     管理権を握るのはあなたです。
+    Elementの使用に関するヘルプ
+    詳細なログはRageShakeのときにもっと詳しいログを提供し開発者を助けてくれます。有効にした場合でも、メッセージの内容などのプライベートな情報は記録されません。
 
\ No newline at end of file

From ba269b244a9273eb9beb2b6a74f816436224ed71 Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Sat, 19 Feb 2022 18:09:55 +0000
Subject: [PATCH 495/581] Translated using Weblate (Japanese)

Currently translated at 82.3% (2294 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 21 ++++++++++++---------
 1 file changed, 12 insertions(+), 9 deletions(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 73e326956b5..36aeba76f25 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -122,10 +122,10 @@
     不具合の内容と状況の説明をお願いします。何をしましたか?何が起こるべきでしたか?実際に起こった事象は何でしょうか?
     ここに不具合の内容を記述
     スクリーンショットの画像を送信
-    クラッシュ時の動作記録を送信
-    動作記録を送信
-    開発者が問題を診断するために、このクライアントの動作記録が不具合報告と一緒に送信されます。不具合報告は、動作記録とスクリーンショットを含めて、公開されることはありません。上記の説明文だけを送信したい場合は、以下のチェックを解除してください。
-    あなたは不満で端末を振っているようです。不具合報告の画面を開きますか?
+    クラッシュ時のログを送信
+    ログを送信
+    開発者が問題を診断するために、このクライアントのログがバグレポートと一緒に送信されます。バグレポートは、ログとスクリーンショットを含めて、公開されることはありません。上記の説明文だけを送信したい場合は、以下のチェックを解除してください。
+    あなたは不満で端末を振っているようです。バグレポートの画面を開きますか?
     前回アプリケーションは正常に停止しませんでした。クラッシュ報告の画面を開きますか?
     不具合を報告しました
     不具合の報告の送信に失敗しました (%s)
@@ -1193,9 +1193,9 @@
     カスタムルールの読み込みに失敗しました。再試行してください。
     一部の通知はカスタム設定で無効になっています。
     一部のメッセージがサイレントに設定されていることに注意してください(音を出さずに通知します)。
-    1つ以上のテストが失敗しました。調査用の不具合報告を送信してください。
+    1つ以上のテストが失敗しました。調査用のバグレポートを送信してください。
     1つ以上のテストが失敗しました。提案された修正を試してください。
-    基本的な診断はOKです。 それでも通知が届かない場合は、調査用の不具合報告を送信してください。
+    基本的な診断はOKです。 それでも通知が届かない場合は、調査用のバグレポートを送信してください。
     実行しています…(%1$dの%2$d)
     テストを実行
     診断トラブルシューティング
@@ -1536,7 +1536,7 @@
     共有
     完了
     成功!
-    バックアップを作成中
+    バックアップを作成しています
     パスフレーズを設定
     手動でキーをエクスポート
     キーバックアップを使って開始
@@ -1659,7 +1659,7 @@
     リカバリーキーはパスワードマネージャー(もしくは金庫)のような、非常に安全な場所で保管してください
     リカバリーキーはセーフティーネットとなります。パスフレーズを忘れた場合でも、リカバリーキーを使えば、暗号化されたメッセージにアクセスすることができます。
 \nリカバリーキーは、パスワードマネージャー(もしくは金庫)のような、非常に安全な場所で保管してください。
-    暗号鍵がバックアップ中です。
+    暗号鍵をバックアップしています。
     (高度)リカバリーキーを使用して設定
     または、リカバリーキーでバックアップを保護し、安全な場所に保存してください。
     暗号鍵のコピーを暗号化してホームサーバーに保存します。バックアップを保護するためにパスフレーズを設定してください。
@@ -2411,7 +2411,7 @@
     このルームで使用されている暗号化はサポートされていません
     暗号化が正しく設定されていません
     %sにテキストメッセージを送信しました。メッセージに含まれた確認コードを入力してください。
-    選択したIDサーバーは利用規約がありません。サービス提供者を信頼しなければ続行しないほうがいいです
+    選択したIDサーバーには利用規約がありません。サービス提供者を信頼している場合にのみ続行してください
     現在IDサーバー %1$s でメールアドレスや電話番号を共有しています。共有を停止するには %2$s に再接続する必要があります。
     ルームを作成し設定しました。
     QRコードを読み取り、新しいダイレクトメッセージを作成
@@ -2514,4 +2514,7 @@
     管理権を握るのはあなたです。
     Elementの使用に関するヘルプ
     詳細なログはRageShakeのときにもっと詳しいログを提供し開発者を助けてくれます。有効にした場合でも、メッセージの内容などのプライベートな情報は記録されません。
+    ルームのアップグレードは高度な作業であり、不具合や不足する機能、セキュリティー上の脆弱性がある場合に推奨されます。
+\nアップグレードは普通、ルームがサーバー上で処理される仕方にだけ影響します。
+    一度有効にしたルームの暗号化は無効にすることはできません。暗号化されたルームで送信されたメッセージは、サーバーからは見ることができず、そのルームのメンバーだけが見ることができます。暗号化を有効にすると、多くのボットやブリッジが正常に動作しなくなる場合があります。
 
\ No newline at end of file

From cea16719e85452c53d131e1bcc168037f1cf2169 Mon Sep 17 00:00:00 2001
From: oksya8and8 
Date: Sat, 19 Feb 2022 20:59:37 +0000
Subject: [PATCH 496/581] Translated using Weblate (Japanese)

Currently translated at 84.3% (2349 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 36aeba76f25..c3606e4b562 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2517,4 +2517,5 @@
     ルームのアップグレードは高度な作業であり、不具合や不足する機能、セキュリティー上の脆弱性がある場合に推奨されます。
 \nアップグレードは普通、ルームがサーバー上で処理される仕方にだけ影響します。
     一度有効にしたルームの暗号化は無効にすることはできません。暗号化されたルームで送信されたメッセージは、サーバーからは見ることができず、そのルームのメンバーだけが見ることができます。暗号化を有効にすると、多くのボットやブリッジが正常に動作しなくなる場合があります。
+    %sでこの部屋について人々に知らせます。
 
\ No newline at end of file

From 47f45f42cce7ce633835546adba08d903fdd00cf Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Sat, 19 Feb 2022 20:59:12 +0000
Subject: [PATCH 497/581] Translated using Weblate (Japanese)

Currently translated at 84.3% (2349 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 83 ++++++++++++++++++++---
 1 file changed, 72 insertions(+), 11 deletions(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index c3606e4b562..12ffe054062 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -142,9 +142,9 @@
     電話番号
     電話番号 (任意で)
     ユーザー名かパスワードが正しくありません
-    ユーザー名は半角英数字、ドット、ハイフン、アンダスコアのみで記して下さい
+    ユーザー名は半角英数字、ドット、ハイフン、アンダースコアのみで記して下さい
     パスワードが短すぎます(最小6文字)
-    正しくない電子メールアドレスのようです
+    正しくないメールアドレスのようです
     正しくない電話番号のようです
     既に登録されている電子メールアドレスです。
     パスワードが一致しません
@@ -385,7 +385,7 @@
     メールアドレスがありません
     電話番号が入力されていません
     電子メールアドレスまたは電話番号が入力されていません
-    接続先サーバーを指定する(追加設定)
+    接続先サーバーを指定(高度)
     不正なトークン
     メールと電話番号の同時登録はまだシステムが対応できませんが、電話番号だけの登録は可能です。
 \n
@@ -581,7 +581,7 @@
     証明書はあなたの電話に信頼されていたものから変更されています。これはきわめて異常な事態です。この新しい証明書は承認しないことを強く推奨します。
     証明書は以前信頼されていたものから信頼されていないものへと変更されています。サーバーがその証明書を更新した可能性があります。予測されるフィンガープリントを取得するために、サーバーの管理者に連絡してください。
     サーバーの管理者が上のフィンガープリントと一致するものを発行した場合に限り、証明書を承認してください。
-    不正な形式のIDです。メールアドレスまたは\'@localpart:domain\'のようなMatrix IDを入力してください
+    不正な形式のIDです。メールアドレスまたは\'@localpart:domain\'という形式のMatrix IDを入力してください
     このコンテンツを報告する理由
     このユーザーによる全てのメッセージを非表示にしますか?
 \n
@@ -978,7 +978,7 @@
     現在のセッション
     その他のセッション
     暗号化を有効にする
-    有効にすると、あとで無効にすることはできません。
+    いったん有効にすると、暗号化を無効にすることはできません。
     セキュリティー
     詳細
     その他の設定
@@ -1063,7 +1063,7 @@
     スパムメッセージです
     不適切なメッセージです
     その他の報告…
-    コンテンツの報告
+    コンテンツを報告
     このコンテンツを報告する理由
     報告
     ユーザーを無視
@@ -1581,7 +1581,7 @@
     %sに招待
     Eメールで招待
     詳細
-    人を招待
+    連絡先を招待
     とにかく参加
     ルームを追加
     %sはあなたを招待しています
@@ -1861,7 +1861,7 @@
     このルームはプレビューできません
     お待ち下さい…
     ネットワークがありません。インターネット接続を確認してください。
-    不正な形式のイベント、表示できません
+    不正な形式のイベントです。表示できません
     %2$sに%1$sによって最後に編集されました
     ルーム管理者によってモデレートされたイベント
     リアクション
@@ -2398,7 +2398,7 @@
     投票を締め切り、投票の最終結果を表示します。
     招待者のみ参加可能。個人やチームに最適
     スペースを作成
-    スペースに招待
+    連絡先をスペースに招待
     IDサーバーは利用規約がありません
     あなたの連絡先はプライベートです。端末の連絡先からユーザーを発見できるように、連絡先の情報をIDサーバーへ送信する許可が必要です。
     ディスカバリー設定を開く
@@ -2506,16 +2506,77 @@
     紙吹雪🎉を送る
     降雪❄️を送る
     あなたのチームのメッセージングに。
-    エンドツーエンド暗号化され、電話番号不要。広告やデータマイニング無し。
+    エンドツーエンドで暗号化され、電話番号不要。広告やデータマイニング無し。
     会話の保存先を自分で決められ、自分で管理できる独立したコミュニケーション。Matrixを基に。
     お宅での対面会話と同じぐらいのプライバシーを提供する、セキュアで独立したコミュニケーション。
     チームワークを効率よくしましょう。
     セキュアメッセージング
     管理権を握るのはあなたです。
     Elementの使用に関するヘルプ
-    詳細なログはRageShakeのときにもっと詳しいログを提供し開発者を助けてくれます。有効にした場合でも、メッセージの内容などのプライベートな情報は記録されません。
+    詳細なログは、イライラシェイクでログを送信する際に、より多くのログを提供することで、開発者にとっての助けになります。有効にした場合でも、メッセージの内容やその他のプライベートな情報は記録されません。
     ルームのアップグレードは高度な作業であり、不具合や不足する機能、セキュリティー上の脆弱性がある場合に推奨されます。
 \nアップグレードは普通、ルームがサーバー上で処理される仕方にだけ影響します。
     一度有効にしたルームの暗号化は無効にすることはできません。暗号化されたルームで送信されたメッセージは、サーバーからは見ることができず、そのルームのメンバーだけが見ることができます。暗号化を有効にすると、多くのボットやブリッジが正常に動作しなくなる場合があります。
     %sでこの部屋について人々に知らせます。
+    このコードを連絡先と共有し、スキャンして追加してもらい、会話を始めましょう。
+    正当な参加者が%sにアクセスできることを確認してください。これは後から変更できます。
+    新規:スペースの参加者が非公開のルームを検索し、参加できるようになりました
+    参加者を追加
+    スペースは、ルームや連絡先をグループ化する新しい方法です
+    
+        %d人の連絡先がすでに参加しています
+    
+    %sに招待
+    ユーザー名かメールアドレスで招待
+    どのルームやスペースからも退出しない
+    退出するルームとスペースを選択
+    %sにある全てのルームとスペースから退出しようとしています。
+    %1$sにようこそ、%2$sさん。
+    %sを退出してよろしいですか?
+    スペースは、ルームや連絡先をグループ化する新しい方法です。
+    招待されています
+    新しいスペースを、あなたが管理するスペースに追加。
+    注意:アプリケーションは再起動します
+    ホームサーバーの管理者にお問い合わせください
+    あなたが参加している全てのルームがホームに表示されます。
+    親のスペースを自動的に更新
+    残り%1$d秒
+    %sに属する人は、誰でもこのルームを検索し、参加することができます。手動で全員を招待する必要はありません。これはルームの設定からいつでも変更できます。
+    親のスペースに属する人は、誰でもこのルームを検索し、参加することができます。手動で全員を招待する必要はありません。これはルームの設定からいつでも変更できます。
+    
+        %1$d個の投票に基づく
+    
+    
+        %1$d個の投票に基づく最終結果
+    
+    最近のルーム
+    新しいセッションが認証されました。セッションは暗号化されたメッセージにアクセスでき、他のユーザーには信頼済として表示されます。
+    いったん有効にすると、暗号化を無効にすることはできません。
+    このルームを、あなたのホームサーバーで、組織内のチームとのコラボレーションにのみ使用するなら、このオプションを有効にするといいかもしれません。これは後から変更できません。
+    %sに属していない人がこのルームに今後参加するのを防ぐ
+    プレーンテキストメッセージの前に ( ͡° ͜ʖ ͡°) を付ける
+    このメールアドレスのドメインの登録は許可されていません
+    スペースを作成しています…
+    ルームを作成しています…
+    高度な設定を表示しない
+    高度な設定を表示
+    プレーンテキストメッセージの前に ¯\\_(ツ)_/¯ を付ける
+    アプリケーションのデバッグを手伝うのに役立つ情報を表示
+    デバッグ用の情報を画面に表示
+    初期同期を行っています…
+    説明文が短すぎます
+    サインインして暗号化の鍵を復元しない限り、暗号化されたメッセージにアクセスすることはできなくなります。
+    再サインイン
+    正しいホームサーバーを発見できません。識別子を確認してください
+    ホームサーバーでアカウントを設定したら、以下でMatrix ID(例:@user:domain.com)とパスワードを使用してください。
+    入力したコードは正しくありません。確認してください。
+    これは正しいユーザー識別子ではありません。期待されるフォーマットは「@user:homeserver.org」となります。
+    パスワードが分からなければ、戻ってパスワードをリセットしてください。
+    電子メールを確認してください
+    カスタムサーバーに接続
+    既にアカウントを持っています
+    既存のサーバーに参加しますか?
+    この質問をスキップ
+    友達と家族
+    %1$sは、これを招待者のみ参加可能に設定しました。
 
\ No newline at end of file

From cbca800f16484a5e814c23f2c8b7f1969c568d4d Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Sun, 20 Feb 2022 07:03:44 +0000
Subject: [PATCH 498/581] Translated using Weblate (Japanese)

Currently translated at 85.0% (2369 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 95 ++++++++++++++---------
 1 file changed, 58 insertions(+), 37 deletions(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 12ffe054062..f1ed804e63b 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -105,11 +105,11 @@
     クリップボードへコピー
     警告
     お気に入り
-    会話
+    知人
     ルーム
     ルーム名で絞り込む
     お気に入りで絞り込む
-    ユーザーで絞り込む
+    連絡先で絞り込む
     ルーム名で絞り込む
     招待中
     低優先度
@@ -130,7 +130,7 @@
     不具合を報告しました
     不具合の報告の送信に失敗しました (%s)
     ルームに参加
-    通話が開始できません
+    通話を開始できません
     音声通話
     ビデオ通話
     全体検索
@@ -221,7 +221,7 @@
     ファイルが見つかりません
     あなたはこのルームで発言する権限がありません。
     ルームの詳細
-    メンバー
+    参加者
     ファイル
     設定
     お気に入り
@@ -381,8 +381,8 @@
     電子メールアドレス (任意で)
     パスワード再確認
     新しいパスワードを再確認
-    パスワードが違います
-    メールアドレスがありません
+    パスワードが入力されていません
+    メールアドレスが入力されていません
     電話番号が入力されていません
     電子メールアドレスまたは電話番号が入力されていません
     接続先サーバーを指定(高度)
@@ -394,21 +394,21 @@
     ユーザー名は既に使用されています
     ホームサーバー:
     IDサーバー:
-    メールアドレスを確認しました
+    メールアドレスを認証しました
     パスワードを初期化するには, アカウントに登録されている電子メールアドレスを入力してください:
-    あなたのアカウントに登録された電子メールアドレスの入力が必要です.
+    あなたのアカウントに登録されたメールアドレスの入力が必要です。
     新しいパスワードの入力が必要です。
     %sへ電子メールが送信されました. リンクをたどったら以下をクリックしてください.
     電子メールアドレスの確認に失敗しました: 電子メールのリンクをクリックしたことを確認してください
     パスワードがリセットされました。
 \n
 \n全てのセッションからログアウトしたため、プッシュ通知を受け取れなくなりました。通知を再び有効にするには、各端末で再ログインをお願いします。
-    登録ができません : 電子メールがあなた個人のものであるか確認できません
+    登録できません:メールアドレスの所有権が確認できません
     指定されたアクセストークンが認識されませんでした
     不正な形式のJSON
     有効なJSONを含んでいませんでした
     ログイン要求が多すぎてサーバーが対応できません
-    まだクリックされていないeメールのリンク
+    まだクリックされていない電子メールのリンク
     以下の容量で画像を送信
     ルームの説明
     通話が接続されました
@@ -437,15 +437,15 @@
     接続端末一覧を表示
     %sさんをこのルームに招待してよろしいですか?
     ユーザーIDで招待
-    電子メールまたはMatrixユーザーID
+    メールアドレスまたはMatrix ID
     全て中止
     ログアウト
     無視
     招待中
-    メンバー
+    参加者
     メンバーを検索
     結果なし
-    メンバー
+    参加者
     ファイル
     ルーム
     ディレクトリを見る
@@ -483,7 +483,7 @@
     外観
     エンドツーエンド暗号化についての情報
     公開端末名
-    ルームのEnd-to-end暗号鍵を出力
+    ルームのエンドツーエンド暗号化の鍵をエクスポート
     認証
     履歴を検索
     あなたはこのルームに参加していません。
@@ -569,9 +569,9 @@
 \nよろしいですか?
     端末の連絡先 (%d)
     ユーザーディレクトリ (%s)
-    Matrixユーザーのみ
+    Matrixのユーザーのみ
     ユーザーIDで招待
-    1つまたは複数のメールアドレスか、Matrix IDを入力してください
+    メールアドレスかMatrix IDを入力してください
     信用する
     信用しない
     フィンガープリント (%s):
@@ -680,7 +680,7 @@
     コミュニティーID
     
     ホーム
-    メンバー
+    参加者
     ルーム
     ユーザーがいません
     ルーム
@@ -902,7 +902,7 @@
     パスワードが無効です
     メディア
     シャッター音を再生
-    公開端末名 (会話を行うユーザーに表示されます)
+    公開セッション名(あなたとやり取りする人々に対して表示されます)
     サイレント
     パスワードを表示
     パスワードを隠す
@@ -986,7 +986,7 @@
     ルームの設定
     通知
     
-        %1$d 人のメンバー
+        %1$d人の参加者
     
     アップロード
     ルームを退出
@@ -1126,10 +1126,10 @@
     SSLエラー。
     SSLエラー:相手のアイデンティティが認証されていません。
     このURLからホームサーバーに接続できませんでした、ご確認ください
-    有効なMatrixサーバーアドレスではありません
+    有効なMatrixサーバーのアドレスではありません
     このURLは検索結果に表示できません、ご確認ください
     この電話番号は既に使用されています。
-    復旧用のメールアドレスを設定します。後からオプションでメールアドレスや電話番号を使用して知人に見つけてもらえるようにできます。
+    アカウント復旧用のメールアドレスを設定します。後からオプションでメールアドレスや電話番号を使用して知人に見つけてもらえるようにできます。
     シングルサインオンを使用してサインイン
     HDを使用する
     HDを使用しない
@@ -1175,7 +1175,7 @@
     ${app_name} で会話しましょう:%s
     友達を招待
     既知のユーザー
-    無効なQRコード (無効な URI)!
+    無効なQRコード(無効なURI)!
     パスワードが一致しません
     メールアドレスの確認中にエラーが発生しました。
     これを行うには設定からインテグレーションを許可を有効にしてください。
@@ -1263,7 +1263,7 @@
     あなたのホームサーバーがアシスト機能を提供しない場合、代わりに%sを使用します(IPアドレスは通話中に共有されます)
     ビデオ通話が行われています…
     フォールバックコールアシストサーバーを許可
-    有効な認証情報ではありません
+    有効な認証情報がないため、権限がありません
     ${app_name} 呼び出し失敗
     通話が確実に機能させるためには、ホームサーバー(%1$s)の管理者にTURNサーバーの設定を依頼してください。
 \n
@@ -1512,7 +1512,7 @@
     トークンの登録
     アカウントを追加
     [%1$s]
-\nこのエラーは、${app_name}の管理外です。スマホにはGoogleアカウントがありません。アカウントマネージャーを開いて、Googleアカウントを追加してください。
+\nこのエラーは、${app_name}の管理外です。このスマートフォンにはGoogleアカウントが登録されていません。アカウントマネージャーを開いて、Googleアカウントを追加してください。
     %1$s
 \nこのエラーは${app_name}の管理外であり、Googleによると、このエラーは、端末にFCMに登録されているアプリが多すぎることを示しています。 このエラーは、アプリの数が極端に多い場合にのみ発生するため、平均的なユーザーには影響しません。
     ${app_name}はGoogle Playサービスを使用してプッシュメッセージを配信していますが、正しく設定されていないようです:
@@ -1722,7 +1722,7 @@
     新しいサーバーを追加
     あなたのサーバー
     暗号化されたメッセージの復元
-    セッションの公開名は会話中の相手に閲覧できます
+    セッションの公開名は、あなたとやり取りする人々に対して表示されます
     ルームのバージョン
     
         ブロックされたユーザー%d人
@@ -1765,8 +1765,8 @@
     おすすめのルーム
     スペース
     ホームサーバーAPIのURL
-    復旧用のメールアドレスを設定します。後からオプションでメールアドレスや電話番号を使用して知人に見つけてもらえるようにできます。
-    電話番号を設定して、後からオプションで知人に見つけてもらえるようにできます。
+    アカウント復旧用のメールアドレスを設定します。後からオプションでメールアドレスや電話番号を使用して知人に見つけてもらえるようにできます。
+    電話番号を設定します。後からオプションで知人に見つけてもらえるようにできます。
     %sを使用してみてください
     アクセスを取り消す
     表示
@@ -1775,7 +1775,7 @@
     新しいダイレクトメッセージを送信
     メールアドレス(任意)
     メールアドレス
-    アカウントを回復するためのメールを設定します。 後で、オプションで、あなたが知人にあなたのメールに見つけてもらえるようにできます。
+    アカウント復旧用のメールアドレスを設定します。後からオプションで知人に見つけてもらえるようにできます。
     メールアドレスを設定
     メールアドレスを確認しました
     検出可能なメールアドレス
@@ -1807,15 +1807,15 @@
     カスタムと高度な設定
     組織向けのプレミアムホスティング
     組織向けのプレミアムホスティング
-    最大のパブリックサーバーで数百万人に無料で参加
+    最大のパブリックサーバーで、数百万人に無料で参加
     メールと同じように、アカウントには1つのホームがありますが、誰とでも話すことができます
     サーバーを選択
     始めましょう
     エクスペリエンスを拡張およびカスタマイズ
     暗号化して会話をプライベートに保つ
-    直接またはグループで人々とチャットする
+    直接またはグループで連絡先とチャット
     あなたの会話。 それを所有する。
-    アカウントの復旧用のメールアドレスを設定して、後からオプションで知人に見つけてもらえるようにできます。
+    アカウント復旧用のメールアドレスを設定します。後からオプションで知人に見つけてもらえるようにできます。
     ここが%sとのダイレクトメッセージのスタート地点です。
     変更履歴はありません
     メッセージの変更履歴
@@ -2486,7 +2486,7 @@
     未確認
     制限は不明です。
     サーバーのファイルアップロードの制限
-    自分の会話は自分のものにしましょう。
+    自分の会話は、自分のものに。
     %1$sは、このルームを招待者のみ参加可能に設定しました。
     %1$sが部屋をリンクを持っているユーザーにアクセスできるように設定しました。
     選択したメッセージをネタバレとして送信
@@ -2511,20 +2511,20 @@
     お宅での対面会話と同じぐらいのプライバシーを提供する、セキュアで独立したコミュニケーション。
     チームワークを効率よくしましょう。
     セキュアメッセージング
-    管理権を握るのはあなたです。
+    管理権を握るのは、あなたです。
     Elementの使用に関するヘルプ
     詳細なログは、イライラシェイクでログを送信する際に、より多くのログを提供することで、開発者にとっての助けになります。有効にした場合でも、メッセージの内容やその他のプライベートな情報は記録されません。
-    ルームのアップグレードは高度な作業であり、不具合や不足する機能、セキュリティー上の脆弱性がある場合に推奨されます。
+    ルームのアップグレードは高度な作業であり、不具合や欠けている機能、セキュリティー上の脆弱性がある場合に推奨されます。
 \nアップグレードは普通、ルームがサーバー上で処理される仕方にだけ影響します。
     一度有効にしたルームの暗号化は無効にすることはできません。暗号化されたルームで送信されたメッセージは、サーバーからは見ることができず、そのルームのメンバーだけが見ることができます。暗号化を有効にすると、多くのボットやブリッジが正常に動作しなくなる場合があります。
-    %sでこの部屋について人々に知らせます。
-    このコードを連絡先と共有し、スキャンして追加してもらい、会話を始めましょう。
+    %sして、このルームを皆に紹介しましょう。
+    このコードを皆と共有し、スキャンして追加してもらい、会話を始めましょう。
     正当な参加者が%sにアクセスできることを確認してください。これは後から変更できます。
     新規:スペースの参加者が非公開のルームを検索し、参加できるようになりました
     参加者を追加
     スペースは、ルームや連絡先をグループ化する新しい方法です
     
-        %d人の連絡先がすでに参加しています
+        %d人の知り合いがすでに参加しています
     
     %sに招待
     ユーザー名かメールアドレスで招待
@@ -2579,4 +2579,25 @@
     この質問をスキップ
     友達と家族
     %1$sは、これを招待者のみ参加可能に設定しました。
+    メッセージを送信できませんでした
+    ウィジェットを開く
+    %1$sに転送
+    通話は終了しました
+    自分自身にダイレクトメッセージを送信することはできません!
+    %1$sは通話を開始しました
+    あなたは通話を開始しました
+    電話番号(任意)
+    電話番号を確認
+    電話番号を設定
+    パスワードがリセットされました。
+    %1$sでパスワードをリセット
+    Element Matrix Servicesのアドレス
+    %1$sにサインイン
+    誰と最もよく会話しますか?
+    特定のルームとスペースから退出…
+    非公開で招待が必要なルームは表示されていません。
+\nルームを追加する権限はありません。
+    非公開で招待が必要なルームは表示されていません。
+    勝者
+    電話番号を設定します。オプションで知人に見つけてもらえるようにできます。
 
\ No newline at end of file

From 70a65d9fd489115e37a46c5625b51e018668ae58 Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Sun, 20 Feb 2022 08:34:47 +0000
Subject: [PATCH 499/581] Translated using Weblate (Japanese)

Currently translated at 86.2% (2402 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 42 +++++++++++++++++++----
 1 file changed, 35 insertions(+), 7 deletions(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index f1ed804e63b..d84cda87097 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -1020,7 +1020,7 @@
         %d 件の有効なセッション
     
     このログインを検証
-    QR コード
+    QRコード
     はい
     いいえ
     機内モードが有効です
@@ -1070,7 +1070,7 @@
     ユーザーを無視
     警告:
     
-        元の大きさのまま画像を送信する
+        元の大きさのまま画像を送信
     
     自分自身には通話できません
     マークダウン書式
@@ -1168,7 +1168,7 @@
     タイムラインでのスワイプによる返信を有効にする
     タイムラインで非表示のイベントを表示
     QRコードをスキャン
-    QR コード画像
+    QRコード画像
     QR コード
     QR コードによる追加
     コードを共有
@@ -1632,9 +1632,9 @@
     リカバリーキーを喪失しましたか? 設定で新しいリカバリーキーを設定できます。
     メッセージの復元
     バックアップのバージョンを取得しています…
-    暗号化されたメッセージ履歴のロックを解除するには、復元パスフレーズを使用してください
-    復元パスフレーズをご存知でなければ、%sができます。
-    リカバリーキーを使用して暗号化されたメッセージ履歴をアンロックします
+    暗号化されたメッセージ履歴のロックを解除するには、復旧用のパスフレーズを使用してください
+    復旧用のパスフレーズが分からなければ、%sできます。
+    リカバリーキーを使用して暗号化されたメッセージ履歴をアンロックする
     リカバリーキーを入力
     リカバリーキーを使用
     ログアウトしたりこの端末を失くしたりすればメッセージへのアクセスを失う可能性があります。
@@ -1653,7 +1653,7 @@
     リカバリーキーが保存されました。
     リカバリーキーが%sに保存されました。
 \n
-\n注意: アプリケーションを削除した場合、ファイルは削除される可能性があります。
+\n注意: アプリケーションを削除した場合、リカバリーキーが削除される可能性があります。
     リカバリーキーを保存
     コピーをしました
     リカバリーキーはパスワードマネージャー(もしくは金庫)のような、非常に安全な場所で保管してください
@@ -2600,4 +2600,32 @@
     非公開で招待が必要なルームは表示されていません。
     勝者
     電話番号を設定します。オプションで知人に見つけてもらえるようにできます。
+    メッセージキー
+    復旧用のパスフレーズ
+    ニックネームの色を変更
+    上記のコードをスキャンできなければ、絵文字の並び方を比較して検証してください。
+    パスワードは変更されていません。
+\n
+\n変更作業を中止しますか?
+    確認メールが%1$sに送信されました。
+    メールボックスを確認してください
+    サインインに戻る
+    元の大きさのままメディアファイルを送信
+    
+        元の大きさのまま動画を送信
+    
+    サーバーとの接続が失われました
+    リカバリーパスフレーズか、リカバリーキーを使用
+    新しいサインイン
+    クロス署名を開始
+    信頼されていません
+    セキュリティーを確認
+    検証リクエスト
+    %sがキャンセルしました
+    閲覧済:
+    検証
+    正しくないメールアドレスのようです
+    国際電話番号の形式を使用してください。
+    国際電話番号は「+」から始まる必要があります
+    コードを%1$sに送信しました。以下に入力して認証してください。
 
\ No newline at end of file

From c0f3e9707ecec7695a166b45f408feb8439ad900 Mon Sep 17 00:00:00 2001
From: Joe Sagawa 
Date: Sun, 20 Feb 2022 08:33:52 +0000
Subject: [PATCH 500/581] Translated using Weblate (Japanese)

Currently translated at 86.2% (2402 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index d84cda87097..e8499a6730a 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2628,4 +2628,20 @@
     国際電話番号の形式を使用してください。
     国際電話番号は「+」から始まる必要があります
     コードを%1$sに送信しました。以下に入力して認証してください。
+    このメールアドレスはどのアカウントにも属していません
+    パスワードの変更はすべてのセッションでエンドツーエンド暗号鍵をリセットし、暗号化されたメッセージ履歴が読めなくなります。パスワードを再設定する前に、暗号鍵のバックアップを設定するか他のセッションからエクスポートしてください。
+    パスワードの再設定を確認するために認証メールを送信しました。
+    このメールアドレスはどのアカウントにも属していません。
+    このアプリではこのホームサーバーにアカウントを作成できません。
+\n
+\nウェブクライエントを使用してアカウント登録しますか?
+    申し訳ありません。このサーバーはアカウントの新規登録を認めていません。
+    このアプリではこのホームサーバーにサインインできません。このホームサーバーは次のサインイン方法に対応しています: %1$s
+\n
+\nウェブクライエントを使用してサインインしますか?
+    %1$sを読み込み中にエラーが発生しました(%2$d)
+    利用したいサーバーのアドレスを入力してください
+    利用したいModular Elementまたはサーバーのアドレスを入力してください
+    迷っていますか?%sしてもいいです
+    みんなと繋がる手助けをいたします。
 
\ No newline at end of file

From c11cede1d03058c379f11c1147a750545b86aed0 Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Sun, 20 Feb 2022 11:19:18 +0000
Subject: [PATCH 501/581] Translated using Weblate (Japanese)

Currently translated at 88.8% (2474 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 85 ++++++++++++++++++-----
 1 file changed, 68 insertions(+), 17 deletions(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index e8499a6730a..e9342109789 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -216,8 +216,8 @@
     ここに送信文を入力 (暗号なし)…
     サーバーとの接続が失われました。
     全て再送信
-    未送信の文を再送信
-    未送信の文を削除
+    未送信のメッセージを再送信
+    未送信のメッセージを削除
     ファイルが見つかりません
     あなたはこのルームで発言する権限がありません。
     ルームの詳細
@@ -803,7 +803,7 @@
     動作を表示
     指定したIDのユーザーをブロック
     指定したIDのユーザーのブロックを解除
-    ユーザーの権限レベルを決める
+    ユーザーの権限レベルを規定
     指定したIDのユーザーの管理者権限を取り消す
     指定したユーザーを現在のルームに招待
     指定されたアドレスのルームに参加
@@ -1105,7 +1105,7 @@
     他の利用可能な言語
     メッセージエディタ
     環境設定
-    この端末を設定
+    この端末で設定
     セキュアバックアップをリセット
     セキュアバックアップを設定
     管理
@@ -1220,9 +1220,9 @@
     他の人から送信されたメッセージの削除
     ユーザーのブロック
     ユーザーの除去
-    設定を変更
-    招待されたユーザー
-    メッセージを送る
+    設定の変更
+    ユーザーの招待
+    メッセージの送信
     デフォルトルール
     ルームに関する変更を行うために必要な役割を更新する権限がありません
     ルームに関する変更を行うために必要な役割を選択
@@ -1409,9 +1409,9 @@
     
     %1$sと%2$sと%3$sと%4$s
     %1$sと%2$sと%3$s
-    %1$sを%2$sから%3$sへ
-    %2$sが%1$sの権限を変更しました。
-    %1$sの権限を変更しました。
+    %1$sの権限レベルを%2$sから%3$sへ変更しました。
+    %1$sが
+    あなたは
     カスタム
     カスタム (%1$d)
     デフォルト
@@ -1462,7 +1462,7 @@
     招待、削除、ブロックは影響を受けません。
     招待/参加/退出/削除/禁止イベントや、アバター/表示名の変更などを含む。
     参加・退出イベントを表示
-    Use /confettiコマンドを使用するか、❄️または🎉を含むメッセージを送信します
+    /confettiコマンドを使用するか、❄️または🎉を含むメッセージを送信
     チャットでエフェクトを表示
     ルームのメンバーのイベントを表示
     ホームサーバーがこの機能をサポートしている場合は、チャット内のリンクをプレビューします。
@@ -1620,7 +1620,7 @@
         %d個のキーが含まれたバックアップを復元しました。
     
     バックアップが復元されました %s!
-    このリカバリーキーではバックアップを復号できませんでした。正しいリカバリーキーを入力したことを確認してください。
+    このリカバリーキーではバックアップを復号化できませんでした。正しいリカバリーキーを入力したことを確認してください。
     リカバリーキーを入力してください
     履歴をアンロック
     鍵をインポートしています…
@@ -2370,7 +2370,7 @@
     このルームにおいてのみ表示名を変更します
     ユーザーの無視を解除し、これからのメッセージを表示します
     続行するには%sを入力してください
-    リカバリーパスフレーズ
+    復旧用のパスフレーズ
     有効なリカバリーキーではありません
     リカバリーキーを入力してください
     投票の種類
@@ -2410,7 +2410,7 @@
     %sが参加しました。
     このルームで使用されている暗号化はサポートされていません
     暗号化が正しく設定されていません
-    %sにテキストメッセージを送信しました。メッセージに含まれた確認コードを入力してください。
+    %sにテキストメッセージを送信しました。メッセージにある確認コードを入力してください。
     選択したIDサーバーには利用規約がありません。サービス提供者を信頼している場合にのみ続行してください
     現在IDサーバー %1$s でメールアドレスや電話番号を共有しています。共有を停止するには %2$s に再接続する必要があります。
     ルームを作成し設定しました。
@@ -2450,7 +2450,7 @@
     このコンテンツが報告されています。
 \n
 \nこのユーザーのコンテンツをこれ以上見たくなければ、ユーザーを無視してそのメッセージを非表示にできます。
-    %1$s%2$s
+    %1$sにより%2$sに
     質問あるいはトピック
     投票の質問あるいはトピック
     スペースのメンバーが非公開のルームを発見できるよう手伝う
@@ -2615,7 +2615,7 @@
         元の大きさのまま動画を送信
     
     サーバーとの接続が失われました
-    リカバリーパスフレーズか、リカバリーキーを使用
+    復旧用のパスフレーズか、リカバリーキーを使用
     新しいサインイン
     クロス署名を開始
     信頼されていません
@@ -2629,7 +2629,7 @@
     国際電話番号は「+」から始まる必要があります
     コードを%1$sに送信しました。以下に入力して認証してください。
     このメールアドレスはどのアカウントにも属していません
-    パスワードの変更はすべてのセッションでエンドツーエンド暗号鍵をリセットし、暗号化されたメッセージ履歴が読めなくなります。パスワードを再設定する前に、暗号鍵のバックアップを設定するか他のセッションからエクスポートしてください。
+    パスワードを変更すると、すべてのセッションでのエンドツーエンド暗号鍵がリセットされ、暗号化されたメッセージ履歴が読めなくなります。パスワードを再設定する前に、暗号鍵のバックアップを設定するか、他のセッションからエクスポートしておいてください。
     パスワードの再設定を確認するために認証メールを送信しました。
     このメールアドレスはどのアカウントにも属していません。
     このアプリではこのホームサーバーにアカウントを作成できません。
@@ -2644,4 +2644,55 @@
     利用したいModular Elementまたはサーバーのアドレスを入力してください
     迷っていますか?%sしてもいいです
     みんなと繋がる手助けをいたします。
+    自分のコード
+    
+        招待を%1$sと他%2$d人に送信しました
+    
+    招待を%1$sに送信しました
+    招待を%1$sと%2$sに送信しました
+    正しいMatrixのQRコードではありません
+    スペースを追加
+    このルームの全ての未送信のメッセージを削除してよろしいですか?
+    メッセージの送信をキャンセルしますか?
+    IDを指定してルームから退出(指定しない場合は現在のルームから退出)
+    IDを指定してスペースに参加
+    開発者ツール
+    音を出さずに通知
+    音を出して通知
+    既定の信頼レベル
+    いくつかのメッセージは送信されませんでした
+    保存されていない変更があります。変更を破棄しますか?
+    このルームはまだ作成されていません。キャンセルしますか?
+    テキストメッセージで共有
+    保護を設定
+    このルームのみ
+    誰でも参加可能。コミュニティーに最適
+    既存のスペースに参加するには、招待が必要です。
+    これは後から変更できます
+    接続できませんでした
+    変更を破棄
+    このリンクは不正な形式です
+    QRコードがスキャンされていません!
+    内容を通知に表示
+    プッシュ通知は無効になっています
+    ユーザーのブロックを解除できませんでした
+    %1$sへの招待を取り消しますか?
+    連絡先を取得しています…
+    連絡先を検索
+    電話帳
+    RiotはElementになりました!
+    このメッセージにアクセスできません
+    アバターを設定
+    IDサーバーのURLを入力
+    マイクのミュートを解除
+    マイクをミュート
+    %1$sを使用
+    役割を設定
+    設定
+    セキュリティーキーを保存
+    セキュリティーキーを使用
+    カメラを開始
+    カメラを停止
+    セキュリティーキーを保存
+    リカバリーキー
 
\ No newline at end of file

From 9c7cae4b9663c059d401bcf3aac7b4e46ba4cc42 Mon Sep 17 00:00:00 2001
From: Joe Sagawa 
Date: Sun, 20 Feb 2022 10:06:17 +0000
Subject: [PATCH 502/581] Translated using Weblate (Japanese)

Currently translated at 88.8% (2474 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 89 ++++++++++++++++++-----
 1 file changed, 70 insertions(+), 19 deletions(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index e9342109789..fcd2e966a27 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2464,7 +2464,7 @@
     招待を取り消す
     サーバーのバージョン
     サーバー名
-    電話番号が正しくありません。確認してください
+    電話番号が正しくないようです。確認してください
     カスタムホームサーバーを選択
     Element Matrix Servicesを選択
     再送信
@@ -2552,27 +2552,27 @@
     最近のルーム
     新しいセッションが認証されました。セッションは暗号化されたメッセージにアクセスでき、他のユーザーには信頼済として表示されます。
     いったん有効にすると、暗号化を無効にすることはできません。
-    このルームを、あなたのホームサーバーで、組織内のチームとのコラボレーションにのみ使用するなら、このオプションを有効にするといいかもしれません。これは後から変更できません。
-    %sに属していない人がこのルームに今後参加するのを防ぐ
+    この部屋を同じホームサーバー上で組織内のチームとのコラボレーションにのみ使用するなら、このオプションを有効にするといいかもしれません。これは後から変更できません。
+    %sに属していないユーザーによるこの部屋への参加を今後永久的に拒否する
     プレーンテキストメッセージの前に ( ͡° ͜ʖ ͡°) を付ける
     このメールアドレスのドメインの登録は許可されていません
     スペースを作成しています…
     ルームを作成しています…
-    高度な設定を表示しない
+    高度な設定を非表示にする
     高度な設定を表示
     プレーンテキストメッセージの前に ¯\\_(ツ)_/¯ を付ける
-    アプリケーションのデバッグを手伝うのに役立つ情報を表示
+    アプリケーションのデバッグに役立つ情報を表示
     デバッグ用の情報を画面に表示
-    初期同期を行っています…
+    初期同期中…
     説明文が短すぎます
-    サインインして暗号化の鍵を復元しない限り、暗号化されたメッセージにアクセスすることはできなくなります。
+    サインインして暗号鍵を取り戻さななければ、暗号化されたメッセージがアクセスできなくなります。
     再サインイン
-    正しいホームサーバーを発見できません。識別子を確認してください
-    ホームサーバーでアカウントを設定したら、以下でMatrix ID(例:@user:domain.com)とパスワードを使用してください。
-    入力したコードは正しくありません。確認してください。
-    これは正しいユーザー識別子ではありません。期待されるフォーマットは「@user:homeserver.org」となります。
-    パスワードが分からなければ、戻ってパスワードをリセットしてください。
-    電子メールを確認してください
+    有効なホームサーバーを発見できません。識別子を確認してください
+    どこかのホームサーバーで既にアカウントを登録している場合、以下でMatrix ID(例:@user:domain.com)とパスワードを使用してください。
+    入力したコードが正しくありません。確認してください。
+    これは正しいユーザー識別子ではありません。正しいフォーマットは「@user:homeserver.org」です。
+    パスワードをお忘れの場合、戻ってパスワードをリセットしてください。
+    メールボックスを確認してください
     カスタムサーバーに接続
     既にアカウントを持っています
     既存のサーバーに参加しますか?
@@ -2589,7 +2589,7 @@
     電話番号(任意)
     電話番号を確認
     電話番号を設定
-    パスワードがリセットされました。
+    パスワードをリセットしました。
     %1$sでパスワードをリセット
     Element Matrix Servicesのアドレス
     %1$sにサインイン
@@ -2599,15 +2599,15 @@
 \nルームを追加する権限はありません。
     非公開で招待が必要なルームは表示されていません。
     勝者
-    電話番号を設定します。オプションで知人に見つけてもらえるようにできます。
+    知人に見つけてもらえるように電話番号を設定できます。任意です。
     メッセージキー
     復旧用のパスフレーズ
     ニックネームの色を変更
     上記のコードをスキャンできなければ、絵文字の並び方を比較して検証してください。
-    パスワードは変更されていません。
+    パスワードはまだ変更されていません。
 \n
 \n変更作業を中止しますか?
-    確認メールが%1$sに送信されました。
+    %1$sに認証メールを送信しました。
     メールボックスを確認してください
     サインインに戻る
     元の大きさのままメディアファイルを送信
@@ -2622,9 +2622,9 @@
     セキュリティーを確認
     検証リクエスト
     %sがキャンセルしました
-    閲覧済:
+    既読
     検証
-    正しくないメールアドレスのようです
+    メールアドレスが正しくないようです
     国際電話番号の形式を使用してください。
     国際電話番号は「+」から始まる必要があります
     コードを%1$sに送信しました。以下に入力して認証してください。
@@ -2695,4 +2695,55 @@
     カメラを停止
     セキュリティーキーを保存
     リカバリーキー
+    位置を共有しました
+    %sとリアクションしました
+    検証終了
+    次のいずれかのセキュリティが破られている可能性があります。
+\n
+\n - あなたのホームサーバー
+\n - 検証している相手のホームサーバー
+\n - あなたか相手のインターネット接続
+\n - あなたか相手の端末
+    セキュアではない
+    この緑の盾のシンボルが信頼できるユーザーの印です。部屋のセキュリティを確認するには、全ての参加者に信頼できることを確認してください。
+    セキュリティを最大限に高めるには、対面で行うか他の信頼できる通信媒体を利用してください。
+    次の絵文字が相手の画面にも同じ順番で現れるのを確認し、このユーザーを検証してください。
+    信頼できないサインイン
+    使用できない文字が含まれています
+    Elementの改善と課題抽出のために、匿名の使用分析データを収集させてくださいませんか?複数の端末での使用を解析するために、あなたの全端末共通のランダムな識別子を生成します。
+\n
+\n%sで利用規約を閲覧できます。
+    最初の検索結果のみ表示中、文字をもっと入力してください…
+    matrix.toリンクのフォーマットが正しくありませんでした
+    注意!この端末には未だ暗号鍵を含む個人情報が保存されています。
+\n
+\nこの端末での使用を終了したり、他のアカウントにサインインしたい場合、このデータをクリアしてください。
+    この端末に現在保存されているすべてのデータをクリアしますか?
+\nアカウントデータとメッセージにアクセスするにはもう一度サインインしてください。
+    現在のセッションはユーザー%1$sのものなのに、あなたが提供している資格情報はユーザー%2$sのものです。この操作は${app_name}にサポートされていません。
+\nまずはデータをクリアしてから別のアカウントにサインインしてください。
+    暗号化されたメッセージが読めるように、サインインしてこの端末にのみ保存されている暗号鍵を取り戻してください。
+    あなたのホームサーバー(%1$s)の管理者があなたを%2$sのアカウントからサインアウトしています。(%3$s)
+    いくつかの原因が考えられます:
+\n
+\n• 他のセッションでパスワードを変更している。
+\n
+\n• 他のセッションでこのセッションを削除している。
+\n
+\n• サーバーの管理者がセキュリティ上の理由であなたのアクセスを取り消している。
+    代理手段として、既にアカウントをお持ちでMatrix IDとパスワードをご存知なら、この方法が使用できます。
+    
+        リクエストが多すぎます。%1$d秒後に再試行できます…
+    
+    このホームサーバーが古いバージョンで運営しています。管理者にアップグレードを要請してください。続行できますが、いくつかの機能が正しく作動しない可能性があります。
+    このホームサーバーが古すぎるバージョンで運営しており、接続できません。管理者にアップグレードを要請してください。
+    ホームサーバーのバージョンが古すぎます
+    ただいま%1$sにメールを送信しました。
+\nアカウント登録を続けるにはメールに含まれたリンクをクリックしてください。
+    CAPTCHA認証を行ってください
+    アカウントがまだ登録されていません。
+\n
+\n登録を中止しますか?
+    %1$sにアカウント登録
+    あなたはすべてのセッションからログアウトしており、これ以上プッシュ通知を受け取れません。通知をもう一度有効にするには、各端末でサインインしてください。
 
\ No newline at end of file

From ff63ae0e73c1156877ce6cd1570ed5b1b3e82152 Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Sun, 20 Feb 2022 12:52:22 +0000
Subject: [PATCH 503/581] Translated using Weblate (Japanese)

Currently translated at 89.4% (2490 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 120 ++++++++++++----------
 1 file changed, 67 insertions(+), 53 deletions(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index fcd2e966a27..4a10ef383de 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -47,7 +47,7 @@
     %1$sは%2$sにルームに参加するよう招待状を送りました
     %1$sは%2$sの招待を受け入れました
     ** 解読できません:%s **
-    送信者の端末からこのメッセージのキーが送信されていません。
+    送信者の端末からこのメッセージの鍵が送信されていません。
     修正できませんでした
     メッセージを送信できません
     画像のアップロードに失敗しました
@@ -94,7 +94,7 @@
     削除
     共有
     削除
-    端末情報
+    セッションの情報
     暗号化されたルームでのグループ通話はサポートされていません
     招待
     全ての発言を既読にする
@@ -209,7 +209,7 @@
     新しい会話
     メンバーを追加
     1名
-    端末
+    セッション
     権限を一般メンバーへ変更
     権限を司会者へ変更
     権限を管理者へ変更
@@ -236,7 +236,7 @@
     電話番号を追加
     通知音
     このアカウントで通知を許可
-    この端末で通知を許可
+    このセッションで通知を許可
     1対1のチャットでのメッセージ
     グループチャットでのメッセージ
     ルームへ招待されたとき
@@ -322,7 +322,7 @@
     エンドツーエンド暗号化
     エンドツーエンド暗号化を使用中
     暗号を有効にするためにはログアウトする必要があります.
-    検証済のセッションに対してのみ暗号化
+    認証済のセッションに対してのみ暗号化
     このセッションでは、このルームの未検証のセッションに対して暗号化されたメッセージを送信しない。
     新しいアドレス (記入例 #foo:matrix.org)
     このルームにはローカルアドレスがありません
@@ -434,7 +434,7 @@
     このメンバーの発言を全て表示
     指名して呼掛け
     ユーザーID, 表示名, 電子メールアドレス
-    接続端末一覧を表示
+    セッション一覧を表示
     %sさんをこのルームに招待してよろしいですか?
     ユーザーIDで招待
     メールアドレスまたはMatrix ID
@@ -483,7 +483,7 @@
     外観
     エンドツーエンド暗号化についての情報
     公開端末名
-    ルームのエンドツーエンド暗号化の鍵をエクスポート
+    ルームのエンドツーエンド暗号鍵をエクスポート
     認証
     履歴を検索
     あなたはこのルームに参加していません。
@@ -537,21 +537,21 @@
 \n続行する前に、各セッションの検証プロセスを進めることをおすすめしますが、検証せずにメッセージを再送信することもできます。
 \n
 \n不明なセッション:
-    ルームに不明な端末が含まれています
-    キーが一致していることを確認
-    一致する場合は、下の確認ボタンを押します。そうでない場合は、他の誰かがこの端末を盗聴しているので、代わりにブロックボタンを押すことをおすすめします。今後この検証プロセスはより洗練されたものになるでしょう。
-    端末の検証
-    不明な端末
-    このセッションでは、未検証のセッションに対して暗号化されたメッセージを送信しない
-    認証済端末に対してのみ暗号化
+    ルームに不明なセッションが含まれています
+    鍵が一致していることを確認
+    一致していない場合は、あなたのコミュニケーションの安全性が損なわれている可能性があります。
+    セッションを認証
+    不明なセッション
+    このセッションでは、未検証のセッションに対して暗号化されたメッセージを送信しない。
+    認証済のセッションに対してのみ暗号化
     インポート
-    ローカルファイルからキーをインポート
-    ルームキーをインポート
+    ローカルファイルから鍵をインポート
+    ルームの暗号鍵をインポート
     ルームのエンドツーエンド暗号鍵をインポート
     パスフレーズを確認
     パスフレーズを入力
     エクスポート
-    暗号鍵をローカルファイルにエクスポート
+    鍵をローカルファイルにエクスポート
     ルームの暗号鍵をエクスポート
     検証
     通話
@@ -615,9 +615,9 @@
     アルゴリズム
     セッションID
     復号エラー
-    発信者装置の情報
+    発信者のセッションの情報
     公開端末名
-    端末キー
+    セッションキー
     Ed25519 フィンガープリント
     未認証
     ブラックリスト
@@ -626,14 +626,14 @@
     認証を取り消す
     ブラックリスト
     ブラックリストから除外
-    この端末が信頼できることを確認するために、その所有者に何らかの他の方法(直接会って、または、電話で)連絡し、以下のキーが、その端末の「設定」で確認できるキーと一致するかお尋ねください:
+    他のセッションのユーザー設定で、以下を比較して確認してください:
     ルームのディレクトリを選択
     公開のルームを表示するホームサーバーを入力してください
     サーバー名
     %sサーバー上の全てのルーム
     全てのローカルの%sルーム
     メッセージが未送信です。今%1$sまたは%2$sしますか?
-    不明な端末が存在しているため、メッセージを送ることができませんでした。今%1$sまたは%2$sしますか?
+    不明なセッションが存在しているため、メッセージを送ることができませんでした。今%1$sまたは%2$sしますか?
     要求されたフィンガープリントキー Ed25519
     jitsiを用いて会議通話を始める
     端末のカメラを使用
@@ -671,8 +671,8 @@
     ルームのエンドツーエンド暗号鍵は \'%s\' に保存されました。
 \n 
 \n警告: このファイルは、アプリケーションをアンインストールすると削除されることがあります。
-    暗号鍵を要求している新しい端末 \'%s\' を追加しました。
-    未認証の端末 \'%s\' が暗号鍵を要求しています。
+    暗号鍵を要求している新しいセッション \'%s\' を追加しました。
+    未認証のセッション \'%s\' が暗号鍵を要求しています。
     作成
     コミュニティーを作成
     コミュニティーの名前
@@ -739,10 +739,10 @@
 \n追加しますか?
     続行する…
     申し訳ありません、この操作を完了するための外部アプリが見つかりません。
-    他の端末から 暗号鍵を再度要求 します。
+    他のセッションから暗号鍵を再度要求します。
     鍵のリクエストが送信されました。
     リクエスト送信済
-    鍵をこの端末に送信できるように、メッセージを復号化できる他の端末で${app_name}を起動してください。
+    鍵をこのセッションに送信できるように、メッセージを復号化できる他の端末で${app_name}を起動してください。
     
         %d秒
     
@@ -852,7 +852,7 @@
     ルームのメンバーの簡易読み込み
     申し訳ありません、エラーが発生しました
     バージョン %s
-    エクスポートされた鍵を暗号化するパスフレーズを作成してください。 キーをインポートするには、同じパスフレーズを入力する必要があります。
+    エクスポートされた鍵を暗号化するパスフレーズを作成してください。 鍵をインポートするには、同じパスフレーズを入力する必要があります。
     パスフレーズの作成
     パスフレーズは一致する必要があります
     情報領域を表示
@@ -877,7 +877,7 @@
     サービスを初期化
     鍵のバックアップ
     鍵のバックアップを使用
-    端末を認証
+    セッションを認証
     鍵のバックアップが終了していません。しばらくお待ちください…
     会議通話中。
 \n%1$sまたは%2$sとして参加
@@ -896,7 +896,7 @@
     開封確認メッセージを表示
     開封確認メッセージをクリックすると詳細なリストを確認できます。
     エンター入力でメッセージを送信
-    ソフトウェアキーボードのEnterボタンを押した際に改行せずにメッセージを送信します
+    ソフトウェアキーボードのEnterボタンを押した際、改行せずメッセージを送信
     パスワード
     パスワードを更新
     パスワードが無効です
@@ -911,7 +911,7 @@
     パスワード
     今ここでサインアウトすると、あなたの暗号化されたメッセージは失われてしまいます
     鍵のバックアップは現在処理中です。処理中にサインアウトすると暗号化されたメッセージにアクセスできなくなります。
-    暗号化されたメッセージにアクセスできなくなることを防ぐため、鍵の安全なバックアップはあなたの端末全てで有効化してください。
+    暗号化されたメッセージにアクセスできなくなることを防ぐため、鍵の安全なバックアップはあなたのセッション全てで有効化してください。
     暗号化されたメッセージは不要です
     鍵をバックアップしています…
     鍵のバックアップを使用
@@ -1087,7 +1087,7 @@
     このセッションで通知が無効化されています。
 \n${app_name} の設定をご確認ください。
     このセッションで通知は有効化されています。
-    セッション設定
+    セッションの設定。
     有効化
     あなたのアカウントで通知が無効化されています。
 \nアカウント設定をご確認ください。
@@ -1454,7 +1454,7 @@
     ディスカバリー(発見)
     これにより、現在のキーまたはフレーズが置き換えられます。
     新しいセキュリティーキーを生成するか、既存のバックアップに新しいセキュリティーフレーズを設定します。
-    サーバー上の暗号化キーをバックアップすることにより、暗号化されたメッセージとデータへのアクセスが失われるのを防ぎます。
+    サーバー上の暗号鍵をバックアップすることにより、暗号化されたメッセージとデータへのアクセスが失われるのを防ぎます。
     メッセージ作成画面に絵文字キーボードを開くボタンを追加
     絵文字キーボードを表示
     アバターと表示名の変更が含まれます。
@@ -1566,8 +1566,8 @@
     
         キー%1$dと%2$dのインポートに成功。
     
-    キーバックアップを管理
-    キーのエクスポートに成功しました
+    鍵のバックアップを管理
+    鍵のエクスポートに成功しました
     選択
     選択
     詳細情報:%s
@@ -1602,16 +1602,16 @@
     バックアップの状態を確認中
     バックアップの削除に失敗しました(%s)
     バックアップを削除しています…
-    このセッションで暗号鍵のバックアップを使用するには、パスフレーズまたはリカバリーキーでバックアップを復元してください。
-    バックアップは%sという未検証セッションによる不正なしょめいがあります
-    バックアップは%sという検証されたセッションによる不正な署名があります
-    バックアップは%sという未検証セッションによる有効な署名があります
-    バックアップは%sという検証されたセッションによる署名があります。
+    このセッションで鍵のバックアップを使用するには、パスフレーズまたはリカバリーキーでバックアップを復元してください。
+    バックアップには未検証のセッション %s による不正な署名があります
+    バックアップには検証されたセッション %s による不正な署名があります
+    バックアップには未検証のセッション %s による有効な署名があります
+    バックアップには認証済のセッション %s による署名があります。
     バックアップはこのセッションによる有効な署名があります。
-    バックアップは%sというIDの不明のセッションによる署名があります。
-    このセッションでは暗号鍵がバックアップされていません。
-    このセッションでは暗号鍵のバックアップが無効になっています。
-    このセッションでは暗号鍵のバックアップが正しく設定されています。
+    バックアップには%sというIDの不明のセッションによる署名があります。
+    このセッションでは鍵がバックアップされていません。
+    このセッションでは鍵のバックアップが無効になっています。
+    このセッションでは鍵のバックアップが正しく設定されています。
     最新の復号キーのバージョンを取得するのに失敗しました(%s)。
     
         %d個の新しい鍵がこのセッションに追加されました。
@@ -1648,7 +1648,7 @@
     コピーをしてください
     中止
     上書き
-    違うセッションにより設定されたキーのバックアップが存在してます。上書きしますか?
+    別のセッションで鍵のバックアップを既に設定しているようです。上書きしますか?
     ホームサーバーにバックアップが存在しています
     リカバリーキーが保存されました。
     リカバリーキーが%sに保存されました。
@@ -1681,7 +1681,7 @@
 \nセッション名:%1$s
 \n最後のオンライン時刻:%2$s
 \n新たにログインして新しいセッションを開始しなかった場合、この要求を無視してください。
-    未検証のセッションが暗号鍵を要請しています。
+    未認証のセッションが暗号鍵を要請しています。
 \nセッション名:%1$s
 \n最後のオンライン時刻:%2$s
 \n新たにログインして新しいセッションを開始しなかった場合、この要求を無視してください。
@@ -1844,7 +1844,7 @@
     あなたは既にこのルームを見ています!
     その他のサードパーティーの使用に関する掲示
     Matrix SDK バージョン
-    ファイル\"%1$s\"からe2eキーをインポートします。
+    ファイル\"%1$s\"からエンドツーエンド暗号鍵をインポートします。
     キーのバックアップデータの取得中にエラーが発生しました
     信頼情報の取得中にエラーが発生しました
     ルームが作成されましたが、一部の招待が送信されていません。理由:
@@ -1878,7 +1878,7 @@
     ホームへようこそ!
     未読メッセージはありません
     未読はありません!
-    %sがセッションを検証を要求しています
+    %sがセッションの検証を要求しています
     インタラクティブセッション検証
     ルームに参加してアプリの使用を開始します。
     リトライ
@@ -1912,7 +1912,7 @@
     相手が確認するのを待っています…
     リクエストを見る
     検証リクエストを受信しました。
-    相手の画面に次の番号が表示されていることを確認して、このセッションを確認します
+    相手の画面に次の番号が表示されていることを確認して、このセッションを検証
     相手の画面に次の絵文字が表示されることを確認して、このセッションを確認します
     このセッションを検証すると、信頼済としてマークされ、自分も相手に信頼済としてマークされます。
     このセッションを検証して、信頼済としてマークします。相手のセッションを信頼すると、さらに安心してエンドツーエンド暗号化を使用することができます。
@@ -1969,7 +1969,7 @@
         %d 通話できません
     
     デフォルトで使いもう尋ねない
-    キー共有リクエストの履歴を送信
+    鍵の共有リクエストの履歴を送信
     結果がありません
     自分で電話をかけることはできません。メンバーが招待を受け入れるのを待ちます
     ミーティングはJitsiのセキュリティーとパーミッションポリシーを使用します。会議中は、現在ルームにいる全ての人に招待状が表示されます。
@@ -2490,7 +2490,7 @@
     %1$sは、このルームを招待者のみ参加可能に設定しました。
     %1$sが部屋をリンクを持っているユーザーにアクセスできるように設定しました。
     選択したメッセージをネタバレとして送信
-    ${app_name} がE2E暗号鍵をディスクに保存する許可を要求しています。
+    ${app_name} がエンドツーエンド暗号鍵をディスクに保存する許可を要求しています。
 \n
 \n暗号鍵を手動でエクスポートできるように、次のポップアップでアクセスを許可してください。
     %1$sはリンクを知っている人がアクセスできるようにこのルームを設定しました。
@@ -2565,7 +2565,7 @@
     デバッグ用の情報を画面に表示
     初期同期中…
     説明文が短すぎます
-    サインインして暗号鍵を取り戻さななければ、暗号化されたメッセージがアクセスできなくなります。
+    サインインして暗号鍵を取り戻さなければ、暗号化されたメッセージにアクセスできなくなります。
     再サインイン
     有効なホームサーバーを発見できません。識別子を確認してください
     どこかのホームサーバーで既にアカウントを登録している場合、以下でMatrix ID(例:@user:domain.com)とパスワードを使用してください。
@@ -2715,13 +2715,13 @@
 \n%sで利用規約を閲覧できます。
     最初の検索結果のみ表示中、文字をもっと入力してください…
     matrix.toリンクのフォーマットが正しくありませんでした
-    注意!この端末には未だ暗号鍵を含む個人情報が保存されています。
+    注意!この端末には暗号鍵を含む個人情報が保存されています。
 \n
 \nこの端末での使用を終了したり、他のアカウントにサインインしたい場合、このデータをクリアしてください。
     この端末に現在保存されているすべてのデータをクリアしますか?
 \nアカウントデータとメッセージにアクセスするにはもう一度サインインしてください。
-    現在のセッションはユーザー%1$sのものなのに、あなたが提供している資格情報はユーザー%2$sのものです。この操作は${app_name}にサポートされていません。
-\nまずはデータをクリアしてから別のアカウントにサインインしてください。
+    現在のセッションはユーザー %1$s のものですが、あなたが提供している資格情報はユーザー %2$s のものです。この操作は${app_name}ではサポートされていません。
+\nまずデータをクリアし、その後、別のアカウントにサインインしてください。
     暗号化されたメッセージが読めるように、サインインしてこの端末にのみ保存されている暗号鍵を取り戻してください。
     あなたのホームサーバー(%1$s)の管理者があなたを%2$sのアカウントからサインアウトしています。(%3$s)
     いくつかの原因が考えられます:
@@ -2746,4 +2746,18 @@
 \n登録を中止しますか?
     %1$sにアカウント登録
     あなたはすべてのセッションからログアウトしており、これ以上プッシュ通知を受け取れません。通知をもう一度有効にするには、各端末でサインインしてください。
+    セキュリティーフレーズを設定
+    セキュリティーフレーズを使用
+    セキュリティーキーは、パスワードマネージャーもしくは金庫のような安全な場所で保管してください。
+    セキュリティーキーは、パスワードマネージャーもしくは金庫のような安全な場所で保管してください。
+    セキュリティーキーを生成し、パスワードマネージャーもしくは金庫のような安全な場所で保管してください。
+    セキュアバックアップ
+    セキュアバックアップを設定
+    会話を開く
+    このIDサーバーは最新のバージョンではありません。${app_name}はAPIのバージョン2のみをサポートしています。
+    ユーザーを招待できませんでした。招待したいユーザーを確認して、もう一度試してください。
+    %sの利用規約を開く
+    ユーザーによる同意は与えられていません。
+    代わりに、他のIDサーバーのURLを入力できます
+    サーバー上の暗号化キーをバックアップすることにより、暗号化されたメッセージとデータへのアクセスが失われるのを防ぎます。
 
\ No newline at end of file

From 6326c4180559b1de4133d9565756b43c5264159b Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Sun, 20 Feb 2022 12:58:10 +0000
Subject: [PATCH 504/581] Translated using Weblate (Japanese)

Currently translated at 89.4% (2490 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 4a10ef383de..0974616532b 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -798,7 +798,7 @@
     
     必要な変数が見つかりません。
     変数が無効です。
-    メッセージを送信するには、キーボードのエンターキーを使用してください
+    メッセージを送信するには、キーボードのエンターキーを押してください
     ボイスメッセージを送信
     動作を表示
     指定したIDのユーザーをブロック
@@ -1017,7 +1017,7 @@
     セッションの管理
     このセッションからログアウト
     
-        %d 件の有効なセッション
+        %d件のアクティブなセッション
     
     このログインを検証
     QRコード
@@ -1670,7 +1670,7 @@
 \n
 \n暗号鍵を失わないように保護されたバックアップをしてください。
     (高度)
-    暗号されたメッセージを絶対に失わないために
+    暗号化されたメッセージを決して失わないために
     利用可能なMatrixセッションがありません
     ${app_name}によるリカバリーキーの生成を望む場合、パスフレーズを削除してください。
     マークダウンが無効です。
@@ -1685,7 +1685,7 @@
 \nセッション名:%1$s
 \n最後のオンライン時刻:%2$s
 \n新たにログインして新しいセッションを開始しなかった場合、この要求を無視してください。
-    暗号鍵共有要請
+    鍵の共有リクエスト
     カスタムカメラ画面の代わりにシステムカメラを使用します。
     使用中のウィジェットがありません
     インテグレーションを管理

From c179885d8b9fc1193ec9053745b46e880c3d0769 Mon Sep 17 00:00:00 2001
From: Joe Sagawa 
Date: Sun, 20 Feb 2022 12:57:43 +0000
Subject: [PATCH 505/581] Translated using Weblate (Japanese)

Currently translated at 89.4% (2490 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 0974616532b..5a2286b8fad 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -1927,9 +1927,9 @@
     サーバーオプションをおまかせする
     無効なホームサーバーディスカバリーレスポンス
     新しいメッセージの暗号キー
-    暗号化されたメッセージを失うことはありません
+    暗号鍵を絶対に喪失しないために
     セキュアバックアップ
-    暗号化されたメッセージを失いません
+    暗号鍵を決して喪失しないために
     バックアップ (%s) のtrust infoの取得に失敗しました。
     セッションの暗号化が有効になっていません
     これを使用するとデータを%sと共有します。

From 33a2cfacd3c11983703d36124dddf3ff4031d312 Mon Sep 17 00:00:00 2001
From: oksya8and8 
Date: Sun, 20 Feb 2022 13:35:23 +0000
Subject: [PATCH 506/581] Translated using Weblate (Japanese)

Currently translated at 89.8% (2502 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 5a2286b8fad..a87c6e46d1c 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2760,4 +2760,7 @@
     ユーザーによる同意は与えられていません。
     代わりに、他のIDサーバーのURLを入力できます
     サーバー上の暗号化キーをバックアップすることにより、暗号化されたメッセージとデータへのアクセスが失われるのを防ぎます。
+    今キャンセルしてログインにアクセスできなくなると、暗号化されたメッセージとデータが失われる可能性があります。
+\n
+\nまた、設定からバックアップの設定や鍵の管理ができます。
 
\ No newline at end of file

From 5d578f69667fc2257521eb60b0cebee3244e24c7 Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Sun, 20 Feb 2022 13:33:54 +0000
Subject: [PATCH 507/581] Translated using Weblate (Japanese)

Currently translated at 89.8% (2502 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 90 +++++++++++++----------
 1 file changed, 51 insertions(+), 39 deletions(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index a87c6e46d1c..2f0816f1eb5 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -922,10 +922,10 @@
     検証
     バックアップから復元
     バックアップを削除
-    鍵をバックアップしています…
+    鍵をバックアップしています。これには数分かかる可能性があります…
     全ての鍵をバックアップしました
     
-        %d 件の鍵をバックアップしています…
+        %d個の鍵をバックアップしています…
     
     バージョン
     アルゴリズム
@@ -1538,8 +1538,8 @@
     成功!
     バックアップを作成しています
     パスフレーズを設定
-    手動でキーをエクスポート
-    キーバックアップを使って開始
+    手動で鍵をエクスポート
+    鍵のバックアップを使用開始
     パスフレーズが弱すぎます
     パスフレーズを入力してください
     Google PlayサービスのAPKが見つかりませんでした。通知がうまく機能しない場合があります。
@@ -1588,31 +1588,31 @@
     このルームでグループ通話をする権利がありません
     オーディオミーティングを開始
     安全バックアップを設定
-    暗号鍵バックアップで管理
-    暗号鍵バックアップを使用
+    鍵のバックアップで管理
+    鍵のバックアップを使用
     暗号化されたメッセージ及びデータへのアクセスを喪失しないための安全措置
-    暗号鍵のバックアップを開始
+    鍵のバックアップを使用開始
     自分でした
-    新しい暗号化されたメッセージの暗号鍵バックアップが検出されました。
+    メッセージの鍵の新しい安全なバックアップが検出されました。
 \n
-\n新しい復元方法を設定しなかった場合、攻撃者がアカウントへアクセスしようとしている可能性があります。アカウントを守るために、設定ですぐにアカウントのパスワードを変更し新しい復元方法を設定してください。
-    新しい暗号鍵バックアップ
-    バックアップされた暗号鍵をサーバーから削除しますか? 現在のリカバリーキーを使って、暗号化されたメッセージの履歴を読むことができなくなります。
+\n新しい復元方法を設定しなかった場合、攻撃者がアカウントへアクセスしようとしている可能性があります。アカウントを守るために、設定ですぐにアカウントのパスワードを変更し、新しい復元方法を設定してください。
+    鍵の新しいバックアップ
+    バックアップされた暗号鍵をサーバーから削除しますか?今後、現在のリカバリーキーを使って、暗号化されたメッセージの履歴を読むことができなくなります。
     バックアップを削除
-    バックアップの状態を確認中
+    バックアップの状態を確認しています
     バックアップの削除に失敗しました(%s)
     バックアップを削除しています…
     このセッションで鍵のバックアップを使用するには、パスフレーズまたはリカバリーキーでバックアップを復元してください。
-    バックアップには未検証のセッション %s による不正な署名があります
-    バックアップには検証されたセッション %s による不正な署名があります
-    バックアップには未検証のセッション %s による有効な署名があります
+    バックアップには未認証のセッション %s による不正な署名があります
+    バックアップには認証済のセッション %s による不正な署名があります
+    バックアップには未認証のセッション %s による有効な署名があります
     バックアップには認証済のセッション %s による署名があります。
     バックアップはこのセッションによる有効な署名があります。
     バックアップには%sというIDの不明のセッションによる署名があります。
     このセッションでは鍵がバックアップされていません。
     このセッションでは鍵のバックアップが無効になっています。
     このセッションでは鍵のバックアップが正しく設定されています。
-    最新の復号キーのバージョンを取得するのに失敗しました(%s)。
+    最新の復号化キーのバージョンを取得するのに失敗しました(%s)。
     
         %d個の新しい鍵がこのセッションに追加されました。
     
@@ -1622,25 +1622,25 @@
     バックアップが復元されました %s!
     このリカバリーキーではバックアップを復号化できませんでした。正しいリカバリーキーを入力したことを確認してください。
     リカバリーキーを入力してください
-    履歴をアンロック
+    履歴のロックを解除
     鍵をインポートしています…
     鍵をダウンロードしています…
     リカバリーキーを計算しています…
-    バックアップを復元:
+    バックアップを復元しています:
     ネットワークエラー: 接続を確認して再試行してください。
-    このパスフレーズではバックアップを復号できませんでした。正しい復元パスフレーズを入力したことを確認してください。
+    このパスフレーズではバックアップを復号化できませんでした。正しいリカバリーパスフレーズを入力したことを確認してください。
     リカバリーキーを喪失しましたか? 設定で新しいリカバリーキーを設定できます。
-    メッセージの復元
+    メッセージの復旧
     バックアップのバージョンを取得しています…
     暗号化されたメッセージ履歴のロックを解除するには、復旧用のパスフレーズを使用してください
     復旧用のパスフレーズが分からなければ、%sできます。
-    リカバリーキーを使用して暗号化されたメッセージ履歴をアンロックする
+    リカバリーキーを使用して、暗号化されたメッセージの履歴のロックを解除
     リカバリーキーを入力
     リカバリーキーを使用
-    ログアウトしたりこの端末を失くしたりすればメッセージへのアクセスを失う可能性があります。
+    ログアウトしたりこの端末を失くしたりすると、メッセージにアクセスできなくなる可能性があります。
     続行しますか?
-    暗号鍵が現在バックグラウンドでホームサーバーへバックアップされています。初期バックアップは数分かかることがあります。
-    バックアップが開始されました
+    暗号鍵を現在バックグラウンドでホームサーバーにバックアップしています。最初のバックアップには数分かかることがあります。
+    バックアップが開始しました
     予期しないエラー
     リカバリーキー
     パスフレーズを使用してリカバリーキーを生成中です。数秒かかることがあります。
@@ -1659,16 +1659,16 @@
     リカバリーキーはパスワードマネージャー(もしくは金庫)のような、非常に安全な場所で保管してください
     リカバリーキーはセーフティーネットとなります。パスフレーズを忘れた場合でも、リカバリーキーを使えば、暗号化されたメッセージにアクセスすることができます。
 \nリカバリーキーは、パスワードマネージャー(もしくは金庫)のような、非常に安全な場所で保管してください。
-    暗号鍵をバックアップしています。
+    鍵をバックアップしています。
     (高度)リカバリーキーを使用して設定
     または、リカバリーキーでバックアップを保護し、安全な場所に保存してください。
-    暗号鍵のコピーを暗号化してホームサーバーに保存します。バックアップを保護するためにパスフレーズを設定してください。
+    鍵のコピーを暗号化してホームサーバーに保存します。バックアップを保護するためにパスフレーズを設定してください。
 \n
-\n最高度のセキュリティーのために、アカウントのパスワードと異なることが大切です。
+\n最高度のセキュリティーのために、アカウントのパスワードと異なるものに設定することが大切です。
     パスフレーズを使用してバックアップを保護します。
-    暗号化が有効なルームで送信されたメッセージはエンドツーエンド暗号化によって保護されます。メッセージを読むための暗号鍵を持っているのは送受信者のみです。
+    暗号化が有効なルームで送信されたメッセージは、エンドツーエンド暗号化によって保護されます。メッセージを読むための鍵を持っているのは送受信者のみです。
 \n
-\n暗号鍵を失わないように保護されたバックアップをしてください。
+\n鍵を失わないために、鍵を安全にバックアップしてください。
     (高度)
     暗号化されたメッセージを決して失わないために
     利用可能なMatrixセッションがありません
@@ -1845,7 +1845,7 @@
     その他のサードパーティーの使用に関する掲示
     Matrix SDK バージョン
     ファイル\"%1$s\"からエンドツーエンド暗号鍵をインポートします。
-    キーのバックアップデータの取得中にエラーが発生しました
+    鍵のバックアップデータの取得中にエラーが発生しました
     信頼情報の取得中にエラーが発生しました
     ルームが作成されましたが、一部の招待が送信されていません。理由:
 \n
@@ -1887,12 +1887,12 @@
     IDサーバーを使用していません
     不明なエラー
     ユーザーの不一致
-    キーの不一致
+    鍵の不一致
     無効なメッセージを受信しました
     セッションに予期しないメッセージが表示されました
     SASが一致しませんでした
     ハッシュコミットメントが一致しませんでした
-    セッションは、キー共有、ハッシュ、MAC、SASメソッドについて合意できません
+    セッションは、鍵の共有、ハッシュ、MAC、SASメソッドについて合意できません
     セッションはそのトランザクションについて知りません
     検証プロセスがタイムアウトしました
     ユーザーが検証をキャンセルしました
@@ -1903,7 +1903,7 @@
     相手が検証をキャンセルしました。
 \n%s
     リクエストはキャンセルされました
-    キーの検証
+    鍵の検証
     レガシー検証を使います。
     何も表示されませんか?全てのクライアントがインタラクティブ検証をまだサポートしているわけではありません。その場合はレガシー検証を使用してください。
     了解
@@ -1926,10 +1926,10 @@
 \n%2$s
     サーバーオプションをおまかせする
     無効なホームサーバーディスカバリーレスポンス
-    新しいメッセージの暗号キー
-    暗号鍵を絶対に喪失しないために
+    メッセージの新しい鍵
+    暗号化されたメッセージを決して失わないために
     セキュアバックアップ
-    暗号鍵を決して喪失しないために
+    暗号化されたメッセージを決して失わないために
     バックアップ (%s) のtrust infoの取得に失敗しました。
     セッションの暗号化が有効になっていません
     これを使用するとデータを%sと共有します。
@@ -2214,7 +2214,7 @@
     "トピック: "
     トラブルシューティング
     添付ファイルの取得中にエラーが発生しました。
-    キーバックアップのバナーを閉じる
+    鍵のバックアップのバナーを閉じる
     キーワードに「%s」を含めることはできません
     %s へのメール通知を有効にする
     ヒント:メッセージを長押しして「%s」を選択。
@@ -2492,7 +2492,7 @@
     選択したメッセージをネタバレとして送信
     ${app_name} がエンドツーエンド暗号鍵をディスクに保存する許可を要求しています。
 \n
-\n暗号鍵を手動でエクスポートできるように、次のポップアップでアクセスを許可してください。
+\n鍵を手動でエクスポートできるように、次のポップアップでアクセスを許可してください。
     %1$sはリンクを知っている人がアクセスできるようにこのルームを設定しました。
     まず、設定画面でIDサーバーの利用規約に同意してください。
     初めにIDサーバーを設定してください。
@@ -2629,7 +2629,7 @@
     国際電話番号は「+」から始まる必要があります
     コードを%1$sに送信しました。以下に入力して認証してください。
     このメールアドレスはどのアカウントにも属していません
-    パスワードを変更すると、すべてのセッションでのエンドツーエンド暗号鍵がリセットされ、暗号化されたメッセージ履歴が読めなくなります。パスワードを再設定する前に、暗号鍵のバックアップを設定するか、他のセッションからエクスポートしておいてください。
+    パスワードを変更すると、すべてのセッションでのエンドツーエンド暗号鍵がリセットされ、暗号化されたメッセージ履歴が読めなくなります。パスワードを再設定する前に、鍵のバックアップを設定するか、他のセッションから鍵をエクスポートしておいてください。
     パスワードの再設定を確認するために認証メールを送信しました。
     このメールアドレスはどのアカウントにも属していません。
     このアプリではこのホームサーバーにアカウントを作成できません。
@@ -2763,4 +2763,16 @@
     今キャンセルしてログインにアクセスできなくなると、暗号化されたメッセージとデータが失われる可能性があります。
 \n
 \nまた、設定からバックアップの設定や鍵の管理ができます。
+    USBメモリーもしくはバックアップドライブに保存
+    鍵のバックアップの設定
+    自己署名キーを同期しています
+    ユーザーキーを同期しています
+    マスターキーを同期しています
+    SSSS デフォルトキーを規定しています
+    安全な鍵をパスフレーズから生成しています
+    メッセージキーを生成
+    暗号化されたメッセージのロックを解除
+    鍵の要求
+    鍵は既に最新です!
+    鍵をリセット
 
\ No newline at end of file

From 52020aacdb98f971372360d285833d8aa77dee84 Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Sun, 20 Feb 2022 17:49:17 +0000
Subject: [PATCH 508/581] Translated using Weblate (Japanese)

Currently translated at 90.4% (2518 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 110 +++++++++++++---------
 1 file changed, 65 insertions(+), 45 deletions(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 2f0816f1eb5..39b2d1df779 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -8,15 +8,15 @@
     %1$sがあなたを招待しました
     %1$sが参加しました
     %1$sが退出しました
-    %1$sが招待を断りました
+    %1$sが招待を拒否しました
     %1$sが%2$sを追放しました
-    %1$sが%2$sをブロック解除しました
+    %1$sが%2$sのブロックを解除しました
     %1$sが%2$sをブロックしました
-    %1$sが%2$sの招待を撤回しました
+    %1$sが%2$sの招待を取り下げました
     %1$sがアバターを変更しました
     %1$sが表示名を%2$sに設定しました
     %1$sが表示名を%2$sから%3$sに変更しました
-    %1$sが表示名 (%2$s) を削除しました
+    %1$sが表示名(%2$s)を削除しました
     %1$sがテーマを%2$sに変更しました
     %1$sがルーム名を%2$sに変更しました
     %sがビデオ通話を開始しました。
@@ -38,13 +38,13 @@
     不明 (%s)。
     %1$sがエンドツーエンド暗号化を有効にしました (%2$s)
     %1$sがVoIP会議をリクエストしました
-    VoIP会議が開始されました
+    VoIP会議が開始しました
     VoIP会議が終了しました
     (アバターも変更されました)
     %1$sがルーム名を削除しました
-    %1$sがルームトピックを削除しました
-    %1$sがプロフィール %2$sを更新しました
-    %1$sは%2$sにルームに参加するよう招待状を送りました
+    %1$sがルームのトピックを削除しました
+    %1$sがプロフィール %2$s を更新しました
+    %1$sは%2$sにルームへの招待を送りました
     %1$sは%2$sの招待を受け入れました
     ** 解読できません:%s **
     送信者の端末からこのメッセージの鍵が送信されていません。
@@ -59,14 +59,14 @@
     ルームのアバターを変更しました
     %1$sがルームのアバターを変更しました
     トピックを%1$sに変更しました
-    表示名を削除しました(%1$sでした)
+    表示名を削除しました(%1$sでした)
     表示名を%1$sから%2$sに変更しました
     表示名を%1$sに設定しました
     アバターを変更しました
     %1$sの招待を取り下げました
-    %1$sをBANしました
-    %1$sのBANを解除しました
-    %1$sを退出させました
+    %1$sをブロックしました
+    %1$sのブロックを解除しました
+    %1$sを追放しました
     招待を拒否しました
     ルームから退出しました
     %1$sがルームから退出しました
@@ -739,9 +739,9 @@
 \n追加しますか?
     続行する…
     申し訳ありません、この操作を完了するための外部アプリが見つかりません。
-    他のセッションから暗号鍵を再度要求します。
+    あなたの他のセッションに暗号鍵を再要求する。
     鍵のリクエストが送信されました。
-    リクエスト送信済
+    要求が送信されました
     鍵をこのセッションに送信できるように、メッセージを復号化できる他の端末で${app_name}を起動してください。
     
         %d秒
@@ -923,7 +923,7 @@
     バックアップから復元
     バックアップを削除
     鍵をバックアップしています。これには数分かかる可能性があります…
-    全ての鍵をバックアップしました
+    全ての鍵がバックアップされています
     
         %d個の鍵をバックアップしています…
     
@@ -1119,7 +1119,7 @@
     相手のコードをスキャン
     スキャンできません
     断る
-    検証リクエスト
+    認証の要求
     通話の開始前に確認
     意図しない通話を防止
     お使いの端末は脆弱性のある古いTLSセキュリティープロトコルを使用しています、このセキュリティーでは接続できません
@@ -1281,11 +1281,11 @@
     会話を始める
     %1$sがエンドツーエンド暗号化をオンにしました。
     エンドツーエンド暗号化をオンにしました。
-    ゲストがルームに入るのを拒否しています。
-    %1$sはゲストがルームに参加するのを拒否しています。
-    ゲストがルームに入るのを拒否しています。
+    ゲストがルームに参加するのを拒否しました。
+    %1$sはゲストがルームに参加するのを拒否しました。
+    ゲストがルームに参加するのを拒否しました。
     %1$sはゲストがルームに参加するのを拒否しました。
-    ここへのゲストの入室を許可しました。
+    ここにゲストが参加することを許可しました。
     %1$sはここにゲストが参加することを許可しました。
     ゲストにルームへの参加を許可しました。
     %1$sがゲストにルームへの参加を許可しました。
@@ -1308,14 +1308,14 @@
     %1$sの招待を受諾しました%2$。理由:%2$s
     %1$sのルームへの招待を取り消しました。理由:%2$s
     %1$sにルームへの招待状を送りました。理由:%2$s
-    VoIPカンファレンスをリクエストしました
+    VoIP会議を要求しました
     このルームのサーバーのアクセス制御リストを変更しました。
     このルームのサーバーアクセス制御リストを設定しました。
     ここをアップグレードしました。
-    あなたはこのルームをアップグレードしました。
+    このルームをアップグレードしました。
     通話を終了しました。
-    通話に応答しました。
-    通話を設定するためのデータを送信しました。
+    電話に出ました。
+    通話を設定するためにデータを送信しました。
     このルームのアドレスを変更しました。
     %1$sはこのルームのメインおよび代替アドレスを変更しました。
     %1$sがこのルームのアドレスを変更しました。
@@ -1342,7 +1342,7 @@
     
         %1$sはこのルームのアドレスとして%2$sを追加しました。
     
-    あなたのプロフィールが更新されました %1$s
+    プロフィール %1$s を更新しました
     %sがこのルームのサーバーのアクセス制御リストを変更しました。
     IPリテラルに一致するサーバーは禁止されています。
     ・IPリテラルに一致するサーバーを許可します。
@@ -1352,10 +1352,10 @@
     %sがここをアップグレードしました。
     %sがこのルームをアップグレードしました。
     エンドツーエンド暗号化をオンにしました (%1$s)
-    あなたは今後のメッセージを%1$sに見えるように設定しました
+    あなたは、今後のメッセージを%1$sに見えるように設定しました
     今後のルーム履歴を%1$sに見えるように設定しました
-    %1$sは今後のメッセージを%2$sに見えるように設定しました
-    %sが通話を設定するためのデータを送信しました。
+    %1$sは、今後のメッセージを%2$sに見えるように設定しました
+    %sが通話を設定するためにデータを送信しました。
     通話をしました。
     ビデオ通話をしました。
     あなたが%1$sをブロックしました。理由:%2$s
@@ -1417,7 +1417,7 @@
     デフォルト
     モデレーター
     管理者
-    あなたががビデオ会議を変更しました
+    ビデオ会議を変更しました
     %1$sがビデオ会議を変更しました
     ビデオ会議を開始しました
     ビデオ会議を終了しました
@@ -1438,10 +1438,10 @@
     %1$sが%2$sのルームへの招待を取り消しました
     %1$sが%2$sを招待しました
     あなたは%1$sにルームへの招待を送りました
-    メッセージが%1$sによって削除されました[理由:%2$s]
+    メッセージが%1$sにより削除されました[理由:%2$s]
     メッセージが削除されました[理由:%1$s]
-    %1$sがメッセージを削除しました
-    メッセージを削除
+    メッセージが%1$sにより削除されました
+    メッセージが削除されました
     ルームのアバターを削除しました
     %1$sがルームのアバターを削除しました
     ルームのトピックを削除しました
@@ -1487,14 +1487,14 @@
 \n%1$s
     ${app_name}のバックグラウンド制限は無効になっています。 このテストは、モバイル(WIFIでない)を使用して実行する必要があります。
 \n%1$s
-    端末を再起動してもサービスは開始されません。${app_name}が一度開かれるまで通知は届きません。
+    端末を再起動してもサービスは開始しません。${app_name}を一度開くまで通知は届きません。
     %1$s
 \nこのエラーは${app_name}の管理外です。 これはいくつかの理由で発生する可能性があります。 後で再試行すればうまくいくかもしれません。システム設定でGoogle Play Serviceのデータ使用量が制限されていないか、端末の時刻が正しいかどうか、もしくはカスタムROMで起こることがあります。
     ${app_name}モバイルからこれを行うことはできません
     ${app_name}はバッテリー最適化の影響を受けません。
     制限を無効にする
     起動時の開始を有効にする
-    端末を再起動するとサービスが開始されます。
+    端末を再起動するとサービスが開始します。
     サービスの再起動に失敗しました
     サービスが強制終了され、自動的に再起動されました。
     通知サービスの自動再起動
@@ -1666,9 +1666,9 @@
 \n
 \n最高度のセキュリティーのために、アカウントのパスワードと異なるものに設定することが大切です。
     パスフレーズを使用してバックアップを保護します。
-    暗号化が有効なルームで送信されたメッセージは、エンドツーエンド暗号化によって保護されます。メッセージを読むための鍵を持っているのは送受信者のみです。
+    暗号化されたメッセージは、エンドツーエンドの暗号化によって保護されています。これらの暗号化されたメッセージを読むための鍵を持っているのは、あなたと受信者だけです。
 \n
-\n鍵を失わないために、鍵を安全にバックアップしてください。
+\n鍵を失くさないよう、鍵を安全にバックアップしてください。
     (高度)
     暗号化されたメッセージを決して失わないために
     利用可能なMatrixセッションがありません
@@ -2147,7 +2147,7 @@
     地図
     投票を終了
     投票を終了
-    選択肢 %1$d
+    選択肢%1$d
     選択肢を作成
     ロケーション
     ロケーションを共有
@@ -2225,9 +2225,9 @@
     スレッドでディスカッションを整理して管理
     %sを待機しています…
     この端末でスキャン
-    検証を送信済
-    手動で検証
-    このセッションを検証
+    認証を送信済
+    手動で認証
+    このセッションを認証
     音声
     ルームのアドレスを入力してください
     このアドレスは既に使用されています
@@ -2415,7 +2415,7 @@
     現在IDサーバー %1$s でメールアドレスや電話番号を共有しています。共有を停止するには %2$s に再接続する必要があります。
     ルームを作成し設定しました。
     QRコードを読み取り、新しいダイレクトメッセージを作成
-    キーのインポートに失敗しました
+    鍵のインポートに失敗しました
     Matrix IDで新しいダイレクトメッセージを作成
     新しいダイレクトメッセージを作成
     ルーム作成メニューを閉じる…
@@ -2620,7 +2620,7 @@
     クロス署名を開始
     信頼されていません
     セキュリティーを確認
-    検証リクエスト
+    認証の要求
     %sがキャンセルしました
     既読
     検証
@@ -2722,7 +2722,7 @@
 \nアカウントデータとメッセージにアクセスするにはもう一度サインインしてください。
     現在のセッションはユーザー %1$s のものですが、あなたが提供している資格情報はユーザー %2$s のものです。この操作は${app_name}ではサポートされていません。
 \nまずデータをクリアし、その後、別のアカウントにサインインしてください。
-    暗号化されたメッセージが読めるように、サインインしてこの端末にのみ保存されている暗号鍵を取り戻してください。
+    暗号化されたメッセージがどの端末でも読めるように、サインインしてこの端末にのみ保存されている暗号鍵を取り戻してください。
     あなたのホームサーバー(%1$s)の管理者があなたを%2$sのアカウントからサインアウトしています。(%3$s)
     いくつかの原因が考えられます:
 \n
@@ -2760,9 +2760,9 @@
     ユーザーによる同意は与えられていません。
     代わりに、他のIDサーバーのURLを入力できます
     サーバー上の暗号化キーをバックアップすることにより、暗号化されたメッセージとデータへのアクセスが失われるのを防ぎます。
-    今キャンセルしてログインにアクセスできなくなると、暗号化されたメッセージとデータが失われる可能性があります。
+    いまキャンセルすると、ログインできなくなった際に、暗号化されたメッセージとデータを失ってしまう可能性があります。
 \n
-\nまた、設定からバックアップの設定や鍵の管理ができます。
+\nまた、設定から、安全なバックアップの設定や鍵の管理を行うことができます。
     USBメモリーもしくはバックアップドライブに保存
     鍵のバックアップの設定
     自己署名キーを同期しています
@@ -2775,4 +2775,24 @@
     鍵の要求
     鍵は既に最新です!
     鍵をリセット
+    質問は空にできません
+    ここで送受信されるメッセージはエンドツーエンド暗号化されています。
+\n
+\nメッセージは安全に保護されており、メッセージのロックを解除するための固有の鍵は、あなたと受信者だけが持っています。
+    このルームのメッセージはエンドツーエンド暗号化されています。
+\n
+\nメッセージは安全に保護されており、メッセージのロックを解除するための固有の鍵は、あなたと受信者だけが持っています。
+    ステートキー
+    リカバリーキーを以下に保存
+    送信者が意図的に鍵を送信しなかったため、このメッセージにアクセスすることができません
+    暗号鍵が適切に送信されませんでした。エンドツーエンド暗号化の性質上、相手のメッセージが届くまで待機する必要があるかもしれません。
+    鍵のバックアップのリカバリーキー
+    鍵のバックアップのパスフレーズが分からなければ、%sできます。
+    鍵のバックアップのリカバリーキーを使用
+    続行するには鍵のバックアップのパスフレーズを入力してください。
+    SSSS鍵をパスフレーズから生成しています(%s)
+    SSSS鍵をパスフレーズから生成しています
+    Curveキーを取得しています
+    バックアップキーを確認しています(%s)
+    バックアップキーを確認しています
 
\ No newline at end of file

From 5e573da5346bdd0100dd482bc4c202f1df06cb7d Mon Sep 17 00:00:00 2001
From: Rintan 
Date: Sun, 20 Feb 2022 18:24:19 +0000
Subject: [PATCH 509/581] Translated using Weblate (Japanese)

Currently translated at 91.0% (2535 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 39b2d1df779..1ef2b2fa3ea 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -1971,7 +1971,7 @@
     デフォルトで使いもう尋ねない
     鍵の共有リクエストの履歴を送信
     結果がありません
-    自分で電話をかけることはできません。メンバーが招待を受け入れるのを待ちます
+    自分との通話は開始できません、他の参加者が招待を受けるまでお待ちください
     ミーティングはJitsiのセキュリティーとパーミッションポリシーを使用します。会議中は、現在ルームにいる全ての人に招待状が表示されます。
     権限がありません
     音声メッセージを送信するには、マイクの権限を許可してください。

From 119d9547ffc3190e2e9cd4c7d368d8f3ff1d792c Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Sun, 20 Feb 2022 18:23:46 +0000
Subject: [PATCH 510/581] Translated using Weblate (Japanese)

Currently translated at 91.0% (2535 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 77 ++++++++++++-----------
 1 file changed, 39 insertions(+), 38 deletions(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 1ef2b2fa3ea..0f8e5e93274 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -30,13 +30,13 @@
     
         %1$sと他%2$d名
     
-    %1$sは、今後のルーム履歴を%2$sに見えるように設定しました
-    ルームのメンバー全員、招待された時点から。
-    ルームのメンバー全員、参加した時点から。
-    ルームのメンバー全員。
-    誰でも。
-    不明 (%s)。
-    %1$sがエンドツーエンド暗号化を有効にしました (%2$s)
+    %1$sは、今後のルーム履歴を%2$sに見えるように設定しました。
+    ルームのメンバー全員(招待された時点から)
+    ルームのメンバー全員(参加した時点から)
+    ルームのメンバー全員
+    全員
+    不明(%s)
+    %1$sがエンドツーエンド暗号化を有効にしました(%2$s)
     %1$sがVoIP会議をリクエストしました
     VoIP会議が開始しました
     VoIP会議が終了しました
@@ -205,7 +205,7 @@
     このユーザー名は既に使用されています
     削除
     参加
-    あなたは%sさんに、このルームへ招待されています
+    %sさんがこのルームに招待しています
     新しい会話
     メンバーを追加
     1名
@@ -219,7 +219,7 @@
     未送信のメッセージを再送信
     未送信のメッセージを削除
     ファイルが見つかりません
-    あなたはこのルームで発言する権限がありません。
+    このルームで発言する権限がありません。
     ルームの詳細
     参加者
     ファイル
@@ -486,8 +486,8 @@
     ルームのエンドツーエンド暗号鍵をエクスポート
     認証
     履歴を検索
-    あなたはこのルームに参加していません。
-    あなたはこのルームで権限がありません。
+    このルームに参加していません。
+    このルームで権限がありません。
     ルーム %s は、見ることができません。
     ユーザー名
     ホームサーバーのURL
@@ -562,7 +562,7 @@
     ダウンロードファイルに保存しますか?
     この招待はこのアカウントに関連付けられていない%sに送信されました。
 \n別のアカウントでログインするか、このメールを自分のアカウントに追加してください。
-    あなたは%sにアクセスしようとしています。ディスカッションに参加しますか?
+    %sにアクセスしようとしています。ディスカッションに参加しますか?
     ルーム
     これはルームのプレビューです。ルームでのやり取りは無効化されています。
     このユーザーにあなたと同じ権限を与えますが、この変更は取り消せません。
@@ -689,8 +689,8 @@
     グループのメンバーをフィルタリング
     グループのルームを絞り込み
     管理者はこのコミュニティーの詳細を規定していません。
-    あなたは%2$sによって%1$sから除外されました
-    あなたは%2$sによって%1$sへの参加を禁止されました
+    %2$sによって%1$sから除外されました
+    %2$sによって%1$sへの参加を禁止されました
     理由:%1$s
     再参加
     端末を振って不具合を報告
@@ -1305,10 +1305,10 @@
         このルームのアドレスの%1$sを削除しました。
     
     %1$sの招待を取り消しました。理由:%2$s
-    %1$sの招待を受諾しました%2$。理由:%2$s
+    %1$sの招待を受け入れました。理由:%2$s
     %1$sのルームへの招待を取り消しました。理由:%2$s
     %1$sにルームへの招待状を送りました。理由:%2$s
-    VoIP会議を要求しました
+    VoIP会議をリクエストしました
     このルームのサーバーのアクセス制御リストを変更しました。
     このルームのサーバーアクセス制御リストを設定しました。
     ここをアップグレードしました。
@@ -1337,7 +1337,7 @@
     
     %1$sが%2$sにルームへの招待を送りました。理由:%3$s
     %1$sが%2$sのルームへの招待を取り消しました。理由:%3$s
-    %1$sが %2$sの招待を承諾しました。理由:%3$s
+    %1$sが %2$sの招待を受け入れました。理由:%3$s
     %1$sは%2$sの招待を取り下げました。理由:%3$s
     
         %1$sはこのルームのアドレスとして%2$sを追加しました。
@@ -1351,20 +1351,20 @@
     %sがこのルームのサーバーアクセス制御リストを設定しました。
     %sがここをアップグレードしました。
     %sがこのルームをアップグレードしました。
-    エンドツーエンド暗号化をオンにしました (%1$s)
-    あなたは、今後のメッセージを%1$sに見えるように設定しました
-    今後のルーム履歴を%1$sに見えるように設定しました
-    %1$sは、今後のメッセージを%2$sに見えるように設定しました
+    エンドツーエンド暗号化を有効にしました(%1$s)
+    今後のメッセージを%1$sに見えるように設定しました。
+    今後のルーム履歴を%1$sに見えるように設定しました。
+    %1$sは、今後のメッセージを%2$sに見えるように設定しました。
     %sが通話を設定するためにデータを送信しました。
-    通話をしました。
-    ビデオ通話をしました。
+    通話を開始しました。
+    ビデオ通話を開始しました。
     あなたが%1$sをブロックしました。理由:%2$s
     %1$sが%2$sをブロックしました。理由:%3$s
     あなたが%1$sのブロックを解除しました。理由:%2$s
     %1$sが%2$sのブロックを解除しました。理由:%3$s
-    あなたは%1$sを除去しました。理由:%2$s
+    %1$sを除去しました。理由:%2$s
     %1$sが%2$sを除去しました。理由:%3$s
-    あなたは招待を拒否しました。理由:%1$s
+    招待を拒否しました。理由:%1$s
     %1$sは招待を拒否しました。理由:%2$s
     退出しました。理由:%1$s
     %1$sが退出しました。理由:%2$s
@@ -1423,21 +1423,21 @@
     ビデオ会議を終了しました
     %1$sがビデオ会議を開始しました
     %1$sがビデオ会議を開始しました
-    あなたは%1$sウィジェットを変更しました
+    %1$sウィジェットを変更しました
     %1$sは%2$sウィジェットを変更しました
-    あなたは%1$sウィジェットを削除しました
+    %1$sウィジェットを削除しました
     %1$sが%2$sウィジェットを削除しました
-    あなたは%1$sウィジェットを追加しました
-    あなたは%1$sの招待を受けました
+    %1$sウィジェットを追加しました
+    %1$sの招待を受け入れました
     %1$sが%2$sウィジェットを追加しました
     ルーム名を変更しました:%1$s
-    あなたは%1$sの招待を取り消しました
+    %1$sの招待を取り消しました
     %1$sが%2$sの招待を取り消しました
-    あなたは%1$sを招待しました
-    あなたは%1$sのルームへの招待を取り消しました
+    %1$sを招待しました
+    %1$sのルームへの招待を取り消しました
     %1$sが%2$sのルームへの招待を取り消しました
     %1$sが%2$sを招待しました
-    あなたは%1$sにルームへの招待を送りました
+    %1$sにルームへの招待を送りました
     メッセージが%1$sにより削除されました[理由:%2$s]
     メッセージが削除されました[理由:%1$s]
     メッセージが%1$sにより削除されました
@@ -2585,7 +2585,7 @@
     通話は終了しました
     自分自身にダイレクトメッセージを送信することはできません!
     %1$sは通話を開始しました
-    あなたは通話を開始しました
+    通話を開始しました
     電話番号(任意)
     電話番号を確認
     電話番号を設定
@@ -2713,7 +2713,7 @@
     Elementの改善と課題抽出のために、匿名の使用分析データを収集させてくださいませんか?複数の端末での使用を解析するために、あなたの全端末共通のランダムな識別子を生成します。
 \n
 \n%sで利用規約を閲覧できます。
-    最初の検索結果のみ表示中、文字をもっと入力してください…
+    最初の検索結果のみ表示中。文字をもっと入力してください…
     matrix.toリンクのフォーマットが正しくありませんでした
     注意!この端末には暗号鍵を含む個人情報が保存されています。
 \n
@@ -2731,12 +2731,12 @@
 \n• 他のセッションでこのセッションを削除している。
 \n
 \n• サーバーの管理者がセキュリティ上の理由であなたのアクセスを取り消している。
-    代理手段として、既にアカウントをお持ちでMatrix IDとパスワードをご存知なら、この方法が使用できます。
+    代わりに、既にアカウントをお持ちでMatrix IDとパスワードをご存知なら、この方法が使用できます。
     
         リクエストが多すぎます。%1$d秒後に再試行できます…
     
-    このホームサーバーが古いバージョンで運営しています。管理者にアップグレードを要請してください。続行できますが、いくつかの機能が正しく作動しない可能性があります。
-    このホームサーバーが古すぎるバージョンで運営しており、接続できません。管理者にアップグレードを要請してください。
+    このホームサーバーは古いバージョンです。管理者にアップグレードを要請してください。続行できますが、いくつかの機能が正しく作動しない可能性があります。
+    このホームサーバーのバージョンは古すぎるため、接続できません。管理者にアップグレードを要請してください。
     ホームサーバーのバージョンが古すぎます
     ただいま%1$sにメールを送信しました。
 \nアカウント登録を続けるにはメールに含まれたリンクをクリックしてください。
@@ -2795,4 +2795,5 @@
     Curveキーを取得しています
     バックアップキーを確認しています(%s)
     バックアップキーを確認しています
+    ナビゲーションのメニューを開く
 
\ No newline at end of file

From ccd71d6072a4417b3971edcec7cd4619fe7ef2db Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Sun, 20 Feb 2022 18:31:25 +0000
Subject: [PATCH 511/581] Translated using Weblate (Japanese)

Currently translated at 91.0% (2536 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 22 +++++++++++-----------
 1 file changed, 11 insertions(+), 11 deletions(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 0f8e5e93274..94c5f01468b 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -578,7 +578,7 @@
     リモートサーバーのIDを確認できませんでした。
     誰かが不当にあなたの通信を傍受しているか、リモートサーバーの証明書をあなたの電話が信用していない可能性があります。
     サーバーの管理者が、これは想定されていることであると言っているのであれば、以下のフィンガープリントが管理者によるフィンガープリントと一致していることを確認してください。
-    証明書はあなたの電話に信頼されていたものから変更されています。これはきわめて異常な事態です。この新しい証明書は承認しないことを強く推奨します。
+    証明書はあなたの電話に信頼されていたものから変更されています。これはきわめて異常な事態です。この新しい証明書を承認しないことを強く推奨します。
     証明書は以前信頼されていたものから信頼されていないものへと変更されています。サーバーがその証明書を更新した可能性があります。予測されるフィンガープリントを取得するために、サーバーの管理者に連絡してください。
     サーバーの管理者が上のフィンガープリントと一致するものを発行した場合に限り、証明書を承認してください。
     不正な形式のIDです。メールアドレスまたは\'@localpart:domain\'という形式のMatrix IDを入力してください
@@ -866,8 +866,8 @@
     畳む
     メッセージとエラーの場合
     とにかく通話
-    了承
-    このホームサーバーの方針を閲覧し承認してください:
+    承諾
+    このホームサーバーの方針を確認し承諾してください:
     通話設定画面
     着信に${app_name}の既定の着信音を使用
     着信音
@@ -1448,7 +1448,7 @@
     ルーム名を削除しました
     許可を与える
     ${app_name}は、信頼できる通知を受け取るために、影響の少ないバックグラウンド接続を維持する必要があります。
-\n次の画面で、${app_name}を常にバックグラウンドで実行することを許可するように求められます。同意してください。
+\n次の画面で、${app_name}を常にバックグラウンドで実行することを許可するように求められます。承認してください。
     バックグラウンド接続
     ディスカバリー設定を管理します。
     ディスカバリー(発見)
@@ -1546,7 +1546,7 @@
     ユーザー名を入力してください。
     無視
     共有
-    続けるには利用規約に同意する必要があります。
+    続けるには利用規約を承認する必要があります。
     全てブロック
     許可
     ルームID
@@ -1734,7 +1734,7 @@
     招待された人だけが発見し参加できます
     非公開
     不明のアクセス設定(%s)
-    誰でもノックができ、メンバーがその参加を承認または拒否できます
+    誰でもルームにノックができ、メンバーがその参加を承認または拒否できます
     現在のルームディレクトリの見え方を取得できません(%1$s)。
     このルームを%1$sのルームディレクトリに公開しますか?
     アドレスを非公開にする
@@ -1779,8 +1779,8 @@
     メールアドレスを設定
     メールアドレスを確認しました
     検出可能なメールアドレス
-    続行するには条件に同意してください
-    ホームサーバーの利用規約に同意したら、再試行してください。
+    続行するには利用規約を承認してください
+    ホームサーバーの利用規約を承認したら、再試行してください。
     次に
     次に
     次に
@@ -1971,7 +1971,7 @@
     デフォルトで使いもう尋ねない
     鍵の共有リクエストの履歴を送信
     結果がありません
-    自分との通話は開始できません、他の参加者が招待を受けるまでお待ちください
+    自分に電話をかけることはできません。参加者が招待を受け入れるまでお待ちください
     ミーティングはJitsiのセキュリティーとパーミッションポリシーを使用します。会議中は、現在ルームにいる全ての人に招待状が表示されます。
     権限がありません
     音声メッセージを送信するには、マイクの権限を許可してください。
@@ -1979,7 +1979,7 @@
     このアクションを実行するには、いくつかの権限が不足しています。システム設定から権限を付与してください。
     IDサーバーに接続できませんでした
     IDサーバーのURLを入力
-    連絡先を発見するために、あなたの連絡先のデータ(電話番号や電子メール)を、設定されたIDサーバー(%1$s)に送信することに同意しますか?
+    連絡先を発見するために、あなたの連絡先のデータ(電話番号や電子メール)を、設定されたIDサーバー(%1$s)に送信することを承認しますか?
 \n
 \nプライバシーの保護のため、データは送信前にハッシュ化されます。
     メールと電話番号を送信
@@ -2635,7 +2635,7 @@
     このアプリではこのホームサーバーにアカウントを作成できません。
 \n
 \nウェブクライエントを使用してアカウント登録しますか?
-    申し訳ありません。このサーバーはアカウントの新規登録を認めていません。
+    申し訳ありませんが、このサーバーはアカウントの新規登録を受け入れていません。
     このアプリではこのホームサーバーにサインインできません。このホームサーバーは次のサインイン方法に対応しています: %1$s
 \n
 \nウェブクライエントを使用してサインインしますか?

From 613c17bca1b37b29355763e917367799259d4bdd Mon Sep 17 00:00:00 2001
From: Joe Sagawa 
Date: Sun, 20 Feb 2022 18:27:01 +0000
Subject: [PATCH 512/581] Translated using Weblate (Japanese)

Currently translated at 91.0% (2536 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 94c5f01468b..5cc693da7da 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2796,4 +2796,6 @@
     バックアップキーを確認しています(%s)
     バックアップキーを確認しています
     ナビゲーションのメニューを開く
+    あなたが承諾しました
+    %sが承諾しました
 
\ No newline at end of file

From fff3a5eefa3ce16a1bbbe35a5b414c0cbad2bf87 Mon Sep 17 00:00:00 2001
From: Joe Sagawa 
Date: Sun, 20 Feb 2022 18:38:35 +0000
Subject: [PATCH 513/581] Translated using Weblate (Japanese)

Currently translated at 91.1% (2539 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 5cc693da7da..0211a3f6afa 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2798,4 +2798,9 @@
     ナビゲーションのメニューを開く
     あなたが承諾しました
     %sが承諾しました
+    %sが検証済み
+    %sを検証する
+    絵文字を比較して検証する
+    絵文字を比較して検証する
+    対面じゃなければ、代わりに絵文字を比較してください
 
\ No newline at end of file

From d22ac7464617990f8907b09daa498991de69c725 Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Sun, 20 Feb 2022 18:37:11 +0000
Subject: [PATCH 514/581] Translated using Weblate (Japanese)

Currently translated at 91.1% (2539 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 0211a3f6afa..4da3ae20785 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2494,7 +2494,7 @@
 \n
 \n鍵を手動でエクスポートできるように、次のポップアップでアクセスを許可してください。
     %1$sはリンクを知っている人がアクセスできるようにこのルームを設定しました。
-    まず、設定画面でIDサーバーの利用規約に同意してください。
+    初めに設定画面でIDサーバーの利用規約を承認してください。
     初めにIDサーバーを設定してください。
     
         %1$d個の投票があります。結果を見るには投票してください
@@ -2796,11 +2796,12 @@
     バックアップキーを確認しています(%s)
     バックアップキーを確認しています
     ナビゲーションのメニューを開く
-    あなたが承諾しました
+    承諾しました
     %sが承諾しました
     %sが検証済み
     %sを検証する
     絵文字を比較して検証する
     絵文字を比較して検証する
     対面じゃなければ、代わりに絵文字を比較してください
+    あなたのホームサーバーは、最大%sのサイズの添付ファイルを受け付けています。
 
\ No newline at end of file

From 865e0fa084617b9325b86155411e3e80b5635320 Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Sun, 20 Feb 2022 18:56:12 +0000
Subject: [PATCH 515/581] Translated using Weblate (Japanese)

Currently translated at 91.1% (2539 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 42 +++++++++++------------
 1 file changed, 21 insertions(+), 21 deletions(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 4da3ae20785..ad2b71b11d6 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -28,7 +28,7 @@
     %1$sと%2$s
     空のルーム
     
-        %1$sと他%2$d名
+        %1$sと他%2$d人
     
     %1$sは、今後のルーム履歴を%2$sに見えるように設定しました。
     ルームのメンバー全員(招待された時点から)
@@ -1333,7 +1333,7 @@
         %1$sはこのルームのアドレスの%2$sを削除しました。
     
     
-        このルームのアドレスとして%1$sが追加されました。
+        このルームのアドレスとして%1$sを追加しました。
     
     %1$sが%2$sにルームへの招待を送りました。理由:%3$s
     %1$sが%2$sのルームへの招待を取り消しました。理由:%3$s
@@ -1358,50 +1358,50 @@
     %sが通話を設定するためにデータを送信しました。
     通話を開始しました。
     ビデオ通話を開始しました。
-    あなたが%1$sをブロックしました。理由:%2$s
+    %1$sをブロックしました。理由:%2$s
     %1$sが%2$sをブロックしました。理由:%3$s
-    あなたが%1$sのブロックを解除しました。理由:%2$s
+    %1$sのブロックを解除しました。理由:%2$s
     %1$sが%2$sのブロックを解除しました。理由:%3$s
-    %1$sを除去しました。理由:%2$s
-    %1$sが%2$sを除去しました。理由:%3$s
+    %1$sを追放しました。理由:%2$s
+    %1$sが%2$sを追放しました。理由:%3$s
     招待を拒否しました。理由:%1$s
     %1$sは招待を拒否しました。理由:%2$s
     退出しました。理由:%1$s
     %1$sが退出しました。理由:%2$s
-    あなたがこのルームを退出しました。理由:%1$s
+    このルームを退出しました。理由:%1$s
     初期同期:
 \n退出したルームをインポートしています
     初期同期:
 \n招待されたルームをインポートしています
-    初期同期:
+    初期同期:
 \n会話を読み込んでいます
 \n多くのルームに参加している場合、読み込みに時間がかかるかもしれません
     %1$sがこのルームを退出しました。理由:%2$s
-    あなたがこのルームに参加しました。理由:%1$s
+    このルームに参加しました。理由:%1$s
     %1$sがこのルームに参加しました。理由:%2$s
-    あなたがこのルームに参加しました。理由:%1$s
+    このルームに参加しました。理由:%1$s
     %1$sがこのルームに参加しました。理由:%2$s
     %1$sがあなたを招待しました。 理由:%2$s
-    あなたが%1$sを招待しました。 理由:%2$s
+    %1$sを招待しました。 理由:%2$s
     %1$sが%2$sを招待しました。 理由:%3$s
     あなたの招待です。理由:%1$s
     %1$sの招待です。理由:%2$s
     送信キューのクリア
     メッセージを送っています…
     メッセージを送りました
-    初期同期:
+    初期同期:
 \nアカウントデータをインポートしています
-    初期同期:
+    初期同期:
 \nコミュニティーをインポートしています
-    初期同期:
+    初期同期:
 \nルームをインポートしています
-    初期同期:
+    初期同期:
 \n暗号をインポートしています
-    初期同期:
+    初期同期:
 \nアカウントをインポートしています…
-    初期同期:
+    初期同期:
 \nデータをダウンロードしています…
-    初期同期:
+    初期同期:
 \nサーバーからの応答を待っています…
     空のルーム(%sでした)
     
@@ -2800,8 +2800,8 @@
     %sが承諾しました
     %sが検証済み
     %sを検証する
-    絵文字を比較して検証する
-    絵文字を比較して検証する
-    対面じゃなければ、代わりに絵文字を比較してください
+    絵文字を比較して検証
+    絵文字を比較して検証
+    対面でない場合は、代わりに絵文字を比較してください
     あなたのホームサーバーは、最大%sのサイズの添付ファイルを受け付けています。
 
\ No newline at end of file

From aa753cc4cfbfae3341e179d505c396d502efe6e2 Mon Sep 17 00:00:00 2001
From: Rintan 
Date: Sun, 20 Feb 2022 19:34:12 +0000
Subject: [PATCH 516/581] Translated using Weblate (Japanese)

Currently translated at 91.3% (2543 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index ad2b71b11d6..0a5f8a49262 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -1585,7 +1585,7 @@
     とにかく参加
     ルームを追加
     %sはあなたを招待しています
-    このルームでグループ通話をする権利がありません
+    このルームでグループ通話を開始する権限がありません
     オーディオミーティングを開始
     安全バックアップを設定
     鍵のバックアップで管理

From 756e975c34a7033506f75655da36bb78c3f12b75 Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Sun, 20 Feb 2022 19:33:39 +0000
Subject: [PATCH 517/581] Translated using Weblate (Japanese)

Currently translated at 91.3% (2543 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 76 +++++++++++------------
 1 file changed, 38 insertions(+), 38 deletions(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 0a5f8a49262..cb5207b4bb9 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -30,7 +30,7 @@
     
         %1$sと他%2$d人
     
-    %1$sは、今後のルーム履歴を%2$sに見えるように設定しました。
+    %1$sが今後のルーム履歴を%2$sに見えるように設定しました。
     ルームのメンバー全員(招待された時点から)
     ルームのメンバー全員(参加した時点から)
     ルームのメンバー全員
@@ -44,9 +44,9 @@
     %1$sがルーム名を削除しました
     %1$sがルームのトピックを削除しました
     %1$sがプロフィール %2$s を更新しました
-    %1$sは%2$sにルームへの招待を送りました
-    %1$sは%2$sの招待を受け入れました
-    ** 解読できません:%s **
+    %1$sが%2$sにルームへの招待を送りました
+    %1$sが%2$sの招待を受け入れました
+    ** 復号化できません:%s **
     送信者の端末からこのメッセージの鍵が送信されていません。
     修正できませんでした
     メッセージを送信できません
@@ -192,7 +192,7 @@
     黒いテーマ
     同期しています…
     メッセージ
-    メンバー詳細
+    メンバーの詳細
     引用
     後で
     転送
@@ -344,8 +344,8 @@
     より大きい
     とても大きい
     巨大
-    発言更新を確認
-    暗号解除されたソースコードを表示
+    発言更新を確認しています
+    復号化されたソースコードを表示
     名前変更
     オフライン
     会話を開始
@@ -354,7 +354,7 @@
     写真または動画を撮影
     初期化メール送信
     写真撮影やビデオ通話には, ${app_name}アプリに端末のカメラの使用を許可する必要があります.
-    通話を開始できませんでした。後ほどお試しください
+    通話を開始できませんでした。後ほど試してください
     権限が無いため、一部の機能を利用できない可能性があります…
     このルームで会議を開始するためには招待の権限が必要です
     とにかく送る
@@ -874,7 +874,7 @@
     着信音を選んでください:
     追い出す
     理由
-    サービスを初期化
+    サービスを初期化しています
     鍵のバックアップ
     鍵のバックアップを使用
     セッションを認証
@@ -910,7 +910,7 @@
     パスワード
     パスワード
     今ここでサインアウトすると、あなたの暗号化されたメッセージは失われてしまいます
-    鍵のバックアップは現在処理中です。処理中にサインアウトすると暗号化されたメッセージにアクセスできなくなります。
+    鍵のバックアップは現在処理中です。処理中にサインアウトすると、暗号化されたメッセージにアクセスできなくなります。
     暗号化されたメッセージにアクセスできなくなることを防ぐため、鍵の安全なバックアップはあなたのセッション全てで有効化してください。
     暗号化されたメッセージは不要です
     鍵をバックアップしています…
@@ -1276,7 +1276,7 @@
     非公開
     切り替える
     加える
-    %1$sはエンドツーエンド暗号化(認識されていないアルゴリズム %2$s)をオンにしました。
+    %1$sがエンドツーエンド暗号化(認識されていないアルゴリズム %2$s)をオンにしました。
     エンドツーエンド暗号化(認識されていないアルゴリズム %1$s)をオンにしました。
     会話を始める
     %1$sがエンドツーエンド暗号化をオンにしました。
@@ -1287,24 +1287,24 @@
     %1$sはゲストがルームに参加するのを拒否しました。
     ここにゲストが参加することを許可しました。
     %1$sはここにゲストが参加することを許可しました。
-    ゲストにルームへの参加を許可しました。
-    %1$sがゲストにルームへの参加を許可しました。
+    ゲストがルームに参加するのを許可しました。
+    %1$sはゲストがルームに参加するのを許可しました。
     システムデフォルト
     このルームのメインおよび代替のアドレスを変更しました。
     このルームの代替アドレスを変更しました。
     
-        このルームの代替アドレス%1$sを削除しました。
+        このルームの代替アドレス %1$s を削除しました。
     
     
-        このルームの代替アドレス%1$sを追加しました。
+        このルームの代替アドレス %1$s を追加しました。
     
     このルームのメインアドレスを削除しました。
     このルームのメインアドレスを%1$sに設定しました。
-    このルームのアドレスとして、%1$sを追加し%2$sを削除しました。
+    このルームのアドレスに%1$sを追加し%2$sを削除しました。
     
         このルームのアドレスの%1$sを削除しました。
     
-    %1$sの招待を取り消しました。理由:%2$s
+    %1$sの招待を取り下げました。理由:%2$s
     %1$sの招待を受け入れました。理由:%2$s
     %1$sのルームへの招待を取り消しました。理由:%2$s
     %1$sにルームへの招待状を送りました。理由:%2$s
@@ -1317,30 +1317,30 @@
     電話に出ました。
     通話を設定するためにデータを送信しました。
     このルームのアドレスを変更しました。
-    %1$sはこのルームのメインおよび代替アドレスを変更しました。
+    %1$sがこのルームのメインおよび代替アドレスを変更しました。
     %1$sがこのルームのアドレスを変更しました。
-    %1$sはこのルームの代替アドレスを変更しました。
+    %1$sがこのルームの代替アドレスを変更しました。
     
-        %1$sがこのルームの代替アドレス%2$sを削除しました。
+        %1$sがこのルームの代替アドレス %2$s を削除しました。
     
     
-        %1$sはこのルームの代替アドレス%2$sを追加しました。
+        %1$sがこのルームの代替アドレス %2$s を追加しました。
     
     %1$sがこのルームのメインアドレスを削除しました。
     %1$sがこのルームのメインアドレスを%2$sに設定しました。
-    %1$sはこのルームのアドレスとして%2$sを追加し%3$sを削除しました。
+    %1$sがこのルームのアドレスに%2$sを追加し%3$sを削除しました。
     
-        %1$sはこのルームのアドレスの%2$sを削除しました。
+        %1$sがこのルームのアドレスの%2$sを削除しました。
     
     
-        このルームのアドレスとして%1$sを追加しました。
+        このルームのアドレスに%1$sを追加しました。
     
     %1$sが%2$sにルームへの招待を送りました。理由:%3$s
     %1$sが%2$sのルームへの招待を取り消しました。理由:%3$s
     %1$sが %2$sの招待を受け入れました。理由:%3$s
-    %1$sは%2$sの招待を取り下げました。理由:%3$s
+    %1$sが%2$sの招待を取り下げました。理由:%3$s
     
-        %1$sはこのルームのアドレスとして%2$sを追加しました。
+        %1$sがこのルームのアドレスに%2$sを追加しました。
     
     プロフィール %1$s を更新しました
     %sがこのルームのサーバーのアクセス制御リストを変更しました。
@@ -1354,7 +1354,7 @@
     エンドツーエンド暗号化を有効にしました(%1$s)
     今後のメッセージを%1$sに見えるように設定しました。
     今後のルーム履歴を%1$sに見えるように設定しました。
-    %1$sは、今後のメッセージを%2$sに見えるように設定しました。
+    %1$sが今後のメッセージを%2$sに見えるように設定しました。
     %sが通話を設定するためにデータを送信しました。
     通話を開始しました。
     ビデオ通話を開始しました。
@@ -1365,11 +1365,11 @@
     %1$sを追放しました。理由:%2$s
     %1$sが%2$sを追放しました。理由:%3$s
     招待を拒否しました。理由:%1$s
-    %1$sは招待を拒否しました。理由:%2$s
+    %1$sが招待を拒否しました。理由:%2$s
     退出しました。理由:%1$s
     %1$sが退出しました。理由:%2$s
     このルームを退出しました。理由:%1$s
-    初期同期:
+    初期同期:
 \n退出したルームをインポートしています
     初期同期:
 \n招待されたルームをインポートしています
@@ -1405,10 +1405,10 @@
 \nサーバーからの応答を待っています…
     空のルーム(%sでした)
     
-        %1$sと%2$sと%3$sと%4$dなど
+        %1$s、%2$s、%3$sと他%4$d人
     
-    %1$sと%2$sと%3$sと%4$s
-    %1$sと%2$sと%3$s
+    %1$s、%2$s、%3$sと%4$s
+    %1$s、%2$sと%3$s
     %1$sの権限レベルを%2$sから%3$sへ変更しました。
     %1$sが
     あなたは
@@ -1424,7 +1424,7 @@
     %1$sがビデオ会議を開始しました
     %1$sがビデオ会議を開始しました
     %1$sウィジェットを変更しました
-    %1$sは%2$sウィジェットを変更しました
+    %1$sが%2$sウィジェットを変更しました
     %1$sウィジェットを削除しました
     %1$sが%2$sウィジェットを削除しました
     %1$sウィジェットを追加しました
@@ -2487,7 +2487,7 @@
     制限は不明です。
     サーバーのファイルアップロードの制限
     自分の会話は、自分のものに。
-    %1$sは、このルームを招待者のみ参加可能に設定しました。
+    %1$sが、このルームを、招待者のみ参加可能に設定しました。
     %1$sが部屋をリンクを持っているユーザーにアクセスできるように設定しました。
     選択したメッセージをネタバレとして送信
     ${app_name} がエンドツーエンド暗号鍵をディスクに保存する許可を要求しています。
@@ -2552,8 +2552,8 @@
     最近のルーム
     新しいセッションが認証されました。セッションは暗号化されたメッセージにアクセスでき、他のユーザーには信頼済として表示されます。
     いったん有効にすると、暗号化を無効にすることはできません。
-    この部屋を同じホームサーバー上で組織内のチームとのコラボレーションにのみ使用するなら、このオプションを有効にするといいかもしれません。これは後から変更できません。
-    %sに属していないユーザーによるこの部屋への参加を今後永久的に拒否する
+    このルームを同じホームサーバー上で組織内のチームとのコラボレーションにのみ使用するなら、このオプションを有効にするといいかもしれません。これは後から変更できません。
+    %sに属していないユーザーによるこのルームへの参加を、今後永久的に拒否
     プレーンテキストメッセージの前に ( ͡° ͜ʖ ͡°) を付ける
     このメールアドレスのドメインの登録は許可されていません
     スペースを作成しています…
@@ -2578,13 +2578,13 @@
     既存のサーバーに参加しますか?
     この質問をスキップ
     友達と家族
-    %1$sは、これを招待者のみ参加可能に設定しました。
+    %1$sがこれを招待者のみ参加可能に設定しました。
     メッセージを送信できませんでした
     ウィジェットを開く
     %1$sに転送
     通話は終了しました
     自分自身にダイレクトメッセージを送信することはできません!
-    %1$sは通話を開始しました
+    %1$sが通話を開始しました
     通話を開始しました
     電話番号(任意)
     電話番号を確認

From 04225de7ef9fc5094d744b0d7a69d79dec3575f8 Mon Sep 17 00:00:00 2001
From: oksya8and8 
Date: Sun, 20 Feb 2022 19:44:22 +0000
Subject: [PATCH 518/581] Translated using Weblate (Japanese)

Currently translated at 91.3% (2543 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index cb5207b4bb9..e4bdb3c6923 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -734,9 +734,9 @@
     可能であれば、英語で説明文を記述してください。
     音声を送信
     スタンプを送信
-    使用可能なスタンプパックがありません。 
+    現在、有効なスタンプパックがありません。 
 \n
-\n追加しますか?
+\nいくつか追加しますか?
     続行する…
     申し訳ありません、この操作を完了するための外部アプリが見つかりません。
     あなたの他のセッションに暗号鍵を再要求する。

From aed16d0d9ee0316f120c8edb03ff2752c3db6726 Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Sun, 20 Feb 2022 19:43:18 +0000
Subject: [PATCH 519/581] Translated using Weblate (Japanese)

Currently translated at 91.3% (2543 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index e4bdb3c6923..ebb15f50ee4 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -116,7 +116,7 @@
     会話
     公開されたルームがありません
     
-        %d名
+        %d名のユーザー
     
     不具合を報告
     不具合の内容と状況の説明をお願いします。何をしましたか?何が起こるべきでしたか?実際に起こった事象は何でしょうか?
@@ -126,7 +126,7 @@
     ログを送信
     開発者が問題を診断するために、このクライアントのログがバグレポートと一緒に送信されます。バグレポートは、ログとスクリーンショットを含めて、公開されることはありません。上記の説明文だけを送信したい場合は、以下のチェックを解除してください。
     あなたは不満で端末を振っているようです。バグレポートの画面を開きますか?
-    前回アプリケーションは正常に停止しませんでした。クラッシュ報告の画面を開きますか?
+    前回、アプリケーションは正常に停止しませんでした。クラッシュ報告の画面を開きますか?
     不具合を報告しました
     不具合の報告の送信に失敗しました (%s)
     ルームに参加
@@ -358,7 +358,7 @@
     権限が無いため、一部の機能を利用できない可能性があります…
     このルームで会議を開始するためには招待の権限が必要です
     とにかく送る
-    ログアウト
+    サインアウト
     ホーム
     会話なし
     端末の電話帳を${app_name}アプリが読み取ることは許可されていません
@@ -1034,7 +1034,7 @@
     暗号化が有効になっていません
     通知設定
     切断
-    ログアウトしますか?
+    サインアウトしてよろしいですか?
     既読にする
     コピー
     成功
@@ -1072,7 +1072,7 @@
     
         元の大きさのまま画像を送信
     
-    自分自身には通話できません
+    自分に電話をかけることはできません
     マークダウン書式
     メッセージ送信前にマークダウン書式を適用します。これにより、アスタリスクを使用して斜体のテキストを表示するなどの高度な書式設定が利用できます。
     音声とビデオ
@@ -1118,7 +1118,7 @@
     相手ユーザーの端末のコードをスキャンし、相互に安全性を検証
     相手のコードをスキャン
     スキャンできません
-    断る
+    拒否
     認証の要求
     通話の開始前に確認
     意図しない通話を防止
@@ -1140,7 +1140,7 @@
     ヘッドセット
     スピーカー
     電話
-    サウンド端末を選択
+    サウンドデバイスを選択
     リアルタイム接続を確立できませんでした。
 \nホームサーバーの管理者に、通話が正常に動作するためにTURNを設定するようご連絡ください。
     再び表示しない
@@ -1265,7 +1265,7 @@
     フォールバックコールアシストサーバーを許可
     有効な認証情報がないため、権限がありません
     ${app_name} 呼び出し失敗
-    通話が確実に機能させるためには、ホームサーバー(%1$s)の管理者にTURNサーバーの設定を依頼してください。
+    確実に通話できるようにするためには、ホームサーバー(%1$s)の管理者にTURNサーバーの設定を依頼してください。
 \n
 \n代わりに、%2$sのパブリックサーバーを使用することもできますが、信頼性は低く、あなたのIPアドレスがそのサーバーと共有されてしまいます。これは設定から管理することができます。
     ルームディレクトリの全てのルームを表示(露骨なコンテンツのあるルームを含む)する。
@@ -1275,7 +1275,7 @@
     戻る
     非公開
     切り替える
-    加える
+    追加
     %1$sがエンドツーエンド暗号化(認識されていないアルゴリズム %2$s)をオンにしました。
     エンドツーエンド暗号化(認識されていないアルゴリズム %1$s)をオンにしました。
     会話を始める

From ebcb9faf53e759796d249d3305f39c4920050983 Mon Sep 17 00:00:00 2001
From: Joe Sagawa 
Date: Mon, 21 Feb 2022 10:53:18 +0000
Subject: [PATCH 520/581] Translated using Weblate (Japanese)

Currently translated at 91.4% (2546 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index ebb15f50ee4..dee45497873 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2804,4 +2804,5 @@
     絵文字を比較して検証
     対面でない場合は、代わりに絵文字を比較してください
     あなたのホームサーバーは、最大%sのサイズの添付ファイルを受け付けています。
+    新しいパスワードを確認するには下記のリンクを開いてください。リンク先のリンクにアクセスしてから、この下をクリックしてください。
 
\ No newline at end of file

From 9ff6e9e1b76ad425577af1e34032c7351815c847 Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Mon, 21 Feb 2022 10:49:59 +0000
Subject: [PATCH 521/581] Translated using Weblate (Japanese)

Currently translated at 91.4% (2546 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 331 +++++++++++-----------
 1 file changed, 168 insertions(+), 163 deletions(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index dee45497873..c43dfd3d101 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -140,20 +140,20 @@
     ファイルを送信
     ログイン画面へ戻る
     電話番号
-    電話番号 (任意で)
+    電話番号(任意)
     ユーザー名かパスワードが正しくありません
     ユーザー名は半角英数字、ドット、ハイフン、アンダースコアのみで記して下さい
-    パスワードが短すぎます(最小6文字)
+    パスワードが短すぎます(最小6文字)
     正しくないメールアドレスのようです
     正しくない電話番号のようです
-    既に登録されている電子メールアドレスです。
+    既に登録されているメールアドレスです。
     パスワードが一致しません
     パスワードを忘れましたか?
-    登録を続行するには電子メールを確認して下さい
-    URLはhttp://か、https://で始めて下さい
-    ログインできません:通信エラー
+    登録を続行するにはメールボックスを確認して下さい
+    URLはhttp://かhttps://で始めて下さい
+    ログインできません:通信エラー
     ログインできません
-    登録できません:通信エラー
+    登録できません:通信エラー
     登録できません
     正しいURLを入力して下さい
     ユーザー名かパスワードが正しくありません
@@ -161,8 +161,8 @@
     大き目
     中程度
     小さ目
-    ダウンロードを停止しますか?
-    アップロードを停止しますか?
+    ダウンロードをキャンセルしますか?
+    アップロードをキャンセルしますか?
     %d秒
     %1$d分%2$d秒
     昨日
@@ -172,7 +172,7 @@
     呼び出し中です…
     はい
     いいえ
-    続ける
+    続行
     最新の未読へ移動
     ルームを退出
     このルームを退出してよろしいですか?
@@ -186,7 +186,7 @@
     %sさんが文字を入力しています…
     %1$sさんと%2$sさんが文字を入力しています…
     %1$sさん、%2$sさん他が文字を入力しています…
-    暗号文を送信…
+    暗号化されたメッセージを送信…
     明るいテーマ
     暗いテーマ
     黒いテーマ
@@ -210,10 +210,10 @@
     メンバーを追加
     1名
     セッション
-    権限を一般メンバーへ変更
-    権限を司会者へ変更
+    権限を一般ユーザーへ戻す
+    権限をモデレーターへ変更
     権限を管理者へ変更
-    ここに送信文を入力 (暗号なし)…
+    メッセージを送信(暗号化されていません)…
     サーバーとの接続が失われました。
     全て再送信
     未送信のメッセージを再送信
@@ -227,11 +227,11 @@
     お気に入り
     低優先度
     対話
-    忘れる
+    履歴を消去
     自分のアイコン画像
     表示名
-    電子メール
-    電子メールアドレスを追加
+    メールアドレス
+    メールアドレスを追加
     電話番号
     電話番号を追加
     通知音
@@ -241,21 +241,21 @@
     グループチャットでのメッセージ
     ルームへ招待されたとき
     通話の呼び出しがあったとき
-    自動発言プログラム(Bot)が発言した時
+    自動発言プログラム(Bot)が発言した時
     端末起動時に開始
     アプリを閉じているときの動作
-    アプリを閉じても新着を確認
-    新着確認を失敗とするまでの時間
-    リクエストの間隔
+    バックグラウンド同期を有効にする
+    同期のリクエストを失敗とするまでの時間
+    同期の間隔
     一時保存を消去
-    画像等の一時保存を消去
-    画像等を一時保存する期間
-    利用者設定
+    メディアの一時保存を消去
+    メディアファイルを保存
+    ユーザー設定
     通知
     無視しているユーザー
     その他
     拡張設定
-    暗号化
+    暗号
     通知対象
     端末の電話帳
     端末の電話帳の使用を許可
@@ -279,7 +279,7 @@
     端末操作表示
     認証を確認中
     電子メールを確認して、本文中のURLをクリックしてください。 完了したら「続ける」をクリックしてください。
-    この電子メールアドレスは既に使われています。
+    このメールアドレスは既に使われています。
     あなたのパスワードは更新されました
     国を選択
     
@@ -287,7 +287,7 @@
     電話番号
     選択した国では、この電話番号は正しくありません
     電話認証
-    SMSで認証番号を送りました. 以下にその番号を入力してください.
+    SMSで認証番号を送りました。以下にその番号を入力してください。
     認証番号を入力
     3日
     1週間
@@ -352,12 +352,12 @@
     音声通話を開始
     ビデオ通話を開始
     写真または動画を撮影
-    初期化メール送信
-    写真撮影やビデオ通話には, ${app_name}アプリに端末のカメラの使用を許可する必要があります.
+    再設定用のメールを送信
+    写真撮影やビデオ通話には、${app_name}端末のカメラの使用を許可する必要があります。
     通話を開始できませんでした。後ほど試してください
     権限が無いため、一部の機能を利用できない可能性があります…
     このルームで会議を開始するためには招待の権限が必要です
-    とにかく送る
+    無視して送る
     サインアウト
     ホーム
     会話なし
@@ -373,41 +373,41 @@
     アカウントを作成
     送信
     飛ばす
-    ユーザー名または電子メール
+    ユーザー名またはメールアドレス
     パスワード
     新しいパスワード
     ユーザー名
-    電子メールアドレス
-    電子メールアドレス (任意で)
+    メールアドレス
+    メールアドレス(任意)
     パスワード再確認
     新しいパスワードを再確認
     パスワードが入力されていません
     メールアドレスが入力されていません
     電話番号が入力されていません
-    電子メールアドレスまたは電話番号が入力されていません
+    メールアドレスまたは電話番号が入力されていません
     接続先サーバーを指定(高度)
     不正なトークン
     メールと電話番号の同時登録はまだシステムが対応できませんが、電話番号だけの登録は可能です。
 \n
 \n設定からプロフィールにメールアドレスを追加できます。
-    このホームサーバーはあなたがロボットではない認証を求めます
+    このホームサーバーは、あなたがロボットではないことの確認を求めています
     ユーザー名は既に使用されています
     ホームサーバー:
-    IDサーバー:
+    IDサーバー:
     メールアドレスを認証しました
-    パスワードを初期化するには, アカウントに登録されている電子メールアドレスを入力してください:
-    あなたのアカウントに登録されたメールアドレスの入力が必要です。
+    パスワードを再設定するには、アカウントに登録されているメールアドレスを入力してください:
+    アカウントに登録されたメールアドレスの入力が必要です。
     新しいパスワードの入力が必要です。
-    %sへ電子メールが送信されました. リンクをたどったら以下をクリックしてください.
-    電子メールアドレスの確認に失敗しました: 電子メールのリンクをクリックしたことを確認してください
+    %sへ電子メールが送信されました。リンクをたどったら以下をクリックしてください。
+    メールアドレスの確認に失敗しました:電子メールのリンクをクリックしたことを確認してください
     パスワードがリセットされました。
 \n
-\n全てのセッションからログアウトしたため、プッシュ通知を受け取れなくなりました。通知を再び有効にするには、各端末で再ログインをお願いします。
+\n全てのセッションがログアウトしたため、プッシュ通知を受け取れなくなりました。通知を再び有効にするには、各端末で再度ログインしてください。
     登録できません:メールアドレスの所有権が確認できません
     指定されたアクセストークンが認識されませんでした
     不正な形式のJSON
     有効なJSONを含んでいませんでした
-    ログイン要求が多すぎてサーバーが対応できません
+    ログイン要求が多すぎます
     まだクリックされていない電子メールのリンク
     以下の容量で画像を送信
     ルームの説明
@@ -422,35 +422,35 @@
     他の端末で通話に応答しました
     写真または動画の撮影
     動画を記録できません
-    保存
+    保存済
     プレビュー
     拒否
     管理者権限操作
     通話
     ダイレクトメッセージ
-    再入室禁止
-    再入室禁止解除
-    このメンバーの発言を全て非表示
+    ブロック
+    ブロックを解除
+    この参加者の発言を全て非表示
     このメンバーの発言を全て表示
-    指名して呼掛け
-    ユーザーID, 表示名, 電子メールアドレス
+    メンション
+    ユーザーID、名前、メールアドレス
     セッション一覧を表示
     %sさんをこのルームに招待してよろしいですか?
     ユーザーIDで招待
-    メールアドレスまたはMatrix ID
-    全て中止
+    メールアドレスかMatrix ID
+    全てキャンセル
     ログアウト
     無視
     招待中
     参加者
-    メンバーを検索
-    結果なし
+    ルームのメンバーを絞り込む
+    結果がありません
     参加者
     ファイル
     ルーム
     ディレクトリを見る
     会話から退出
-    会話
+    メッセージ
     設定
     バージョン
     利用規約
@@ -472,7 +472,7 @@
     パスワードの更新に失敗しました
     %sの全てのメッセージを表示しますか?
 \n
-\nこの操作はアプリを再起動するため時間がかかる場合があります。
+\nこの操作はアプリを再起動するため、時間がかかる場合があります。
     電話番号の認証時にエラーが発生しました
     このルームへのリンクを作成するには、アドレスが必要です。
     このルームは暗号化されています。
@@ -499,19 +499,19 @@
     Matrixの連絡先のみ
     通信先が通話の受取に失敗しました。
     情報
-    ${app_name}は添付ファイルを送信および保存するために写真とビデオライブラリにアクセスするための許可が必要です。
+    ${app_name}は、添付ファイルを送信および保存するために、写真とビデオライブラリにアクセスするための許可を必要としています。
 \n
-\nあなたの端末からファイルを送信できるようにするには、次のポップアップでアクセスを許可してください。
+\n端末からファイルを送信できるようにするには、次のポップアップでアクセスを許可してください。
     "
 \n
-\n通話をするには、次のポップアップでアクセスできるように設定してください。"
-    ${app_name}アプリは、音声通話を実行するためにマイクへアクセスするための許可が必要です。
+\n通話をするためには、次のポップアップでアクセスを許可してください。"
+    ${app_name}は、音声通話を実行するためにマイクへアクセスするための許可を必要としています。
     "
 \n
 \n通話をするためには、次のポップアップでアクセスを許可してください。"
     ${app_name}はビデオ通話を行うためにカメラとマイクにアクセスする許可を必要としています。
 \n
-\n次のポップアップでアクセスを許可して通話ができるようにしてください。
+\n通話をするためには、次のポップアップでアクセスを許可してください。
     ${app_name}では、あなたの端末の電話帳のメールアドレスや電話番号をもとに、他のユーザーを検索することができます。${app_name}による電話帳の検索を許可する場合は、次のポップアップでアクセスを許可してください。
     ${app_name}はあなたの電話帳のメールアドレスや電話番号をもとに他のユーザーを見つけることができます。
 \n
@@ -559,13 +559,13 @@
     通知あり(サイレント)
     不具合の報告
     開封確認メッセージのリスト
-    ダウンロードファイルに保存しますか?
-    この招待はこのアカウントに関連付けられていない%sに送信されました。
+    ダウンロードフォルダーに保存しますか?
+    この招待は、このアカウントに関連付けられていない%sに送信されました。
 \n別のアカウントでログインするか、このメールを自分のアカウントに追加してください。
     %sにアクセスしようとしています。ディスカッションに参加しますか?
     ルーム
     これはルームのプレビューです。ルームでのやり取りは無効化されています。
-    このユーザーにあなたと同じ権限を与えますが、この変更は取り消せません。
+    このユーザーにあなたと同じ権限を与えます。この変更は取り消せません。
 \nよろしいですか?
     端末の連絡先 (%d)
     ユーザーディレクトリ (%s)
@@ -574,25 +574,25 @@
     メールアドレスかMatrix IDを入力してください
     信用する
     信用しない
-    フィンガープリント (%s):
+    フィンガープリント(%s):
     リモートサーバーのIDを確認できませんでした。
-    誰かが不当にあなたの通信を傍受しているか、リモートサーバーの証明書をあなたの電話が信用していない可能性があります。
-    サーバーの管理者が、これは想定されていることであると言っているのであれば、以下のフィンガープリントが管理者によるフィンガープリントと一致していることを確認してください。
-    証明書はあなたの電話に信頼されていたものから変更されています。これはきわめて異常な事態です。この新しい証明書を承認しないことを強く推奨します。
-    証明書は以前信頼されていたものから信頼されていないものへと変更されています。サーバーがその証明書を更新した可能性があります。予測されるフィンガープリントを取得するために、サーバーの管理者に連絡してください。
+    誰かが不当にあなたの通信を傍受しているか、あなたの電話がリモートサーバーの証明書を信用していない可能性があります。
+    サーバーの管理者が、これは想定されていることであると言っているのであれば、以下のフィンガープリントが、管理者によるフィンガープリントと一致していることを確認してください。
+    証明書はあなたの電話により信頼されていたものから変更されています。これはきわめて異常な事態です。この新しい証明書を承認しないことを強く推奨します。
+    証明書は以前信頼されていたものから信頼されていないものへと変更されています。サーバーがその証明書を更新した可能性があります。サーバーの管理者に連絡して、適切なフィンガープリントを確認してください。
     サーバーの管理者が上のフィンガープリントと一致するものを発行した場合に限り、証明書を承認してください。
     不正な形式のIDです。メールアドレスまたは\'@localpart:domain\'という形式のMatrix IDを入力してください
     このコンテンツを報告する理由
     このユーザーによる全てのメッセージを非表示にしますか?
 \n
-\nこの操作はアプリを再起動するため時間がかかる場合があります。
+\nこの操作はアプリを再起動するため、時間がかかる場合があります。
     アップロードをキャンセル
     ダウンロードをキャンセル
     検索
     メッセージ
     ディレクトリを検索しています…
     サードパーティーの使用に関する掲示
-    このアプリの情報をシステム設定で表示する。
+    このアプリの情報をシステム設定で表示。
     アプリの情報
     自分の表示名を含むメッセージ
     自分のユーザー名を含むメッセージ
@@ -600,8 +600,8 @@
     olmのバージョン
     サードパーティーの使用に関する掲示
     ホーム画面
-    逃した通知があるルームをピン止め
-    未読のあるルームをピン止め
+    逃した通知があるルームを固定
+    未読のあるルームを固定
     分析
     データ節約モード
     メールアドレスを認証できません。メールを確認して、そこに記載してあるリンクをクリックしてください。その後、「続ける」をクリックしてください。
@@ -656,8 +656,8 @@
     音声通話を開始してよろしいですか?
     ビデオ通話を開始してよろしいですか?
     グループリスト
-    ユーザーをブロックすると、ユーザーはこのルームから削除され、二度と参加できなくなります。
-    全てのメッセージ (音量大)
+    ユーザーをブロックすると、ユーザーはこのルームから追放され、二度と参加できなくなります。
+    全てのメッセージ(音量大)
     全てのメッセージ
     ミュート
     ホーム画面にショートカットを作成
@@ -695,27 +695,27 @@
     再参加
     端末を振って不具合を報告
     
-        %dメンバーシップの変更
+        %d個のメンバーシップの変更
     
     メンバーを表示
     見出しを開く
     同期しています…
     
-        %d名のメンバー
+        %d名のアクティブなメンバー
     
     
-        %d名
+        %d名のメンバー
     
     
         %d件の新しいメッセージ
     
     
-        %dルーム
+        %d個のルーム
     
     
-        %2$sに%1$sルーム見つかりました
+        %2$sに%1$s個のルームが見つかりました
     
-    ルームの履歴を消す
+    ルームの履歴を消去
     アバター
     メンションのみ
     通知のプライバシー
@@ -734,7 +734,7 @@
     可能であれば、英語で説明文を記述してください。
     音声を送信
     スタンプを送信
-    現在、有効なスタンプパックがありません。 
+    現在、有効なステッカーパックがありません。
 \n
 \nいくつか追加しますか?
     続行する…
@@ -760,16 +760,16 @@
     "%1$s、 "
     %1$sと%2$s
     %1$s %2$s
-    暗号化された返信を送信…
-    返信を送る(未暗号化)…
+    暗号化された返信を送る…
+    返信を送る(暗号化されていません)…
     
         %d個選択済
     
     低プライバシー
-    • 通知中のメッセージの内容は Matrixのホームサーバーから直接安全に入手しています
-    ・通知は メタデータとメッセージのデータ を含みます
-    • 通知は メッセージの内容を表示しません
-    ユーザーの名前をあげるときバイブレーションで通知
+    • 通知中のメッセージの内容はMatrixのホームサーバーから直接安全に入手しています
+    ・通知はメタデータとメッセージのデータを含みます
+    • 通知はメッセージの内容を表示しません
+    ユーザーをメンションするとき、バイブレーションで通知
     送信の前にメディアをプレビュー
     アカウントを停止
     自分のアカウントを停止
@@ -798,7 +798,7 @@
     
     必要な変数が見つかりません。
     変数が無効です。
-    メッセージを送信するには、キーボードのエンターキーを押してください
+    メッセージを送信するには、キーボードのEnterキーを押してください
     ボイスメッセージを送信
     動作を表示
     指定したIDのユーザーをブロック
@@ -827,10 +827,10 @@
     アカウントを停止
     この操作により、あなたのアカウントは永久に使えなくなります。あなたはログインできなくなり、誰も同じユーザーIDを再登録できなくなります。アカウントが参加している全てのルームを退出し、IDサーバーからアカウントの詳細は削除されます。 この操作は取り消せません。
 \n
-\nアカウントを停止しても、 デフォルトではあなたが送信したメッセージは忘却されません。メッセージの忘却を望む場合は、以下のボックスにチェックを入れてください。
+\nアカウントを停止しても、 デフォルトではあなたが送信したメッセージの履歴は消去されません。メッセージの履歴の消去を望む場合は、以下のボックスにチェックを入れてください。
 \n
-\nMatrixのメッセージの可視性は、電子メールと同様のものです。メッセージを忘却すると、あなたが送信したメッセージは、新規または未登録のユーザーには共有されることはありませんが、既にメッセージを取得している登録ユーザーは、今後もそのコピーにアクセスできます。
-    アカウントを停止したとき自分の送信した全てのメッセージを削除(警告: この操作により、今後のユーザーは会話を不完全な形で見ることになります)
+\nMatrixのメッセージの見え方は、電子メールと同様のものです。メッセージの履歴を消去すると、あなたが送信したメッセージは、新規または未登録のユーザーには共有されることはありませんが、既にメッセージを取得している登録ユーザーは、今後もそのコピーにアクセスできます。
+    アカウントを停止したときに、自分の送信した全てのメッセージの履歴を消去してください(警告: この操作により、今後のユーザーは会話を不完全な形で見ることになります)
     続けるには、パスワードを入力してください:
     アカウントを停止
     パスワードを入力してください。
@@ -865,14 +865,14 @@
     展開
     畳む
     メッセージとエラーの場合
-    とにかく通話
+    無視して通話
     承諾
     このホームサーバーの方針を確認し承諾してください:
     通話設定画面
     着信に${app_name}の既定の着信音を使用
     着信音
     着信音を選んでください:
-    追い出す
+    会話から追放
     理由
     サービスを初期化しています
     鍵のバックアップ
@@ -890,13 +890,13 @@
     バックグラウンド同期を行わない
     優先同期間隔
     %s
-\n同期は、端末のリソース (バッテリ残量) または状態 (スリープ) に応じて延期される場合があります。
+\n同期は、端末のリソース (バッテリーの残量)または状態(スリープ)に応じて延期される場合があります。
     入力中通知を送信
     文字入力中であることを他のメンバーに伝えます。
     開封確認メッセージを表示
-    開封確認メッセージをクリックすると詳細なリストを確認できます。
-    エンター入力でメッセージを送信
-    ソフトウェアキーボードのEnterボタンを押した際、改行せずメッセージを送信
+    開封確認メッセージをクリックすると、詳細な一覧を確認できます。
+    Enterキーでメッセージを送信
+    ソフトウェアキーボードのEnterボタンを押すと、改行の代わりにメッセージを送信
     パスワード
     パスワードを更新
     パスワードが無効です
@@ -934,7 +934,7 @@
     システム設定。
     アカウント設定。
     カスタム設定。
-    起動時の実行
+    起動時に実行
     バックグラウンド制限の確認
     とどまる
     編集
@@ -1098,7 +1098,7 @@
     システム設定で通知は有効化されています。
     バッテリー最適化
     
-        %d 秒
+        %d秒
     
     拡張設定
     現在の言語
@@ -1106,7 +1106,7 @@
     メッセージエディタ
     環境設定
     この端末で設定
-    セキュアバックアップをリセット
+    セキュアバックアップを再設定
     セキュアバックアップを設定
     管理
     セキュアバックアップ
@@ -1122,9 +1122,9 @@
     認証の要求
     通話の開始前に確認
     意図しない通話を防止
-    お使いの端末は脆弱性のある古いTLSセキュリティープロトコルを使用しています、このセキュリティーでは接続できません
+    お使いの端末は、脆弱性のある古いTLSセキュリティープロトコルを使用しています。セキュリティーの観点から、接続することはできません
     SSLエラー。
-    SSLエラー:相手のアイデンティティが認証されていません。
+    SSLエラー:相手のIDが認証されていません。
     このURLからホームサーバーに接続できませんでした、ご確認ください
     有効なMatrixサーバーのアドレスではありません
     このURLは検索結果に表示できません、ご確認ください
@@ -1187,8 +1187,8 @@
 \n%1$s
     Firebaseトークン
     Playサービスを修正
-    GooglePlayサービスAPKは利用可能で最新の状態になっています。
-    Playサービスチェック
+    Google PlayサービスのAPKは利用可能で最新の状態になっています。
+    Playサービスのチェック
     設定を確認
     カスタムルールの読み込みに失敗しました。再試行してください。
     一部の通知はカスタム設定で無効になっています。
@@ -1204,7 +1204,7 @@
     %sを削除しますか?
     認証が必要です
     パスワードを確認
-    暗号化されたルームでの検索はまだサポートされていません。
+    暗号化されたルームでの検索は、まだサポートされていません。
     ブロックされたユーザーを絞り込む
     トピックを変更
     ルームをアップグレード
@@ -1219,48 +1219,48 @@
     全員への通知
     他の人から送信されたメッセージの削除
     ユーザーのブロック
-    ユーザーの除去
+    ユーザーの追放
     設定の変更
     ユーザーの招待
     メッセージの送信
-    デフォルトルール
+    既定の役割
     ルームに関する変更を行うために必要な役割を更新する権限がありません
     ルームに関する変更を行うために必要な役割を選択
     ルームの権限
     権限
     ルームに関する変更を行うために必要な役割を表示し更新します。
-    ブロックを解除すると、ユーザーは再びルームに参加できるようになります。
-    禁止されたユーザー
-    禁止の理由
+    ブロックを解除すると、ユーザーはルームに再び参加できるようになります。
+    ユーザーをブロック
+    ブロックする理由
     ユーザーのブロックを解除
-    このユーザーはルームから除去されます。
+    このユーザーはルームから追放されます。
 \n
-\n再参加を防ぐためには、除去する代わりにブロックする必要があります。
-    除去の理由
-    ユーザーを除去
+\n再参加を防ぐためには、追放する代わりにブロックする必要があります。
+    追放する理由
+    ユーザーを追放
     このユーザーの招待をキャンセルしてよろしいですか?
     招待をキャンセル
     このユーザーを解除すると、そのユーザーからの全てのメッセージが再び表示されます。
     ユーザーを無視しない
     このユーザーを無視すると、あなたが共有しているルームからそのユーザーのメッセージが削除されます。
 \n
-\nこの動作は、設定からいつでも元に戻すことができます。
+\nこの操作は、設定からいつでも元に戻すことができます。
     ユーザーを無視
     降格
-    あなたは自分自身を降格させようとしているため、今後、この変更を元に戻すことはできなくなります。あなたがルームの中で最後の特権ユーザーである場合、特権を再取得することはできません。
+    あなたは自分自身を降格させようとしています。この変更は取り消せません。あなたがルームの中で最後の特権ユーザーである場合、特権を再取得することはできません。
     降格しますか?
     招待をキャンセル
     このルームは公開されていません。 招待がなければ再び参加することはできません。
-    このアクションを実行するには、設定にIDサーバーを追加してください。
+    この操作を実行するには、設定にIDサーバーを追加してください。
     連絡先へのアクセスを許可します。
     QRコードをスキャンするには、カメラへのアクセスを許可する必要があります。
     通話をかけました
     %sが通話をかけました
-    かける
-    コールし直す
+    保留
+    通話に戻る
     通話をやり直す
     アクティブな通話(%s)
-    あなたのホームサーバーがアシスト機能を提供しない場合、代わりに%sを使用します(IPアドレスは通話中に共有されます)
+    あなたのホームサーバーがアシスト機能を提供しない場合、代わりに%sを使用します(IPアドレスが通話中に共有されます)
     ビデオ通話が行われています…
     フォールバックコールアシストサーバーを許可
     有効な認証情報がないため、権限がありません
@@ -1457,23 +1457,23 @@
     サーバー上の暗号鍵をバックアップすることにより、暗号化されたメッセージとデータへのアクセスが失われるのを防ぎます。
     メッセージ作成画面に絵文字キーボードを開くボタンを追加
     絵文字キーボードを表示
-    アバターと表示名の変更が含まれます。
-    アカウントイベントを表示
+    アバターと表示名の変更を含む。
+    アカウントのイベントを表示
     招待、削除、ブロックは影響を受けません。
-    招待/参加/退出/削除/禁止イベントや、アバター/表示名の変更などを含む。
+    招待/参加/退出/追放/ブロックに関するイベントや、アバター/表示名の変更などを含む。
     参加・退出イベントを表示
     /confettiコマンドを使用するか、❄️または🎉を含むメッセージを送信
     チャットでエフェクトを表示
     ルームのメンバーのイベントを表示
     ホームサーバーがこの機能をサポートしている場合は、チャット内のリンクをプレビューします。
     ボット、ブリッジ、ウィジェット、ステッカーパックの管理をします。
-\nユーザーに代わり、構成データを受信しウィジェットを変更、ルーム招待の送信、権限の設定などができます。
+\nインテグレーションマネージャーは、構成データを受信し、ユーザーに代わってウィジェットの変更、ルーム招待の送信、権限の設定などを行うことができます。
     設定の更新に失敗しました。
     インテグレーション(統合)
     アプリがバックグラウンドにある場合、着信メッセージは通知されません。
     ${app_name}は正確な時間に定期的にバックグラウンドで同期します(構成可能)。
-\nこれはラジオとバッテリーの使用量に影響し$ {app_name}がイベントをリッスンしていることを示す永続的な通知が表示されます。
-    ${app_name}は、端末の限られたリソース(バッテリー)を維持する方法でバックグラウンド同期をします。
+\nこれは無線とバッテリーの使用量に影響し、$ {app_name}がイベントを待機していることを示す永続的な通知が表示されます。
+    ${app_name}は、端末の限られたリソース(バッテリーの残量)を維持する方法でバックグラウンド同期をします。
 \n端末の状態によっては、OSによって同期が延期される場合があります。
     LEDの色、振動、音を選択してください…
     通知(サイレント)を設定
@@ -1482,14 +1482,14 @@
     アプリはバックグラウンドでホームサーバーに接続する必要がないためバッテリー使用量を減らすことができます
     最適化を無視
     画面をオフにした状態で端末のプラグを抜いて一定時間静止したままにすると、端末は機内モードになります。 これにより、アプリがネットワークにアクセスできなくなり、ジョブ、同期、および標準のアラームが防止されます。
-    ${app_name}に対してバックグラウンド制限が有効になっています。
-\nアプリがバックグラウンドで実行しようとすると積極的に制限され、通知に影響を与える可能性があります。
+    ${app_name}のバックグラウンド制限が有効になっています。
+\nアプリがバックグラウンドで実行しようとする作業が積極的に制限されるため、通知に影響を与える可能性があります。
 \n%1$s
-    ${app_name}のバックグラウンド制限は無効になっています。 このテストは、モバイル(WIFIでない)を使用して実行する必要があります。
+    ${app_name}のバックグラウンド制限が無効になっています。 このテストは、モバイル(WIFIでない)を使用して実行する必要があります。
 \n%1$s
     端末を再起動してもサービスは開始しません。${app_name}を一度開くまで通知は届きません。
-    %1$s
-\nこのエラーは${app_name}の管理外です。 これはいくつかの理由で発生する可能性があります。 後で再試行すればうまくいくかもしれません。システム設定でGoogle Play Serviceのデータ使用量が制限されていないか、端末の時刻が正しいかどうか、もしくはカスタムROMで起こることがあります。
+    [%1$s]
+\nこのエラーは${app_name}の管理外です。 これはいくつかの理由で発生する可能性があります。 後で再試行すればうまくいくかもしれません。システム設定でGoogle Playサービスのデータ使用量が制限されていないか、端末の時刻が正しいかどうかを確認してください。カスタムROMで生じることもあります。
     ${app_name}モバイルからこれを行うことはできません
     ${app_name}はバッテリー最適化の影響を受けません。
     制限を無効にする
@@ -1502,19 +1502,19 @@
     通知をクリックしてください。 通知が表示されない場合は、システム設定を確認してください。
     通知を表示
     通知を表示しています。 クリックしてください!
-    プッシュの受信に失敗しました。 解決策は、アプリケーションを再インストールすることです。
-    アプリケーションはプッシュを受信しています
-    アプリケーションはプッシュを待っています
-    テストプッシュ
-    FCMトークンのホームサーバーへの登録に失敗しました。:
+    プッシュ通知の受信に失敗しました。 アプリケーションを再インストールすれば解決するかもしれません。
+    アプリケーションはプッシュ通知を受信しています
+    アプリケーションはプッシュ通知を待っています
+    プッシュ通知のテスト
+    FCMトークンのホームサーバーへの登録に失敗しました:
 \n%1$s
     FCMトークンのホームサーバーへの登録が成功しました。
     トークンの登録
     アカウントを追加
     [%1$s]
-\nこのエラーは、${app_name}の管理外です。このスマートフォンにはGoogleアカウントが登録されていません。アカウントマネージャーを開いて、Googleアカウントを追加してください。
-    %1$s
-\nこのエラーは${app_name}の管理外であり、Googleによると、このエラーは、端末にFCMに登録されているアプリが多すぎることを示しています。 このエラーは、アプリの数が極端に多い場合にのみ発生するため、平均的なユーザーには影響しません。
+\nこのエラーは${app_name}の管理外です。このスマートフォンにはGoogleアカウントが登録されていません。アカウントマネージャーを開いて、Googleアカウントを追加してください。
+    [%1$s]
+\nこのエラーは${app_name}の管理外です。Googleによると、このエラーは、FCMに登録されている端末上のアプリの数が多すぎることを示唆しています。 このエラーは、アプリの数が極端に多い場合にのみ発生するため、平均的なユーザーには影響しません。
     ${app_name}はGoogle Playサービスを使用してプッシュメッセージを配信していますが、正しく設定されていないようです:
 \n%1$s
     FCMトークンの取得に失敗しました:
@@ -1582,7 +1582,7 @@
     Eメールで招待
     詳細
     連絡先を招待
-    とにかく参加
+    無視して参加
     ルームを追加
     %sはあなたを招待しています
     このルームでグループ通話を開始する権限がありません
@@ -1718,7 +1718,7 @@
     
     既に一覧に載っているサーバーです
     サーバーまたはそのルーム一覧が見つかりません
-    検索される新しいサーバーの名前を入力します。
+    検索したい新しいサーバーの名前を入力してください。
     新しいサーバーを追加
     あなたのサーバー
     暗号化されたメッセージの復元
@@ -1788,7 +1788,7 @@
     次に
     ユーザー名で招待
     ユーザー名を選択してください。
-    ユーザー名やパスワードが正しくありません。 入力したパスワードは、スペースで開始または終了しますので、ご確認ください。
+    ユーザー名やパスワードが正しくありません。 入力したパスワードは、スペースで開始または終了していますので、ご確認ください。
     そのユーザー名は既に使用されています
     ユーザー名
     ユーザー名または電子メール
@@ -1883,7 +1883,7 @@
     ルームに参加してアプリの使用を開始します。
     リトライ
     他のホームサーバーに接続しようとしているようですね。サインアウトしますか?
-    IDサーバーが設定されていない場合は、パスワードの再設定が必要となります。
+    IDサーバーが設定されていない場合は、パスワードを再設定する必要があります。
     IDサーバーを使用していません
     不明なエラー
     ユーザーの不一致
@@ -1920,7 +1920,7 @@
     検証を開始します
     最大限のセキュリティーを確保するために、これを行うか、別の信頼できる通信手段を用いることをお勧めします。
     短い文字列を比較して検証します。
-    認証が無効または期限切れのため、ログアウトされました。
+    認証情報が不正または期限切れのため、ログアウトされました。
     構成を使用
     ${app_name}がuserIdドメイン\"%1$s\"のカスタムサーバー構成を検出しました。
 \n%2$s
@@ -1947,7 +1947,7 @@
     デフォルトの圧縮
     ルームのアップグレード
     ボットによるメッセージ
-    ルーム招待
+    ルームへの招待
     \@roomを含むメッセージ
     ルームがアップグレードされたとき
     暗号化されたグループメッセージ
@@ -1963,10 +1963,10 @@
     メンションとキーワード
     通知のデフォルト
     
-        %d ビデオ通話できません
+        %d個の不在着信(ビデオ)
     
     
-        %d 通話できません
+        %d個の不在着信(音声)
     
     デフォルトで使いもう尋ねない
     鍵の共有リクエストの履歴を送信
@@ -1975,8 +1975,8 @@
     ミーティングはJitsiのセキュリティーとパーミッションポリシーを使用します。会議中は、現在ルームにいる全ての人に招待状が表示されます。
     権限がありません
     音声メッセージを送信するには、マイクの権限を許可してください。
-    この動作を行うには、システム設定からカメラの権限を許可してください。
-    このアクションを実行するには、いくつかの権限が不足しています。システム設定から権限を付与してください。
+    この操作を実行するには、システム設定からカメラの権限を許可してください。
+    この操作を実行するための権限が不足しています。システム設定から権限を付与してください。
     IDサーバーに接続できませんでした
     IDサーバーのURLを入力
     連絡先を発見するために、あなたの連絡先のデータ(電話番号や電子メール)を、設定されたIDサーバー(%1$s)に送信することを承認しますか?
@@ -2037,9 +2037,9 @@
     アドレス
     継続
     ファイル
-    このユーザーはスペースから除去されます。
+    このユーザーはスペースから追放されます。
 \n
-\n再参加を防ぐためには、除去する代わりにブロックする必要があります。
+\n再参加を防ぐためには、追放する代わりにブロックする必要があります。
     通話を終了しています…
     通知を待機しています
     \@room
@@ -2056,13 +2056,13 @@
     %sとのビデオ通話
     呼び出しています…
     ホームサーバーを選択
-    %sのURLにあるホームサーバーに接続できません。リンクをチェックするか、手動でホームサーバーを選択してください。
+    %sのURLにあるホームサーバーに接続できません。リンクを確認するか、手動でホームサーバーを選択してください。
     後で
     スペース
     スレッドから
     参加している全スレッドを表示
     現在のルームのスレッドを全て表示
-    ユーザーをブロックすると、ユーザーはこのスペースから削除され、二度と参加できなくなります。
+    ユーザーをブロックすると、ユーザーはこのスペースから追放され、二度と参加できなくなります。
     PINコードを有効にする
     これを招待者のみ参加可能に設定しました。
     ルームの設定
@@ -2076,7 +2076,7 @@
     全てのスレッド
     ユーザーを自動的に招待
     ユーザー
-    このユーザーのブロックを解除すると、そのユーザーはスペースに再び参加できるようになります。
+    ブロックを解除すると、ユーザーはスペースに再び参加できるようになります。
     このルームを招待者のみ参加可能に設定しました。
     低優先度から削除
     低優先度に追加
@@ -2202,8 +2202,8 @@
     スペースのアバターの変更
     投票を作成
     投票を作成
-    暗号化が正しく設定されていないためメッセージを送ることができません。クリックして設定を開いてください。
-    暗号化が正しく設定されていないためメッセージを送ることができません。管理者に連絡して、暗号化を正しい状態に復元してください。
+    暗号化が正しく設定されていないため、メッセージを送ることができません。クリックして設定を開いてください。
+    暗号化が正しく設定されていないため、メッセージを送ることができません。管理者に連絡して、暗号化を正しい状態に復元してください。
     %2$dの%1$d
     あなたは既にこのスレッドを見ています!
     ルームに表示
@@ -2303,9 +2303,9 @@
     PINコードを変更
     PINコードと生体認証でアクセスを保護できます。
     アクセスを保護
-    PINコードをリセットするには、再ログインして新しいコードを作成してください。
+    PINコードを再設定するには、再ログインして新しいコードを作成してください。
     新しいPINコード
-    PINコードをリセット
+    PINコードを再設定
     PINコードを忘れましたか?
     PINコードを入力
     PINコードを確認
@@ -2515,7 +2515,7 @@
     Elementの使用に関するヘルプ
     詳細なログは、イライラシェイクでログを送信する際に、より多くのログを提供することで、開発者にとっての助けになります。有効にした場合でも、メッセージの内容やその他のプライベートな情報は記録されません。
     ルームのアップグレードは高度な作業であり、不具合や欠けている機能、セキュリティー上の脆弱性がある場合に推奨されます。
-\nアップグレードは普通、ルームがサーバー上で処理される仕方にだけ影響します。
+\nアップグレードは通常、ルームがサーバー上で処理される仕方にだけ影響します。
     一度有効にしたルームの暗号化は無効にすることはできません。暗号化されたルームで送信されたメッセージは、サーバーからは見ることができず、そのルームのメンバーだけが見ることができます。暗号化を有効にすると、多くのボットやブリッジが正常に動作しなくなる場合があります。
     %sして、このルームを皆に紹介しましょう。
     このコードを皆と共有し、スキャンして追加してもらい、会話を始めましょう。
@@ -2571,7 +2571,7 @@
     どこかのホームサーバーで既にアカウントを登録している場合、以下でMatrix ID(例:@user:domain.com)とパスワードを使用してください。
     入力したコードが正しくありません。確認してください。
     これは正しいユーザー識別子ではありません。正しいフォーマットは「@user:homeserver.org」です。
-    パスワードをお忘れの場合、戻ってパスワードをリセットしてください。
+    パスワードをお忘れの場合、戻ってパスワードを再設定してください。
     メールボックスを確認してください
     カスタムサーバーに接続
     既にアカウントを持っています
@@ -2589,8 +2589,8 @@
     電話番号(任意)
     電話番号を確認
     電話番号を設定
-    パスワードをリセットしました。
-    %1$sでパスワードをリセット
+    パスワードを再設定しました。
+    %1$sでパスワードを再設定
     Element Matrix Servicesのアドレス
     %1$sにサインイン
     誰と最もよく会話しますか?
@@ -2720,7 +2720,7 @@
 \nこの端末での使用を終了したり、他のアカウントにサインインしたい場合、このデータをクリアしてください。
     この端末に現在保存されているすべてのデータをクリアしますか?
 \nアカウントデータとメッセージにアクセスするにはもう一度サインインしてください。
-    現在のセッションはユーザー %1$s のものですが、あなたが提供している資格情報はユーザー %2$s のものです。この操作は${app_name}ではサポートされていません。
+    現在のセッションはユーザー %1$s のものですが、あなたが提供している認証情報はユーザー %2$s のものです。この操作は${app_name}ではサポートされていません。
 \nまずデータをクリアし、その後、別のアカウントにサインインしてください。
     暗号化されたメッセージがどの端末でも読めるように、サインインしてこの端末にのみ保存されている暗号鍵を取り戻してください。
     あなたのホームサーバー(%1$s)の管理者があなたを%2$sのアカウントからサインアウトしています。(%3$s)
@@ -2745,7 +2745,7 @@
 \n
 \n登録を中止しますか?
     %1$sにアカウント登録
-    あなたはすべてのセッションからログアウトしており、これ以上プッシュ通知を受け取れません。通知をもう一度有効にするには、各端末でサインインしてください。
+    あなたはすべてのセッションからログアウトしており、これ以上プッシュ通知を受け取れません。通知を再び有効にするには、各端末でサインインしてください。
     セキュリティーフレーズを設定
     セキュリティーフレーズを使用
     セキュリティーキーは、パスワードマネージャーもしくは金庫のような安全な場所で保管してください。
@@ -2805,4 +2805,9 @@
     対面でない場合は、代わりに絵文字を比較してください
     あなたのホームサーバーは、最大%sのサイズの添付ファイルを受け付けています。
     新しいパスワードを確認するには下記のリンクを開いてください。リンク先のリンクにアクセスしてから、この下をクリックしてください。
+    PINコードを再設定するには「PINコードを忘れた」をタップしてログアウトし、その後再設定してください。
+    
+        いま検証できる%d個の端末を表示
+    
+    この操作を実行するには ${app_name}に認証情報を入力する必要があります。
 
\ No newline at end of file

From 2f7856e521ad50d5cc6440160854cc26bcfb2ea6 Mon Sep 17 00:00:00 2001
From: Joe Sagawa 
Date: Mon, 21 Feb 2022 11:18:11 +0000
Subject: [PATCH 522/581] Translated using Weblate (Japanese)

Currently translated at 91.4% (2546 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index c43dfd3d101..44368675b9a 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -1944,7 +1944,7 @@
     スペース %s のメンバーが検索、プレビュー、参加できます。
     非公開(招待のみ)
     デフォルトのメディアソース
-    デフォルトの圧縮
+    規定圧縮率
     ルームのアップグレード
     ボットによるメッセージ
     ルームへの招待

From f9be66d017581527a682b7557f9fce12e88dc163 Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Mon, 21 Feb 2022 11:17:15 +0000
Subject: [PATCH 523/581] Translated using Weblate (Japanese)

Currently translated at 91.4% (2546 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 42 +++++++++++------------
 1 file changed, 21 insertions(+), 21 deletions(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 44368675b9a..226b1e421ea 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -276,7 +276,7 @@
     ログイン中のアカウント
     言語を選択
     言語
-    端末操作表示
+    インターフェース
     認証を確認中
     電子メールを確認して、本文中のURLをクリックしてください。 完了したら「続ける」をクリックしてください。
     このメールアドレスは既に使われています。
@@ -398,7 +398,7 @@
     パスワードを再設定するには、アカウントに登録されているメールアドレスを入力してください:
     アカウントに登録されたメールアドレスの入力が必要です。
     新しいパスワードの入力が必要です。
-    %sへ電子メールが送信されました。リンクをたどったら以下をクリックしてください。
+    %sへ電子メールが送信されました。リンクにアクセスしてから、以下をクリックしてください。
     メールアドレスの確認に失敗しました:電子メールのリンクをクリックしたことを確認してください
     パスワードがリセットされました。
 \n
@@ -560,7 +560,7 @@
     不具合の報告
     開封確認メッセージのリスト
     ダウンロードフォルダーに保存しますか?
-    この招待は、このアカウントに関連付けられていない%sに送信されました。
+    この招待は、このアカウントに登録されていない%sに送信されました。
 \n別のアカウントでログインするか、このメールを自分のアカウントに追加してください。
     %sにアクセスしようとしています。ディスカッションに参加しますか?
     ルーム
@@ -604,7 +604,7 @@
     未読のあるルームを固定
     分析
     データ節約モード
-    メールアドレスを認証できません。メールを確認して、そこに記載してあるリンクをクリックしてください。その後、「続ける」をクリックしてください。
+    メールアドレスを認証できません。メールを確認して、記載されているリンクをクリックしてください。その後、「続ける」をクリックしてください。
     この通知の対象を削除してよろしいですか?
     %1$s %2$sを削除してよろしいですか?
     コード
@@ -774,12 +774,12 @@
     アカウントを停止
     自分のアカウントを停止
     通知のプライバシー
-    ${app_name}は通知を安全で内密に扱うため、バックグラウンドで動作できます。これによりバッテリー使用に影響が出ることがあります。
+    ${app_name}はバックグラウンドで動作し、通知を安全で内密に扱います。これによりバッテリー使用に影響が出ることがあります。
     許可を与える
     他のオプションを選択
-    解析データを送信
-    ${app_name}はアプリを改善するため、匿名の解析データを収集します。
-    ${app_name}を改善するのを助けるため、解析を許可してください。
+    分析データを送信
+    ${app_name}はアプリを改善するため、匿名の分析データを収集します。
+    分析を有効にして、${app_name}の改善を手伝ってくれませんか?
     はい、手伝いたいです!
     あなたは現在どのコミュニティーのメンバーでもありません。
     ここに入力…
@@ -1081,7 +1081,7 @@
     あなたのアカウントに追加されたメールアドレスはありません
     あなたのアカウントに追加された電話番号はありません
     電話番号
-    あなたの Matrix アカウントに関連付けられたメールアドレスと電話番号を管理
+    あなたのMatrixアカウントに登録されたメールアドレスと電話番号を管理
     メールアドレスと電話番号
     有効化
     このセッションで通知が無効化されています。
@@ -1178,10 +1178,10 @@
     無効なQRコード(無効なURI)!
     パスワードが一致しません
     メールアドレスの確認中にエラーが発生しました。
-    これを行うには設定からインテグレーションを許可を有効にしてください。
+    これを行うには設定から「インテグレーションを許可」を有効にしてください。
     インテグレーションが無効になっています
     インテグレーションマネージャー
-    インテグレーションを許可
+    インテグレーション(統合)を許可
     データ節約モードでは、特定のフィルターを適用することで、プレゼンスの更新やタイピングの通知がフィルタリングされます。
     FCMトークンが正常に取得されました:
 \n%1$s
@@ -1448,14 +1448,14 @@
     ルーム名を削除しました
     許可を与える
     ${app_name}は、信頼できる通知を受け取るために、影響の少ないバックグラウンド接続を維持する必要があります。
-\n次の画面で、${app_name}を常にバックグラウンドで実行することを許可するように求められます。承認してください。
+\n次の画面で、${app_name}を常にバックグラウンドで実行することを許可するように求められますので、承認してください。
     バックグラウンド接続
     ディスカバリー設定を管理します。
     ディスカバリー(発見)
     これにより、現在のキーまたはフレーズが置き換えられます。
     新しいセキュリティーキーを生成するか、既存のバックアップに新しいセキュリティーフレーズを設定します。
-    サーバー上の暗号鍵をバックアップすることにより、暗号化されたメッセージとデータへのアクセスが失われるのを防ぎます。
-    メッセージ作成画面に絵文字キーボードを開くボタンを追加
+    サーバー上の暗号鍵をバックアップすることにより、暗号化されたメッセージとデータへのアクセスが失われるのを予防します。
+    メッセージ作成画面に絵文字キーボードを開くためのボタンを追加
     絵文字キーボードを表示
     アバターと表示名の変更を含む。
     アカウントのイベントを表示
@@ -2312,9 +2312,9 @@
     ユーザーのロケーションをタイムラインに表示
     一度有効にすると、ロケーションはどのルームにも送れるようになります
     サードパーティー製ライブラリー
-    いつでも設定から無効にできます
-    私たちは情報を第三者と共有することはありません
-    私たちはアカウントのデータを記録したり分析したりすることはありません
+    これはいつでも設定から無効にできます
+    私たちは、情報を第三者と共有することはありません
+    私たちは、アカウントのデータを記録したり分析したりすることはありません
     Elementの改善を手伝う
     リンクを知っている人がアクセスできるようにこのルームを設定しました。
     どのユーザーも無視していません
@@ -2628,7 +2628,7 @@
     国際電話番号の形式を使用してください。
     国際電話番号は「+」から始まる必要があります
     コードを%1$sに送信しました。以下に入力して認証してください。
-    このメールアドレスはどのアカウントにも属していません
+    このメールアドレスはどのアカウントにも登録されていません
     パスワードを変更すると、すべてのセッションでのエンドツーエンド暗号鍵がリセットされ、暗号化されたメッセージ履歴が読めなくなります。パスワードを再設定する前に、鍵のバックアップを設定するか、他のセッションから鍵をエクスポートしておいてください。
     パスワードの再設定を確認するために認証メールを送信しました。
     このメールアドレスはどのアカウントにも属していません。
@@ -2696,7 +2696,7 @@
     セキュリティーキーを保存
     リカバリーキー
     位置を共有しました
-    %sとリアクションしました
+    %sでリアクションしました
     検証終了
     次のいずれかのセキュリティが破られている可能性があります。
 \n
@@ -2710,7 +2710,7 @@
     次の絵文字が相手の画面にも同じ順番で現れるのを確認し、このユーザーを検証してください。
     信頼できないサインイン
     使用できない文字が含まれています
-    Elementの改善と課題抽出のために、匿名の使用分析データを収集させてくださいませんか?複数の端末での使用を解析するために、あなたの全端末共通のランダムな識別子を生成します。
+    Elementの改善と課題抽出のために、匿名の使用分析データを収集させてくださいませんか?複数の端末での使用を分析するために、あなたの全端末共通のランダムな識別子を生成します。
 \n
 \n%sで利用規約を閲覧できます。
     最初の検索結果のみ表示中。文字をもっと入力してください…
@@ -2804,7 +2804,7 @@
     絵文字を比較して検証
     対面でない場合は、代わりに絵文字を比較してください
     あなたのホームサーバーは、最大%sのサイズの添付ファイルを受け付けています。
-    新しいパスワードを確認するには下記のリンクを開いてください。リンク先のリンクにアクセスしてから、この下をクリックしてください。
+    新しいパスワードを確認するには下記のリンクを開いてください。リンクにアクセスしてから、以下をクリックしてください。
     PINコードを再設定するには「PINコードを忘れた」をタップしてログアウトし、その後再設定してください。
     
         いま検証できる%d個の端末を表示

From 0dd9809166bc4aea4f8f0118f43e5c22eaf9d671 Mon Sep 17 00:00:00 2001
From: Joe Sagawa 
Date: Mon, 21 Feb 2022 11:18:34 +0000
Subject: [PATCH 524/581] Translated using Weblate (Japanese)

Currently translated at 91.4% (2546 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 226b1e421ea..d48027a6f91 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -1943,7 +1943,7 @@
     スペースのメンバーに発見とアクセスを許可します。
     スペース %s のメンバーが検索、プレビュー、参加できます。
     非公開(招待のみ)
-    デフォルトのメディアソース
+    規定メディアソース
     規定圧縮率
     ルームのアップグレード
     ボットによるメッセージ

From f3734bbb5340584155d799c3a8839f5e32fd26c2 Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Mon, 21 Feb 2022 11:18:21 +0000
Subject: [PATCH 525/581] Translated using Weblate (Japanese)

Currently translated at 91.4% (2546 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index d48027a6f91..2a57b5752f8 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -1944,7 +1944,7 @@
     スペース %s のメンバーが検索、プレビュー、参加できます。
     非公開(招待のみ)
     規定メディアソース
-    規定圧縮率
+    既定の圧縮率
     ルームのアップグレード
     ボットによるメッセージ
     ルームへの招待

From 65239e403489f2439c67ff4759877c93390079ce Mon Sep 17 00:00:00 2001
From: Adam Brown 
Date: Mon, 21 Feb 2022 12:04:02 +0000
Subject: [PATCH 526/581] relying on the glide caches (persisted and in memory)
 instead of our own - this effectively halves the amount of image memory used
 by notifications

---
 .../vector/app/features/notifications/BitmapLoader.kt  | 10 +---------
 .../im/vector/app/features/notifications/IconLoader.kt |  9 +--------
 2 files changed, 2 insertions(+), 17 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/notifications/BitmapLoader.kt b/vector/src/main/java/im/vector/app/features/notifications/BitmapLoader.kt
index d1c4624cdc1..da0f071841a 100644
--- a/vector/src/main/java/im/vector/app/features/notifications/BitmapLoader.kt
+++ b/vector/src/main/java/im/vector/app/features/notifications/BitmapLoader.kt
@@ -28,11 +28,6 @@ import javax.inject.Singleton
 @Singleton
 class BitmapLoader @Inject constructor(private val context: Context) {
 
-    /**
-     * Avatar Url -> Bitmap
-     */
-    private val cache = HashMap()
-
     /**
      * Get icon of a room.
      * If already in cache, use it, else load it and call BitmapLoaderListener.onBitmapsLoaded() when ready
@@ -42,10 +37,7 @@ class BitmapLoader @Inject constructor(private val context: Context) {
         if (path == null) {
             return null
         }
-
-        return cache.getOrPut(path) {
-            loadRoomBitmap(path)
-        }
+        return loadRoomBitmap(path)
     }
 
     @WorkerThread
diff --git a/vector/src/main/java/im/vector/app/features/notifications/IconLoader.kt b/vector/src/main/java/im/vector/app/features/notifications/IconLoader.kt
index 3e68744c886..ec53e89d573 100644
--- a/vector/src/main/java/im/vector/app/features/notifications/IconLoader.kt
+++ b/vector/src/main/java/im/vector/app/features/notifications/IconLoader.kt
@@ -30,11 +30,6 @@ import javax.inject.Singleton
 @Singleton
 class IconLoader @Inject constructor(private val context: Context) {
 
-    /**
-     * Avatar Url -> IconCompat
-     */
-    private val cache = HashMap()
-
     /**
      * Get icon of a user.
      * If already in cache, use it, else load it and call IconLoaderListener.onIconsLoaded() when ready
@@ -46,9 +41,7 @@ class IconLoader @Inject constructor(private val context: Context) {
             return null
         }
 
-        return cache.getOrPut(path) {
-            loadUserIcon(path)
-        }
+        return loadUserIcon(path)
     }
 
     @WorkerThread

From af39632d3ec976d4c6123f880ed4d3745dc7b6a9 Mon Sep 17 00:00:00 2001
From: Linerly 
Date: Mon, 21 Feb 2022 07:59:03 +0000
Subject: [PATCH 527/581] Translated using Weblate (Indonesian)

Currently translated at 100.0% (2785 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/id/
---
 vector/src/main/res/values-in/strings.xml | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/vector/src/main/res/values-in/strings.xml b/vector/src/main/res/values-in/strings.xml
index 5677c283a45..78c5b2708c1 100644
--- a/vector/src/main/res/values-in/strings.xml
+++ b/vector/src/main/res/values-in/strings.xml
@@ -382,7 +382,7 @@ Ijinkan akses lewat halaman selanjutnya untuk menemukan pengguna ${app_name} yan
     Keluar
     Abaikan
     Sidik jari (%s):
-    Tidak dapat memastikan identitas remote server.
+    Tidak dapat memastikan identitas server jarak jauh.
     Ini dapat terjadi ketika seseorang sedang mensabotase arus data Anda, atau perangkat Anda tidak percaya terhadap sertifikat remote server.
     Apabila administrator server telah menyatakan ini memang akan terjadi, pastikan sidik jari berikut cocok dengan sidik jari yang mereka sediakan.
     Sertifikat ini tidak lagi sesuai dengan yang dipercayai oleh perangkat Anda sebelumnya. Ini SANGAT JANGGAL. Kami rekomendasikan Anda untuk TIDAK MENERIMA sertifikat baru ini.
@@ -634,7 +634,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
     Ini adalah fitur uji coba dan mungkin rusak tanpa terduga. Hati-hati menggunakannya.
     Enkripsi Ujung-ke-Ujung
     Enkripsi Ujung-ke-Ujung aktif
-    Anda perlu keluar dulu untuk mengaktifkan enkripsi.
+    Anda perlu keluar terlebih dahulu untuk mengaktifkan enkripsi.
     Enkripsi ke perangkat terverifikasi saja
     Jangan mengirim pesan terenkripsi ke perangkat yang belum diverifikasi di ruangan ini dengan perangkat ini.
     Ruangan ini tidak punya alamat lokal
@@ -1462,7 +1462,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
     Perubahan siapa yang dapat membaca riwayat hanya akan berlaku untuk pesan berikutnya di ruangan ini. Visibilitas riwayat yang ada tidak akan berubah.
     Pengaturan akun
     Anda dapat mengelola notifikasi di %1$s.
-    Harap dicatat bahwa sebutan & pemberitahuan keyword tidak tersedia di ruangan terenkripsi di ponsel.
+    Harap dicatat bahwa pemberitahuan sebutan & kata kunci tidak tersedia di ruangan terenkripsi di ponsel.
     Beritahu saya untuk
     Putar suara rana
     Pilih
@@ -1825,7 +1825,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
 \n%s
     Permintaan Dibatalkan
     Kunci Verifikasi
-    Gunakan verifikasi legacy.
+    Gunakan verifikasi lama.
     Tidak ada yang muncul\? Belum semua client mendukung verifikasi interaktif. Gunakan verifikasi legacy.
     Saya mengerti
     Pesan aman dengan pengguna ini dienkripsi ujung-ke-ujung dan tidak dapat dibaca oleh pihak ketiga.
@@ -2324,7 +2324,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
     Panggilan grup dimulai
     Maaf, sebuah kesalahan terjadi saat bergabung: %s
     Tingkatkan ke versi ruangan yang disarankan
-    Kamar ini menjalankan versi %s, yang ditandai oleh homeserver ini sebagai tidak stabil.
+    Ruangan ini menjalankan versi %s, yang ditandai oleh homeserver ini sebagai tidak stabil.
     Izinkan siapa saja di %s untuk mencari dan mengakses. Anda dapat memilih space yang lain juga.
     Anda membutuhkan izin untuk meningkatkan sebuah ruangan
     Otomatis memperbarui induk space
@@ -2375,7 +2375,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
     Tinggalkan semua ruangan dan space
     Anda adalah admin satu-satunya di space ini. Meninggalkannya berarti siapa saja tidak akan mempunyai kontrol atas space-nya.
     Anda tidak akan dapat bergabung lagi kecuali jika Anda diundang lagi.
-    Anda hanya salah satu orang di sini. Jika Anda tinggalkan, siapa saja tidak dapat bergabung di masa depan, termasuk Anda.
+    Anda orang satu-satunya di sini. Jika Anda tinggalkan, siapa saja tidak dapat bergabung di masa depan, termasuk Anda.
     Apakah Anda yakin untuk meninggalkan %s\?
     Tinggalkan Space
     Tambahkan ruangan
@@ -2693,9 +2693,9 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
     Gunakan File
     Masukkan %s Anda untuk melanjutkan
     Verifikasi diri Anda dan lainnya untuk tetap membuat pesan Anda aman
-    Aktifkan Tanda Tangan Silang
+    Aktifkan Penandatanganan Silang
     Peningkatan enkripsi tersedia
-    Pesan…
+    Kirim pesan…
     Akun ini telah dinonaktifkan.
     Nama pengguna dan/atau kata sandi salah. Kata sandi dimulai atau berakhir dengan spasi, mohon dicek.
     Mengirim pesan sebagai teks biasa, tanpa mengimpretasikannya sebagai markdown

From cbad7de1c86ab59a92a52db75bc402c27cba8c3e Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Mon, 21 Feb 2022 12:09:09 +0000
Subject: [PATCH 528/581] Translated using Weblate (Japanese)

Currently translated at 91.3% (2545 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 112 +++++++++++-----------
 1 file changed, 56 insertions(+), 56 deletions(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 2a57b5752f8..350ccedc385 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -254,7 +254,7 @@
     通知
     無視しているユーザー
     その他
-    拡張設定
+    高度な設定
     暗号
     通知対象
     端末の電話帳
@@ -267,7 +267,7 @@
     ID(端末固有番号)
     公開端末名
     公開端末名の更新
-    最終接続日
+    最後のオンライン日時
     %1$s @ %2$s
     認証
     この操作には追加の認証が必要です。
@@ -301,7 +301,7 @@
     お気に入り
     低優先度
     なし
-    参加と可視範囲
+    アクセスと可視範囲
     ルームディレクトリへ公開
     ルームへのアクセス
     ルームの履歴の可視範囲
@@ -315,24 +315,24 @@
     ルームのリンクを知っている人なら誰でも(ゲストユーザーを除く)
     ルームのリンクを知っている人なら誰でも(ゲストユーザーを含む)
     ブロックされたユーザー
-    拡張設定
+    高度な設定
     このルームのサーバー内識別ID
     ラボ
-    これらは予期せぬ不具合が生じるかもしれない実験的機能です。慎重に使用してください。
+    これらは予期しない不具合が生じるかもしれない実験的機能です。慎重に使用してください。
     エンドツーエンド暗号化
     エンドツーエンド暗号化を使用中
-    暗号を有効にするためにはログアウトする必要があります.
+    暗号化を有効にするためにはログアウトする必要があります.
     認証済のセッションに対してのみ暗号化
-    このセッションでは、このルームの未検証のセッションに対して暗号化されたメッセージを送信しない。
+    このセッションから、このルームの未認証のセッションに対して、暗号化されたメッセージを送信しない。
     新しいアドレス (記入例 #foo:matrix.org)
     このルームにはローカルアドレスがありません
-    住所表記
+    アドレス
     エイリアスのフォーマットが正しくありません
     \'%s\'はエイリアスの正しいフォーマットではありません
     このルームのメインアドレスが設定されていません。
     メインアドレスの警告
     メインアドレスとして設定
-    メインアドレスとして設定を解除
+    メインアドレスとしての設定を解除
     ルーム固有IDをコピー
     ルームのアドレスをコピー
     セッションID
@@ -484,11 +484,11 @@
     エンドツーエンド暗号化についての情報
     公開端末名
     ルームのエンドツーエンド暗号鍵をエクスポート
-    認証
+    認証済
     履歴を検索
     このルームに参加していません。
     このルームで権限がありません。
-    ルーム %s は、見ることができません。
+    ルーム %s は閲覧できません。
     ユーザー名
     ホームサーバーのURL
     IDサーバーのURL
@@ -524,20 +524,20 @@
     要求を無視
     検証せずに共有
     検証を開始
-    リクエストに user_id がありません。
-    リクエストに room_id がありません。
+    リクエストにuser_idがありません。
+    リクエストにroom_idがありません。
     リクエストの送信に失敗しました。
     ウィジェットを作成できません。
     このルームのウィジェットを管理する権限が必要です
     ウィジェットの作成に失敗しました
     ウィジェットをこのルームから削除してもよろしいですか?
     サーバーが利用できないか、オーバーロードしている可能性があります
-    このルームは検証されていない不明なセッションが含まれています。
-\nこれは、そのセッションが主張するユーザーのものであるという保証がないことを意味します。
-\n続行する前に、各セッションの検証プロセスを進めることをおすすめしますが、検証せずにメッセージを再送信することもできます。
+    このルームに、検証されていない不明なセッションがあります。
+\nそのセッションが、主張するユーザーのものであるという保証はありません。
+\n続行する前に、各セッションの検証プロセスを進めることを推奨しますが、検証せずにメッセージを再送信することもできます。
 \n
 \n不明なセッション:
-    ルームに不明なセッションが含まれています
+    ルームに不明なセッションがあります
     鍵が一致していることを確認
     一致していない場合は、あなたのコミュニケーションの安全性が損なわれている可能性があります。
     セッションを認証
@@ -608,7 +608,7 @@
     この通知の対象を削除してよろしいですか?
     %1$s %2$sを削除してよろしいですか?
     コード
-    %sはこのルームのタイムラインのある箇所を読み込もうとしましたが、見つかりませんでした。
+    %sはこのルームのタイムライン上の特定の箇所を読み込もうとしましたが、見つかりませんでした。
     イベント情報
     ユーザーID
     Curve25519 固有鍵
@@ -624,7 +624,7 @@
     なし
     認証する
     認証を取り消す
-    ブラックリスト
+    ブラックリストに追加
     ブラックリストから除外
     他のセッションのユーザー設定で、以下を比較して確認してください:
     ルームのディレクトリを選択
@@ -635,10 +635,10 @@
     メッセージが未送信です。今%1$sまたは%2$sしますか?
     不明なセッションが存在しているため、メッセージを送ることができませんでした。今%1$sまたは%2$sしますか?
     要求されたフィンガープリントキー Ed25519
-    jitsiを用いて会議通話を始める
+    Jitsiを用いて会議通話を始める
     端末のカメラを使用
     警告!
-    ビデオ会議は開発中であり、確実でない可能性があります。
+    ビデオ会議は開発中であり、安定して動作しない可能性があります。
     コマンドエラー
     認識されないコマンド:%s
     
@@ -663,9 +663,9 @@
     ホーム画面にショートカットを作成
     インラインURLプレビュー
     通知
-    このルームはコミュニティーの特色を表示していません
-    所属するコミュニティー
-    新しいコミュニティーID (記入例 +foo:matrix.org)
+    このルームはコミュニティーのアバターを表示していません
+    コミュニティーのアバター
+    新しいコミュニティーID(記入例 +foo:matrix.org)
     無効なコミュニティーID
     \'%s\' は有効なコミュニティーIDではありません
     ルームのエンドツーエンド暗号鍵は \'%s\' に保存されました。
@@ -790,7 +790,7 @@
         %d件の通知された未読メッセージ
     
     
-        %dルーム
+        %d個のルーム
     
     %2$s件中%1$s件
     
@@ -799,7 +799,7 @@
     必要な変数が見つかりません。
     変数が無効です。
     メッセージを送信するには、キーボードのEnterキーを押してください
-    ボイスメッセージを送信
+    音声メッセージを送信
     動作を表示
     指定したIDのユーザーをブロック
     指定したIDのユーザーのブロックを解除
@@ -843,8 +843,8 @@
     あなたのサービス管理者に連絡
     このホームサーバーはリソース制限の1つを超過しているため、 ユーザーがログインできなくなることがあります
     このホームサーバーはリソース制限の1つを超過しています。
-     このホームサーバーは月間アクティブユーザー上限に達しているため、 ユーザーがログインできなくなることがあります
-    このホームサーバーは月間アクティブユーザー上限に達しています。
+     このホームサーバーは月間アクティブユーザーの上限に達しているため、 ユーザーがログインできなくなることがあります
+    このホームサーバーは月間アクティブユーザーの上限に達しています。
     この上限を上げるには%sしてください。
     このサービスを使い続けるには%sしてください。
     最初にルームのメンバーのみを読み込むことでパフォーマンスを向上。
@@ -852,7 +852,7 @@
     ルームのメンバーの簡易読み込み
     申し訳ありません、エラーが発生しました
     バージョン %s
-    エクスポートされた鍵を暗号化するパスフレーズを作成してください。 鍵をインポートするには、同じパスフレーズを入力する必要があります。
+    エクスポートされた鍵を暗号化するパスフレーズを作成してください。 鍵をインポートするには、同一のパスフレーズを入力する必要があります。
     パスフレーズの作成
     パスフレーズは一致する必要があります
     情報領域を表示
@@ -1447,7 +1447,7 @@
     ルームのトピックを削除しました
     ルーム名を削除しました
     許可を与える
-    ${app_name}は、信頼できる通知を受け取るために、影響の少ないバックグラウンド接続を維持する必要があります。
+    ${app_name}は、確実に通知を受け取るために、影響の少ないバックグラウンド接続を維持する必要があります。
 \n次の画面で、${app_name}を常にバックグラウンドで実行することを許可するように求められますので、承認してください。
     バックグラウンド接続
     ディスカバリー設定を管理します。
@@ -1553,18 +1553,18 @@
     ウィジェットID
     あなたのテーマ
     あなたのユーザーID
-    あなたのアバターURL
+    あなたのアバターのURL
     ブラウザーで開く
-    ウィジェットをロード
+    ウィジェットを読み込む
     ウィジェット
-    アクティブなウィジェット
+    使用中のウィジェット
     自分
     新しいメッセージ
     ルーム
     新しいイベント
     不明なIP
     
-        キー%1$dと%2$dのインポートに成功。
+        %2$d個の鍵のうち%1$d個のインポートに成功。
     
     鍵のバックアップを管理
     鍵のエクスポートに成功しました
@@ -1573,7 +1573,7 @@
     詳細情報:%s
     ローカルアドレスを追加
     このルームにはローカルアドレスがありません
-    アドレス \"%1$s\" を削除しますか?
+    アドレス\"%1$s\"を削除しますか?
     メインアドレス
     これがメインアドレスです
     電話番号の認証中にエラーが発生しました。
@@ -1679,29 +1679,29 @@
     この機能はメッセージを録音するために第三者のアプリを必要とします。
     新しいセッションが暗号鍵を要請しています。
 \nセッション名:%1$s
-\n最後のオンライン時刻:%2$s
+\n最後のオンライン日時:%2$s
 \n新たにログインして新しいセッションを開始しなかった場合、この要求を無視してください。
     未認証のセッションが暗号鍵を要請しています。
 \nセッション名:%1$s
-\n最後のオンライン時刻:%2$s
+\n最後のオンライン日時:%2$s
 \n新たにログインして新しいセッションを開始しなかった場合、この要求を無視してください。
     鍵の共有リクエスト
     カスタムカメラ画面の代わりにシステムカメラを使用します。
     使用中のウィジェットがありません
     インテグレーションを管理
-    インテグレーション管理者が設定されていません。
-    DRM保護されているメディアの読込
+    インテグレーションマネージャーが設定されていません。
+    DRM保護されているメディアを読み込む
     マイクの使用
     カメラの使用
     このウィジェットは次のリソースの使用を要求します:
-    現在の会議から退出しもう一つの会議に参加しますか?
+    現在の会議から退出し、もう一つの会議に参加しますか?
     申し訳ありませんが、ビデオ会議に参加する途中で問題が発生しました
-    申し訳ありませんが、古い端末(Android OS 6.0以前)はJitsiを使用したビデオ会議がサポートされていません
+    申し訳ありませんが、古い端末(Android OS 6.0以前)はJitsiを使用したビデオ会議をサポートしていません
     あなたの表示名
-    ウィジェットの読込に失敗しました。
+    ウィジェットの読み込みに失敗しました。
 \n%s
-    ウィジェットを再読込
-    クッキーを設定し%sとデータを交換する可能性があります:
+    ウィジェットを再読み込み
+    これを使用すると、クッキーが設定され、データが%sと共有される可能性があります:
     ウィジェットの追加者:
     **送信に失敗 - ルームを開いてください
     新しい招待
@@ -1727,7 +1727,7 @@
     
         ブロックされたユーザー%d人
     
-    このルームのあるスペースのメンバーは誰でも発見し参加できます。ルームをスペースに追加できるのは、ルームの管理者だけです。
+    このルームのあるスペースのメンバーは、誰でもこのルームを発見し参加できます。ルームをスペースに追加できるのは、ルームの管理者だけです。
     スペースのメンバーのみ
     誰でもルームを発見し参加できます
     公開
@@ -1737,8 +1737,8 @@
     誰でもルームにノックができ、メンバーがその参加を承認または拒否できます
     現在のルームディレクトリの見え方を取得できません(%1$s)。
     このルームを%1$sのルームディレクトリに公開しますか?
-    アドレスを非公開にする
-    アドレスを公開
+    このアドレスを非公開にする
+    このアドレスを公開
     アドレスを設定すれば、他のユーザーがあなたのホームサーバー (%1$s) を通じてこのルームを見つけられるようになります。
     ローカルアドレス
     新しい公開アドレス(例: #alias:server)
@@ -1749,16 +1749,16 @@
     公開
     手動で新しいアドレスを公開
     他の公開アドレス:
-    公開されたアドレスを通して、どのサーバーのどのユーザーでもこのルームに参加できます。アドレスを公開するには、まずローカルアドレスとして設定する必要があります。
+    公開アドレスを通して、どのサーバーのどのユーザーでも、このルームに参加できます。アドレスを公開するには、まずローカルアドレスとして設定する必要があります。
     公開アドレス
     ルームのアドレス
-    ルームのアドレス及びルームディレクトリにおける可視性を管理できます。
+    このルームのアドレスと、ルームディレクトリにおける見え方を管理できます。
     スペースのアドレスを管理できます。
     スペースのアドレス
     ルームのアドレス
     ゲストの参加を許可
     ルームへのアクセス
-    発信履歴閲覧権限の変更は今後送信されるメッセージにのみ適用されます。既存履歴の表示は影響されません。
+    履歴の閲覧権限に関する変更は、今後、このルームで表示されるメッセージにのみ適用されます。既存の履歴の見え方には影響しません。
     無視して続行
     毎回確認
     招待
@@ -1897,7 +1897,7 @@
     検証プロセスがタイムアウトしました
     ユーザーが検証をキャンセルしました
     このルームを含む参加済のスペース
-    このルームにアクセスできるスペースを決定します。スペースが選択されるとそのメンバーはルーム名を見つけて参加できます。
+    このルームにアクセスできるスペースを決定します。スペースが選択されると、そのメンバーはルーム名を見つけて参加できます。
     検証がキャンセルされました。
 \n理由:%s
     相手が検証をキャンセルしました。
@@ -1918,7 +1918,7 @@
     このセッションを検証して、信頼済としてマークします。相手のセッションを信頼すると、さらに安心してエンドツーエンド暗号化を使用することができます。
     入力された検証要求
     検証を開始します
-    最大限のセキュリティーを確保するために、これを行うか、別の信頼できる通信手段を用いることをお勧めします。
+    最大限のセキュリティーを確保するために、これを対面で行うか、別の信頼済の通信手段を用いることを推奨します。
     短い文字列を比較して検証します。
     認証情報が不正または期限切れのため、ログアウトされました。
     構成を使用
@@ -1932,7 +1932,7 @@
     暗号化されたメッセージを決して失わないために
     バックアップ (%s) のtrust infoの取得に失敗しました。
     セッションの暗号化が有効になっていません
-    これを使用するとデータを%sと共有します。
+    これを使用するとデータが%sと共有される可能性があります:
     %1$s:%2$s %3$s
     %1$s:%2$s
     あなたが知らないかもしれない他のスペースやルーム
@@ -1943,7 +1943,7 @@
     スペースのメンバーに発見とアクセスを許可します。
     スペース %s のメンバーが検索、プレビュー、参加できます。
     非公開(招待のみ)
-    規定メディアソース
+    既定のメディアソース
     既定の圧縮率
     ルームのアップグレード
     ボットによるメッセージ
@@ -2363,12 +2363,12 @@
     暗号化されたルームでのメンションとキーワードによる通知は、携帯端末では利用できません。
     ユーザーを無視し、そのメッセージを非表示にします
     %sとのコマンドは認識されていますが、スレッドではサポートされていません。
-    誰でもこのスペースを発見し参加できます
+    誰でもスペースを発見し参加できます
     法的情報
     ユーザーに関する情報を表示します
     このルームにおいてのみアバターを変更します
     このルームにおいてのみ表示名を変更します
-    ユーザーの無視を解除し、これからのメッセージを表示します
+    ユーザーの無視を解除し、以後のメッセージを表示します
     続行するには%sを入力してください
     復旧用のパスフレーズ
     有効なリカバリーキーではありません

From 1b52124bb8ba56fd520ce3517d8628fd0e05539a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?N=C3=ADcolas=20F=2E=20R=2E=20A=2E=20Prado?=
 
Date: Sat, 19 Feb 2022 23:55:16 +0000
Subject: [PATCH 529/581] Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (2785 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/pt_BR/
---
 vector/src/main/res/values-pt-rBR/strings.xml | 32 +++++++++----------
 1 file changed, 16 insertions(+), 16 deletions(-)

diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/vector/src/main/res/values-pt-rBR/strings.xml
index 9eabb09e697..cda7517f6b7 100644
--- a/vector/src/main/res/values-pt-rBR/strings.xml
+++ b/vector/src/main/res/values-pt-rBR/strings.xml
@@ -405,7 +405,7 @@
     Servidorcasa:
     Servidor de identidade:
     Eu tenho verificado meu endereço de email
-    Para resettar sua senha, entre o endereço de email linkado a sua conta:
+    Para resetar sua senha, digite o endereço de email vinculado a sua conta:
     O endereço de email linkado a sua conta deve ser entrado.
     Uma nova senha deve ser entrada.
     Um email tem sido enviado para %s. Uma vez que tenha seguido o link que ele contém, clique abaixo.
@@ -508,7 +508,7 @@
     Sair desta sala
     Banir
     Desbanir
-    Resettar a usuária(o) normal
+    Resetar a usuária(o) normal
     Fazer moderador(a)
     Fazer admin
     Ignorar
@@ -678,7 +678,7 @@
     Acesso e visibilidade
     Listar esta sala em diretório de salas
     Acesso a Sala
-    Legibilidade de Histórico de Sala
+    Legibilidade do Histórico da Sala
     Quem pode ler o histórico\?
     Quem pode acessar esta sala?
     Qualquer pessoa
@@ -828,7 +828,7 @@
     Som de notificação
     Mnsgns contendo meu nome de exibição
     Mnsgns contendo meu nome de usuária(o)
-    Previsualização de URL emlinha
+    Pré-visualização de URL em linha
     Mostrar timestamps em formato de 12 horas
     Vibrar ao mencionar um/uma usuário(a)
     Analítica
@@ -1294,7 +1294,7 @@
     Backup Seguro
     Gerenciar
     Configurar Backup Seguro
-    Resettar Backup Seguro
+    Resetar Backup Seguro
     Configurar neste dispositivo
     Salvaguardar-se contra perda de acesso a mensagens & dados encriptados ao fazer backup de chaves de encriptação em seu servidor.
     Gere uma nova Chave de Segurança ou defina uma nova Frase de Segurança para seu backup existente.
@@ -1540,7 +1540,7 @@
     Correspondência errada de usuária(o)
     Erro Desconhecido
     Você não está usando nenhum servidor de identidade
-    Nenhum servidor de identidade está configurado, ele é requerido para resettar sua senha.
+    Nenhum servidor de identidade está configurado, ele é requerido para resetar sua senha.
     Parece que você está tentando se conectar a um outro servidorcasa. Você quer fazer signout\?
     Editar
     Responder
@@ -1783,13 +1783,13 @@
 \n
 \nVocê quer fazer signup usando um cliente web\?
     Este email não está associado com nenhuma conta.
-    Resettar senha em %1$s
+    Resetar senha em %1$s
     Um email de verificação vai ser enviado para sua inbox para confirmar definição de sua nova senha.
     Próximo
     Email
     Nova senha
     Aviso!
-    Mudar sua senha vai resettar quaisquer chaves de encriptação ponta-a-ponta em todas as suas sessões, fazendo histórico de chat encriptado ilegível. Configure Backup de Chave ou exporte suas chaves de sala de uma outra sessão antes de resettar sua senha.
+    Mudar sua senha vai resetar quaisquer chaves de encriptação ponta-a-ponta em todas as suas sessões, tornando histórico de chat encriptado ilegível. Configure Backup de Chave ou exporte suas chaves de sala de uma outra sessão antes de resetar sua senha.
     Continuar
     Este email não está linkado a nenhuma conta
     Cheque sua inbox
@@ -2032,7 +2032,7 @@
     %1$s (%2$s) fez signin usando uma nova sessão:
     Até que esta(e) usuária(o) confie nesta sessão, mensagens enviadas para e desde ela são etiquetadas com avisos. Alternativamente, você pode verificá-la manualmente.
     Inicializar AssinaturaCruzada
-    Resettar Chaves
+    Resetar Chaves
     QR code
     Quase lá! %s está mostrando um tick (✓)\?
     Sim
@@ -2287,11 +2287,11 @@
     Falha para validar PIN, por favor toque um novo.
     Entre seu PIN
     Esqueceu PIN\?
-    Resettar PIN
+    Resetar PIN
     Novo PIN
-    Para resettar seu PIN, você vai precisar refazer login e criar um novo.
+    Para resetar seu PIN, você vai precisar refazer login e criar um novo.
     Habilitar PIN
-    Se você quer resettar seu PIN, toque em Esqueceu PIN para fazer logout e resettá-lo.
+    Se você quer resetar seu PIN, toque em Esqueceu PIN para fazer logout e resetá-lo.
     Confirmar PIN para desabilitar PIN
     Prevenir chamada acidental
     Pedir por confirmação antes de começar uma chamada
@@ -2368,9 +2368,9 @@
         Mostrar %d dispositivos com os quais você pode verificar agora
     
     Você vai recomeçar com nada de histórico, mensagens, dispositivos confiados ou usuárias(os) confiadas(os)
-    Se você resettar tudo
+    Se você resetar tudo
     Somente faça isto se você não tem nenhum outro dispositivo com o qual você pode verificar este dispositivo.
-    Resettar tudo
+    Resetar tudo
     Esqueceu ou perdeu todas as opções de recuperação\? Resette tudo
     Você juntou-se.
     Mensagens nesta sala são encriptadas ponta-a-ponta.
@@ -2394,7 +2394,7 @@
     Pesquisar em salas encriptadas não é suportado ainda.
     Você não tem permissão para começar uma chamada
     Você não tem permissão para começar uma chamada de conferência
-    Resettar
+    Resetar
     %1$s fez isto somente convite.
     Você fez isto somente convite.
     %s juntou-se.
@@ -3050,7 +3050,7 @@
     Recomece o aplicativo para a mudançar tomar efeito.
     Habilitar matemática LaTeX
     Você não é permitida(o) a juntar-se a esta sala
-    Criar sondagem
+    Criar enquete
     Abrir contatos
     Enviar sticker
     Fazer upload de arquivo

From 89daabb24fc6db5706b2ac70f6d865969cbb1812 Mon Sep 17 00:00:00 2001
From: yaronsb 
Date: Mon, 21 Feb 2022 10:50:38 +0000
Subject: [PATCH 530/581] Translated using Weblate (Hebrew)

Currently translated at 78.0% (2173 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/he/
---
 vector/src/main/res/values-iw/strings.xml | 146 ++++++++++++++++++++++
 1 file changed, 146 insertions(+)

diff --git a/vector/src/main/res/values-iw/strings.xml b/vector/src/main/res/values-iw/strings.xml
index a245b14b9db..48bc5a675d0 100644
--- a/vector/src/main/res/values-iw/strings.xml
+++ b/vector/src/main/res/values-iw/strings.xml
@@ -2411,4 +2411,150 @@
     כל חברי החדר, מהנקודה בה הם הוזמנו.
     את/ה הפכת הודעות עתידיות לגלויות בפני %1$s
     %1$s הפך הודעות עתידיות לגלויות בפני %2$s
+    חסמת את %1$s. סיבה: %2$s
+    %1$s חסם את %2$s. סיבה: %3$s
+    ביטלת את החסימה של %1$s. סיבה: %2$s
+    %1$s ביטל החסימה של %2$s. סיבה: %3$s
+    הסרת את %1$s. סיבה: %2$s
+    %1$s הסיר את %2$s. סיבה: %3$s
+    דחית את ההזמנה. סיבה: %1$s
+    %1$s דחה את ההזמנה. סיבה: %2$s
+    עזבת. סיבה: %1$s
+    %1$s עזב. סיבה: %2$s
+    יצאת מהחדר. סיבה: %1$s
+    %1$s עזבו את החדר. סיבה: %2$s
+    הצטרפת. סיבה: %1$s
+    %1$s הצטרף. סיבה: %2$s
+    הצטרפת לחדר. סיבה: %1$s
+    %1$s הצטרף לחדר. סיבה: %2$s
+    %1$s הזמין אותך. סיבה: %2$s
+    הזמנת את %1$s. סיבה: %2$s
+    %1$s הפעילה הצפנה מקצה לקצה (אלגוריתם לא מזוהה %2$s).
+    הפעלת הצפנה מקצה לקצה (אלגוריתם לא מזוהה %1$s).
+    %1$s הזמין את %2$s. סיבה: %3$s
+    ההזמנה שלך. סיבה: %1$s
+    ההזמנה של %1$s. סיבה: %2$s
+    נקה את תור השליחה
+    הודעה נשלחת…
+    הודעה נשלחה
+    סנכרון ראשוני:
+\nייבוא נתוני חשבון
+    סנכרון ראשוני:
+\nייבוא קהילות
+    סנכרון ראשוני:
+\nייבוא חדרים עזובים
+    סנכרון ראשוני:
+\nייבוא חדרים מוזמנים
+    סנכרון ראשוני:
+\nטוען את השיחות שלך
+\nאם הצטרפת להרבה חדרים, זה עשוי לקחת זמן מה
+    סנכרון ראשוני:
+\nייבוא חדרים
+    סנכרון ראשוני:
+\nייבוא קריפטו
+    סנכרון ראשוני:
+\nמייבא חשבון…
+    סנכרון ראשוני:
+\nמוריד נתונים…
+    סנכרון ראשוני:
+\nממתין לתגובת השרת…
+    חדר ריק (היה %s)
+    חדר ריק
+    
+        %1$s ועוד אחד
+        %1$s ו-%2$d אחרים
+        %1$s ו-%2$d אחרים
+        %1$s ו-%2$d אחר
+    
+    
+        %1$s, %2$s, %3$s ו-%4$d אחר
+        %1$s, %2$s, %3$s ו-%4$d אחרים
+        %1$s, %2$s, %3$s ו-%4$d אחרים
+        %1$s, %2$s, %3$s ו-%4$d אחר
+    
+    %3$s, %2$s, %1$s ו-%4$s
+    %2$s, %1$s ו-%3$s
+    %1$s ו-%2$s
+    הזמנה לחדר
+    הזמן מ-%s
+    מספר טלפון
+    כתובת דוא\"ל
+    אינך רשאי להצטרף לחדר זה
+    כרגע לא ניתן להצטרף מחדש לחדר ריק.
+    שגיאת מטריקס
+    שגיאת רשת
+    העלאת התמונה נכשלה
+    לא ניתן לשלוח הודעה
+    לא ניתן היה לתקן
+    מכשיר השולח לא שלח לנו את המפתחות להודעה זו.
+    ** לא ניתן לפענח: %s **
+    %1$s מ-%2$s עד %3$s
+    %1$s שינה את רמת העוצמה של %2$s.
+    שינית את רמת העוצמה של %1$s.
+    מותאם אישית
+    מותאם אישית (%1$d)
+    ברירת מחדל
+    מַנחֶה
+    מנהל מערכת
+    שינית ועידת וידאו
+    ועידת וידאו השתנתה ע\"י %1$s
+    סיימת ועידת וידאו
+    ועידת וידאו הסתיימה ע\"י %1$s
+    התחלת ועידת וידאו
+    ועידת וידאו התחילה על ידי %1$s
+    שינית את הווידג\'ט %1$s
+    %1$s שינה את %2$s ווידג\'ט
+    הסרת את ווידג\'ט %1$s
+    %1$s הסיר את %2$s ווידג\'ט
+    הוספת ווידג\'ט %1$s
+    %1$s הוסיף %2$s ווידג\'ט
+    קיבלת את ההזמנה עבור %1$s
+    %1$s קיבל את ההזמנה עבור %2$s
+    ביטלת את ההזמנה עבור %1$s
+    %1$s ביטל/ה את ההזמנה עבור %2$s
+    ביטלת את ההזמנה של %1$s להצטרף לחדר
+    %1$s ביטל/ה את ההזמנה של %2$s להצטרף לחדר
+    הזמנת את %1$s
+    %1$s הזמין את %2$s
+    שלחת הזמנה אל %1$s להצטרף לחדר
+    %1$s שלח הזמנה אל %2$s להצטרף לחדר
+    עדכנת את הפרופיל שלך %1$s
+    %1$s עדכן את הפרופיל שלו %2$s
+    הודעה הוסרה על ידי %1$s [סיבה: %2$s]
+    ההודעה הוסרה [סיבה: %1$s]
+    ההודעה הוסרה על ידי %1$s
+    ההודעה הוסרה
+    הסרת את דמות החדר
+    %1$s הסיר את דמות החדר
+    הסרת את נושא החדר
+    %1$s הסיר את נושא החדר
+    הסרת את שם החדר
+    %1$s הסיר את שם החדר
+    (גם האווטאר השתנה)
+    ועידת VoIP הסתיימה
+    ועידת VoIP החלה
+    ביקשת ועידת VoIP
+    %1$s ביקש ועידת VoIP
+    🎉 כל השרתים אסורים להשתתף! לא ניתן עוד להשתמש בחדר זה.
+    ללא שינוי.
+    • שרתים התואמים ל-%s מורשים כעת.
+    • שרתים התואמים ל-%s הוסרו מרשימת המורשים.
+    • שרתים התואמים ל-%s הוסרו מרשימת החסומים.
+    • שרתים התואמים ל-%s נאסרו כעת.
+    שינית את רשימות ה-ACL של השרת עבור החדר הזה.
+    %s שינה את רשימות ה-ACL של השרת עבור החדר הזה.
+    • מותרים שרתים התואמים ל-%s.
+    • שרתים התואמים %s אסורים.
+    אתה מגדיר את רשימות ה-ACL של השרת עבור החדר הזה.
+    %s הגדר את רשימות ה-ACL של השרת עבור החדר הזה.
+    שדרגת כאן.
+    %s שודרג כאן.
+    שדרגת את החדר הזה.
+    %s שדרג את החדר הזה.
+    הפעלת הצפנה מקצה לקצה (%1$s)
+    %1$s הפעילה הצפנה מקצה לקצה (%2$s)
+    לא ידוע (%s).
+    כל אחד.
+    כל חברי החדר.
+    כל חברי החדר, מהנקודה בה הצטרפו.
 
\ No newline at end of file

From d51e1a4dfbcdcd6d61af9dc5b18cf25d145a6fe1 Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Mon, 21 Feb 2022 12:19:05 +0000
Subject: [PATCH 531/581] Translated using Weblate (Japanese)

Currently translated at 91.3% (2545 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 350ccedc385..b91820ca9b3 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -42,7 +42,7 @@
     VoIP会議が終了しました
     (アバターも変更されました)
     %1$sがルーム名を削除しました
-    %1$sがルームのトピックを削除しました
+    %1$sがルームの説明を削除しました
     %1$sがプロフィール %2$s を更新しました
     %1$sが%2$sにルームへの招待を送りました
     %1$sが%2$sの招待を受け入れました
@@ -808,8 +808,8 @@
     指定したユーザーを現在のルームに招待
     指定されたアドレスのルームに参加
     ルームを退室
-    ルームのテーマを設定
-    指定したIDのユーザーとの接続を切断
+    ルームの説明を設定
+    指定したIDのユーザーをこのルームから追放
     表示するニックネームを変更
     Markdown書式の入/切
     Matrixアプリの管理を修正するには
@@ -1028,7 +1028,7 @@
     アカウントデータ
     削除…
     削除の確認
-    このイベントを削除してよろしいですか?ルーム名やトピックの変更を削除すると、変更が元に戻る点にご注意ください。
+    このイベントを削除してよろしいですか?ルーム名や説明の変更を削除すると、変更が取り消されますのでご注意ください。
     暗号化は有効です
     このルーム内でのメッセージはエンドツーエンド暗号化されます。詳細の確認や検証はユーザーのプロフィールをご確認ください。
     暗号化が有効になっていません
@@ -1444,7 +1444,7 @@
     メッセージが削除されました
     ルームのアバターを削除しました
     %1$sがルームのアバターを削除しました
-    ルームのトピックを削除しました
+    ルームの説明を削除しました
     ルーム名を削除しました
     許可を与える
     ${app_name}は、確実に通知を受け取るために、影響の少ないバックグラウンド接続を維持する必要があります。
@@ -1852,7 +1852,7 @@
 \n%s
     ルームの設定
     トピック
-    ルームトピック(オプション)
+    ルームの説明(任意)
     ルーム名
     このルームはプレビューできません。参加しますか?
     現在、このルームにはアクセスできません。
@@ -2365,9 +2365,9 @@
     %sとのコマンドは認識されていますが、スレッドではサポートされていません。
     誰でもスペースを発見し参加できます
     法的情報
-    ユーザーに関する情報を表示します
-    このルームにおいてのみアバターを変更します
-    このルームにおいてのみ表示名を変更します
+    ユーザーに関する情報を表示
+    このルームにおいてのみアバターを変更
+    このルームにおいてのみ表示名を変更
     ユーザーの無視を解除し、以後のメッセージを表示します
     続行するには%sを入力してください
     復旧用のパスフレーズ

From eeb9785651d844ba2675c0c684f210638ebb1762 Mon Sep 17 00:00:00 2001
From: Adam Brown 
Date: Mon, 21 Feb 2022 13:03:31 +0000
Subject: [PATCH 532/581] limiting the room bitmap loader image size, the
 notification icon size is relatively small and there's no cap on the image
 size

---
 .../im/vector/app/features/notifications/BitmapLoader.kt  | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/vector/src/main/java/im/vector/app/features/notifications/BitmapLoader.kt b/vector/src/main/java/im/vector/app/features/notifications/BitmapLoader.kt
index da0f071841a..9190141dfbf 100644
--- a/vector/src/main/java/im/vector/app/features/notifications/BitmapLoader.kt
+++ b/vector/src/main/java/im/vector/app/features/notifications/BitmapLoader.kt
@@ -21,6 +21,7 @@ import android.graphics.Bitmap
 import androidx.annotation.WorkerThread
 import com.bumptech.glide.Glide
 import com.bumptech.glide.load.DecodeFormat
+import com.bumptech.glide.signature.ObjectKey
 import timber.log.Timber
 import javax.inject.Inject
 import javax.inject.Singleton
@@ -28,6 +29,9 @@ import javax.inject.Singleton
 @Singleton
 class BitmapLoader @Inject constructor(private val context: Context) {
 
+    private val iconWidth = context.resources.getDimensionPixelSize(android.R.dimen.notification_large_icon_width)
+    private val iconHeight = context.resources.getDimensionPixelSize(android.R.dimen.notification_large_icon_height)
+
     /**
      * Get icon of a room.
      * If already in cache, use it, else load it and call BitmapLoaderListener.onBitmapsLoaded() when ready
@@ -47,8 +51,10 @@ class BitmapLoader @Inject constructor(private val context: Context) {
                 Glide.with(context)
                         .asBitmap()
                         .load(path)
+                        .fitCenter()
                         .format(DecodeFormat.PREFER_ARGB_8888)
-                        .submit()
+                        .signature(ObjectKey("room-icon-notification"))
+                        .submit(iconWidth, iconHeight)
                         .get()
             } catch (e: Exception) {
                 Timber.e(e, "decodeFile failed")

From f050fc1e4ae757d4b947b2257d905cc0bba6d8fd Mon Sep 17 00:00:00 2001
From: Maxime Naturel 
Date: Mon, 21 Feb 2022 14:55:08 +0100
Subject: [PATCH 533/581] Returning empty viewState instead of null to avoid
 crash

---
 .../app/features/signout/soft/SoftLogoutViewModel.kt   | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt
index 84b443a60f7..00422d8872c 100644
--- a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutViewModel.kt
@@ -56,7 +56,7 @@ class SoftLogoutViewModel @AssistedInject constructor(
 
     companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() {
 
-        override fun initialState(viewModelContext: ViewModelContext): SoftLogoutViewState? {
+        override fun initialState(viewModelContext: ViewModelContext): SoftLogoutViewState {
             val sessionHolder = EntryPoints.get(viewModelContext.app(), SingletonEntryPoint::class.java)
                     .activeSessionHolder()
 
@@ -72,7 +72,13 @@ class SoftLogoutViewModel @AssistedInject constructor(
                         hasUnsavedKeys = session.hasUnsavedKeys()
                 )
             } else {
-                null
+                SoftLogoutViewState(
+                        homeServerUrl = "",
+                        userId = "",
+                        deviceId = "",
+                        userDisplayName = "",
+                        hasUnsavedKeys = false
+                )
             }
         }
     }

From 95c00a1cce66946ce1c62d6f143891db575d0287 Mon Sep 17 00:00:00 2001
From: Benoit Marty 
Date: Mon, 21 Feb 2022 17:52:26 +0100
Subject: [PATCH 534/581] Udpate comment

---
 .../internal/crypto/store/db/migration/MigrateCryptoTo015.kt    | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo015.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo015.kt
index 7bfea7d72f2..465c18555a4 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo015.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo015.kt
@@ -21,7 +21,7 @@ import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
 import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoRoomEntityFields
 import org.matrix.android.sdk.internal.util.database.RealmMigrator
 
-// Version 14L Update the way we remember key sharing
+// Version 15L adds wasEncryptedOnce field to CryptoRoomEntity
 class MigrateCryptoTo015(realm: DynamicRealm) : RealmMigrator(realm, 15) {
 
     override fun doMigrate(realm: DynamicRealm) {

From 9f44975b4a23ec6bf3398e8dc24862ac49acbe3a Mon Sep 17 00:00:00 2001
From: Adam Brown 
Date: Mon, 21 Feb 2022 16:54:51 +0000
Subject: [PATCH 535/581] tranforming images for adaptive shortcuts within
 glides transformations - avoid creating new bitmaps each time the room list
 changes

---
 .../home/AdaptiveIconTransformation.kt        | 54 +++++++++++++++++++
 .../app/features/home/AvatarRenderer.kt       | 45 +++++++++++-----
 .../app/features/home/ShortcutCreator.kt      | 14 ++---
 3 files changed, 92 insertions(+), 21 deletions(-)
 create mode 100644 vector/src/main/java/im/vector/app/features/home/AdaptiveIconTransformation.kt

diff --git a/vector/src/main/java/im/vector/app/features/home/AdaptiveIconTransformation.kt b/vector/src/main/java/im/vector/app/features/home/AdaptiveIconTransformation.kt
new file mode 100644
index 00000000000..9efd842e587
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/AdaptiveIconTransformation.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.home
+
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool
+import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
+import com.bumptech.glide.util.Util
+import java.nio.ByteBuffer
+import java.security.MessageDigest
+
+private const val ADAPTIVE_TRANSFORMATION_ID = "adaptive-icon-transform"
+private val ID_BYTES = ADAPTIVE_TRANSFORMATION_ID.toByteArray()
+
+class AdaptiveIconTransformation(private val adaptiveIconSize: Int, private val adaptiveIconOuterSides: Float) : BitmapTransformation() {
+
+    override fun updateDiskCacheKey(messageDigest: MessageDigest) {
+        messageDigest.update(ID_BYTES)
+        messageDigest.update(ByteBuffer.allocate(4).putInt(adaptiveIconSize).putFloat(adaptiveIconOuterSides).array())
+    }
+
+    override fun transform(pool: BitmapPool, toTransform: Bitmap, outWidth: Int, outHeight: Int): Bitmap {
+        val insetBmp = Bitmap.createBitmap(adaptiveIconSize, adaptiveIconSize, Bitmap.Config.ARGB_8888)
+        val canvas = Canvas(insetBmp)
+        canvas.drawBitmap(toTransform, adaptiveIconOuterSides, adaptiveIconOuterSides, null)
+        canvas.setBitmap(null)
+        return insetBmp
+    }
+
+    override fun equals(other: Any?): Boolean {
+        return if (other is AdaptiveIconTransformation) {
+            other.adaptiveIconSize == adaptiveIconSize && other.adaptiveIconOuterSides == adaptiveIconOuterSides
+        } else {
+            false
+        }
+    }
+
+    override fun hashCode() = Util.hashCode(ADAPTIVE_TRANSFORMATION_ID.hashCode(), Util.hashCode(adaptiveIconSize, Util.hashCode(adaptiveIconOuterSides)))
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt b/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt
index 2ee32336370..d8a8409f5a7 100644
--- a/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt
+++ b/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt
@@ -26,6 +26,7 @@ import androidx.core.graphics.drawable.toBitmap
 import com.amulyakhare.textdrawable.TextDrawable
 import com.bumptech.glide.load.MultiTransformation
 import com.bumptech.glide.load.Transformation
+import com.bumptech.glide.load.engine.DiskCacheStrategy
 import com.bumptech.glide.load.resource.bitmap.CenterCrop
 import com.bumptech.glide.load.resource.bitmap.CircleCrop
 import com.bumptech.glide.load.resource.bitmap.RoundedCorners
@@ -157,25 +158,41 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
     fun shortcutDrawable(glideRequests: GlideRequests, matrixItem: MatrixItem, iconSize: Int): Bitmap {
         return glideRequests
                 .asBitmap()
-                .let {
-                    val resolvedUrl = resolvedUrl(matrixItem.avatarUrl)
-                    if (resolvedUrl != null) {
-                        it.load(resolvedUrl)
-                    } else {
-                        val avatarColor = matrixItemColorProvider.getColor(matrixItem)
-                        it.load(TextDrawable.builder()
-                                .beginConfig()
-                                .bold()
-                                .endConfig()
-                                .buildRect(matrixItem.firstLetterOfDisplayName(), avatarColor)
-                                .toBitmap(width = iconSize, height = iconSize))
-                    }
-                }
+                .avatarOrText(matrixItem, iconSize)
                 .apply(RequestOptions.centerCropTransform())
                 .submit(iconSize, iconSize)
                 .get()
     }
 
+    @AnyThread
+    @Throws
+    fun adaptiveShortcutDrawable(glideRequests: GlideRequests, matrixItem: MatrixItem, iconSize: Int, adaptiveIconSize: Int, adaptiveIconOuterSides: Float): Bitmap {
+        return glideRequests
+                .asBitmap()
+                .avatarOrText(matrixItem, iconSize)
+                .transform(CenterCrop(), AdaptiveIconTransformation(adaptiveIconSize, adaptiveIconOuterSides))
+                .diskCacheStrategy(DiskCacheStrategy.RESOURCE)
+                .submit(adaptiveIconSize, adaptiveIconSize)
+                .get()
+    }
+
+    private fun GlideRequest.avatarOrText(matrixItem: MatrixItem, iconSize: Int): GlideRequest {
+        return this.let {
+            val resolvedUrl = resolvedUrl(matrixItem.avatarUrl)
+            if (resolvedUrl != null) {
+                it.load(resolvedUrl)
+            } else {
+                val avatarColor = matrixItemColorProvider.getColor(matrixItem)
+                it.load(TextDrawable.builder()
+                        .beginConfig()
+                        .bold()
+                        .endConfig()
+                        .buildRect(matrixItem.firstLetterOfDisplayName(), avatarColor)
+                        .toBitmap(width = iconSize, height = iconSize))
+            }
+        }
+    }
+
     @UiThread
     fun renderBlur(matrixItem: MatrixItem,
                    imageView: ImageView,
diff --git a/vector/src/main/java/im/vector/app/features/home/ShortcutCreator.kt b/vector/src/main/java/im/vector/app/features/home/ShortcutCreator.kt
index ee7edc021d6..082d318cc7c 100644
--- a/vector/src/main/java/im/vector/app/features/home/ShortcutCreator.kt
+++ b/vector/src/main/java/im/vector/app/features/home/ShortcutCreator.kt
@@ -19,7 +19,6 @@ package im.vector.app.features.home
 import android.content.Context
 import android.content.pm.ShortcutInfo
 import android.graphics.Bitmap
-import android.graphics.Canvas
 import android.os.Build
 import androidx.annotation.WorkerThread
 import androidx.core.content.pm.ShortcutInfoCompat
@@ -61,7 +60,12 @@ class ShortcutCreator @Inject constructor(
     fun create(roomSummary: RoomSummary, rank: Int = 1): ShortcutInfoCompat {
         val intent = RoomDetailActivity.shortcutIntent(context, roomSummary.roomId)
         val bitmap = try {
-            avatarRenderer.shortcutDrawable(GlideApp.with(context), roomSummary.toMatrixItem(), iconSize)
+            val glideRequests = GlideApp.with(context)
+            val matrixItem = roomSummary.toMatrixItem()
+            when (useAdaptiveIcon) {
+                true  -> avatarRenderer.adaptiveShortcutDrawable(glideRequests, matrixItem, iconSize, adaptiveIconSize, adaptiveIconOuterSides.toFloat())
+                false -> avatarRenderer.shortcutDrawable(glideRequests, matrixItem, iconSize)
+            }
         } catch (failure: Throwable) {
             null
         }
@@ -83,11 +87,7 @@ class ShortcutCreator @Inject constructor(
 
     private fun Bitmap.toProfileImageIcon(): IconCompat {
         return if (useAdaptiveIcon) {
-            val insetBmp = Bitmap.createBitmap(adaptiveIconSize, adaptiveIconSize, Bitmap.Config.ARGB_8888)
-            val canvas = Canvas(insetBmp)
-            canvas.drawBitmap(this, adaptiveIconOuterSides.toFloat(), adaptiveIconOuterSides.toFloat(), null)
-
-            IconCompat.createWithAdaptiveBitmap(insetBmp)
+            IconCompat.createWithAdaptiveBitmap(this)
         } else {
             IconCompat.createWithBitmap(this)
         }

From 33c30e27faa68a64b07a04dbd9e13a48f96f1ad2 Mon Sep 17 00:00:00 2001
From: Adam Brown 
Date: Mon, 21 Feb 2022 17:14:36 +0000
Subject: [PATCH 536/581] flattening the room and user icon loading into the
 same file

---
 .../features/notifications/BitmapLoader.kt    | 65 -------------
 .../app/features/notifications/IconLoader.kt  | 66 -------------
 .../notifications/NotificationBitmapLoader.kt | 92 +++++++++++++++++++
 .../notifications/RoomGroupMessageCreator.kt  | 11 +--
 4 files changed, 96 insertions(+), 138 deletions(-)
 delete mode 100644 vector/src/main/java/im/vector/app/features/notifications/BitmapLoader.kt
 delete mode 100644 vector/src/main/java/im/vector/app/features/notifications/IconLoader.kt
 create mode 100644 vector/src/main/java/im/vector/app/features/notifications/NotificationBitmapLoader.kt

diff --git a/vector/src/main/java/im/vector/app/features/notifications/BitmapLoader.kt b/vector/src/main/java/im/vector/app/features/notifications/BitmapLoader.kt
deleted file mode 100644
index 9190141dfbf..00000000000
--- a/vector/src/main/java/im/vector/app/features/notifications/BitmapLoader.kt
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * 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.notifications
-
-import android.content.Context
-import android.graphics.Bitmap
-import androidx.annotation.WorkerThread
-import com.bumptech.glide.Glide
-import com.bumptech.glide.load.DecodeFormat
-import com.bumptech.glide.signature.ObjectKey
-import timber.log.Timber
-import javax.inject.Inject
-import javax.inject.Singleton
-
-@Singleton
-class BitmapLoader @Inject constructor(private val context: Context) {
-
-    private val iconWidth = context.resources.getDimensionPixelSize(android.R.dimen.notification_large_icon_width)
-    private val iconHeight = context.resources.getDimensionPixelSize(android.R.dimen.notification_large_icon_height)
-
-    /**
-     * Get icon of a room.
-     * If already in cache, use it, else load it and call BitmapLoaderListener.onBitmapsLoaded() when ready
-     */
-    @WorkerThread
-    fun getRoomBitmap(path: String?): Bitmap? {
-        if (path == null) {
-            return null
-        }
-        return loadRoomBitmap(path)
-    }
-
-    @WorkerThread
-    private fun loadRoomBitmap(path: String): Bitmap? {
-        return path.let {
-            try {
-                Glide.with(context)
-                        .asBitmap()
-                        .load(path)
-                        .fitCenter()
-                        .format(DecodeFormat.PREFER_ARGB_8888)
-                        .signature(ObjectKey("room-icon-notification"))
-                        .submit(iconWidth, iconHeight)
-                        .get()
-            } catch (e: Exception) {
-                Timber.e(e, "decodeFile failed")
-                null
-            }
-        }
-    }
-}
diff --git a/vector/src/main/java/im/vector/app/features/notifications/IconLoader.kt b/vector/src/main/java/im/vector/app/features/notifications/IconLoader.kt
deleted file mode 100644
index ec53e89d573..00000000000
--- a/vector/src/main/java/im/vector/app/features/notifications/IconLoader.kt
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * 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.notifications
-
-import android.content.Context
-import android.os.Build
-import androidx.annotation.WorkerThread
-import androidx.core.graphics.drawable.IconCompat
-import com.bumptech.glide.Glide
-import com.bumptech.glide.load.DecodeFormat
-import com.bumptech.glide.request.RequestOptions
-import timber.log.Timber
-import javax.inject.Inject
-import javax.inject.Singleton
-
-@Singleton
-class IconLoader @Inject constructor(private val context: Context) {
-
-    /**
-     * Get icon of a user.
-     * If already in cache, use it, else load it and call IconLoaderListener.onIconsLoaded() when ready
-     * Before Android P, this does nothing because the icon won't be used
-     */
-    @WorkerThread
-    fun getUserIcon(path: String?): IconCompat? {
-        if (path == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
-            return null
-        }
-
-        return loadUserIcon(path)
-    }
-
-    @WorkerThread
-    private fun loadUserIcon(path: String): IconCompat? {
-        return path.let {
-            try {
-                Glide.with(context)
-                        .asBitmap()
-                        .load(path)
-                        .apply(RequestOptions.circleCropTransform()
-                                .format(DecodeFormat.PREFER_ARGB_8888))
-                        .submit()
-                        .get()
-            } catch (e: Exception) {
-                Timber.e(e, "decodeFile failed")
-                null
-            }?.let { bitmap ->
-                IconCompat.createWithBitmap(bitmap)
-            }
-        }
-    }
-}
diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationBitmapLoader.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationBitmapLoader.kt
new file mode 100644
index 00000000000..518b011ffd3
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationBitmapLoader.kt
@@ -0,0 +1,92 @@
+/*
+ * 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.notifications
+
+import android.content.Context
+import android.graphics.Bitmap
+import android.os.Build
+import androidx.annotation.WorkerThread
+import androidx.core.graphics.drawable.IconCompat
+import com.bumptech.glide.Glide
+import com.bumptech.glide.load.DecodeFormat
+import com.bumptech.glide.load.resource.bitmap.CircleCrop
+import com.bumptech.glide.signature.ObjectKey
+import timber.log.Timber
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class NotificationBitmapLoader @Inject constructor(private val context: Context) {
+
+    /**
+     * Get icon of a room
+     */
+    @WorkerThread
+    fun getRoomBitmap(path: String?): Bitmap? {
+        if (path == null) {
+            return null
+        }
+        return loadRoomBitmap(path)
+    }
+
+    @WorkerThread
+    private fun loadRoomBitmap(path: String): Bitmap? {
+        return try {
+            Glide.with(context)
+                    .asBitmap()
+                    .load(path)
+                    .format(DecodeFormat.PREFER_ARGB_8888)
+                    .signature(ObjectKey("room-icon-notification"))
+                    .submit()
+                    .get()
+        } catch (e: Exception) {
+            Timber.e(e, "decodeFile failed")
+            null
+        }
+    }
+
+    /**
+     * Get icon of a user.
+     * Before Android P, this does nothing because the icon won't be used
+     */
+    @WorkerThread
+    fun getUserIcon(path: String?): IconCompat? {
+        if (path == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
+            return null
+        }
+
+        return loadUserIcon(path)
+    }
+
+    @WorkerThread
+    private fun loadUserIcon(path: String): IconCompat? {
+        return try {
+            val bitmap = Glide.with(context)
+                    .asBitmap()
+                    .load(path)
+                    .transform(CircleCrop())
+                    .format(DecodeFormat.PREFER_ARGB_8888)
+                    .signature(ObjectKey("user-icon-notification"))
+                    .submit()
+                    .get()
+            IconCompat.createWithBitmap(bitmap)
+        } catch (e: Exception) {
+            Timber.e(e, "decodeFile failed")
+            null
+        }
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/notifications/RoomGroupMessageCreator.kt b/vector/src/main/java/im/vector/app/features/notifications/RoomGroupMessageCreator.kt
index b57c81f6867..8310c15daa2 100644
--- a/vector/src/main/java/im/vector/app/features/notifications/RoomGroupMessageCreator.kt
+++ b/vector/src/main/java/im/vector/app/features/notifications/RoomGroupMessageCreator.kt
@@ -16,7 +16,6 @@
 
 package im.vector.app.features.notifications
 
-import android.content.Context
 import android.graphics.Bitmap
 import androidx.core.app.NotificationCompat
 import androidx.core.app.Person
@@ -28,11 +27,9 @@ import timber.log.Timber
 import javax.inject.Inject
 
 class RoomGroupMessageCreator @Inject constructor(
-        private val iconLoader: IconLoader,
-        private val bitmapLoader: BitmapLoader,
+        private val bitmapLoader: NotificationBitmapLoader,
         private val stringProvider: StringProvider,
-        private val notificationUtils: NotificationUtils,
-        private val appContext: Context
+        private val notificationUtils: NotificationUtils
 ) {
 
     fun createRoomMessage(events: List, roomId: String, userDisplayName: String, userAvatarUrl: String?): RoomNotification.Message {
@@ -41,7 +38,7 @@ class RoomGroupMessageCreator @Inject constructor(
         val roomIsGroup = !firstKnownRoomEvent.roomIsDirect
         val style = NotificationCompat.MessagingStyle(Person.Builder()
                 .setName(userDisplayName)
-                .setIcon(iconLoader.getUserIcon(userAvatarUrl))
+                .setIcon(bitmapLoader.getUserIcon(userAvatarUrl))
                 .setKey(firstKnownRoomEvent.matrixID)
                 .build()
         ).also {
@@ -92,7 +89,7 @@ class RoomGroupMessageCreator @Inject constructor(
             } else {
                 Person.Builder()
                         .setName(event.senderName)
-                        .setIcon(iconLoader.getUserIcon(event.senderAvatarPath))
+                        .setIcon(bitmapLoader.getUserIcon(event.senderAvatarPath))
                         .setKey(event.senderId)
                         .build()
             }

From 5bbb3f28b14186e750385cfa3bb6cb308e453ccf Mon Sep 17 00:00:00 2001
From: Adam Brown 
Date: Mon, 21 Feb 2022 17:30:45 +0000
Subject: [PATCH 537/581] adding changelog entry

---
 changelog.d/5276.misc | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 changelog.d/5276.misc

diff --git a/changelog.d/5276.misc b/changelog.d/5276.misc
new file mode 100644
index 00000000000..437bd28eb6b
--- /dev/null
+++ b/changelog.d/5276.misc
@@ -0,0 +1 @@
+Improves bitmap memory usage by caching the shortcut images
\ No newline at end of file

From 506690a8979a58203f0a1cf9dc31acde0cabf050 Mon Sep 17 00:00:00 2001
From: Adam Brown 
Date: Mon, 21 Feb 2022 17:54:05 +0000
Subject: [PATCH 538/581] fixing wrong shortcut icon size and ensuring the
 transformed files are stored with unique keys

---
 .../im/vector/app/features/home/AdaptiveIconTransformation.kt | 2 +-
 .../main/java/im/vector/app/features/home/AvatarRenderer.kt   | 4 +++-
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/home/AdaptiveIconTransformation.kt b/vector/src/main/java/im/vector/app/features/home/AdaptiveIconTransformation.kt
index 9efd842e587..6eb41a1d85c 100644
--- a/vector/src/main/java/im/vector/app/features/home/AdaptiveIconTransformation.kt
+++ b/vector/src/main/java/im/vector/app/features/home/AdaptiveIconTransformation.kt
@@ -31,7 +31,7 @@ class AdaptiveIconTransformation(private val adaptiveIconSize: Int, private val
 
     override fun updateDiskCacheKey(messageDigest: MessageDigest) {
         messageDigest.update(ID_BYTES)
-        messageDigest.update(ByteBuffer.allocate(4).putInt(adaptiveIconSize).putFloat(adaptiveIconOuterSides).array())
+        messageDigest.update(ByteBuffer.allocate(8).putInt(adaptiveIconSize).putFloat(adaptiveIconOuterSides).array())
     }
 
     override fun transform(pool: BitmapPool, toTransform: Bitmap, outWidth: Int, outHeight: Int): Bitmap {
diff --git a/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt b/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt
index d8a8409f5a7..5b96caa3f10 100644
--- a/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt
+++ b/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt
@@ -33,6 +33,7 @@ import com.bumptech.glide.load.resource.bitmap.RoundedCorners
 import com.bumptech.glide.request.RequestOptions
 import com.bumptech.glide.request.target.DrawableImageViewTarget
 import com.bumptech.glide.request.target.Target
+import com.bumptech.glide.signature.ObjectKey
 import im.vector.app.core.contacts.MappedContact
 import im.vector.app.core.di.ActiveSessionHolder
 import im.vector.app.core.glide.AvatarPlaceholder
@@ -171,8 +172,9 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
                 .asBitmap()
                 .avatarOrText(matrixItem, iconSize)
                 .transform(CenterCrop(), AdaptiveIconTransformation(adaptiveIconSize, adaptiveIconOuterSides))
+                .signature(ObjectKey("adaptive-icon"))
                 .diskCacheStrategy(DiskCacheStrategy.RESOURCE)
-                .submit(adaptiveIconSize, adaptiveIconSize)
+                .submit(iconSize, iconSize)
                 .get()
     }
 

From 6ded62a9fa30d2e8676ae33d3f4923818bb87f72 Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Mon, 21 Feb 2022 23:49:44 +0000
Subject: [PATCH 539/581] Translated using Weblate (Japanese)

Currently translated at 91.6% (2552 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 272 +++++++++++-----------
 1 file changed, 140 insertions(+), 132 deletions(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index b91820ca9b3..b604f11442f 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -144,9 +144,9 @@
     ユーザー名かパスワードが正しくありません
     ユーザー名は半角英数字、ドット、ハイフン、アンダースコアのみで記して下さい
     パスワードが短すぎます(最小6文字)
-    正しくないメールアドレスのようです
-    正しくない電話番号のようです
-    既に登録されているメールアドレスです。
+    メールアドレスの形式が正しくありません
+    電話番号の形式が正しくありません
+    このメールアドレスは既に登録されています。
     パスワードが一致しません
     パスワードを忘れましたか?
     登録を続行するにはメールボックスを確認して下さい
@@ -172,7 +172,7 @@
     呼び出し中です…
     はい
     いいえ
-    続行
+    続行する
     最新の未読へ移動
     ルームを退出
     このルームを退出してよろしいですか?
@@ -278,7 +278,7 @@
     言語
     インターフェース
     認証を確認中
-    電子メールを確認して、本文中のURLをクリックしてください。 完了したら「続ける」をクリックしてください。
+    電子メールを確認して、本文中のURLをクリックしてください。 完了したら「続行する」をクリックしてください。
     このメールアドレスは既に使われています。
     あなたのパスワードは更新されました
     国を選択
@@ -348,14 +348,14 @@
     復号化されたソースコードを表示
     名前変更
     オフライン
-    会話を開始
+    チャットを開始
     音声通話を開始
     ビデオ通話を開始
     写真または動画を撮影
     再設定用のメールを送信
     写真撮影やビデオ通話には、${app_name}端末のカメラの使用を許可する必要があります。
     通話を開始できませんでした。後ほど試してください
-    権限が無いため、一部の機能を利用できない可能性があります…
+    権限がないため、一部の機能を利用できない可能性があります…
     このルームで会議を開始するためには招待の権限が必要です
     無視して送る
     サインアウト
@@ -398,7 +398,7 @@
     パスワードを再設定するには、アカウントに登録されているメールアドレスを入力してください:
     アカウントに登録されたメールアドレスの入力が必要です。
     新しいパスワードの入力が必要です。
-    %sへ電子メールが送信されました。リンクにアクセスしてから、以下をクリックしてください。
+    %sへ電子メールが送信されました。メール内のリンクにアクセスしてから、以下をクリックしてください。
     メールアドレスの確認に失敗しました:電子メールのリンクをクリックしたことを確認してください
     パスワードがリセットされました。
 \n
@@ -604,7 +604,7 @@
     未読のあるルームを固定
     分析
     データ節約モード
-    メールアドレスを認証できません。メールを確認して、記載されているリンクをクリックしてください。その後、「続ける」をクリックしてください。
+    メールアドレスを認証できません。メールを確認して、記載されているリンクをクリックしてください。その後、「続行する」をクリックしてください。
     この通知の対象を削除してよろしいですか?
     %1$s %2$sを削除してよろしいですか?
     コード
@@ -686,11 +686,11 @@
     ルーム
     参加済
     招待済
-    グループのメンバーをフィルタリング
-    グループのルームを絞り込み
+    グループのメンバーを絞り込む
+    グループのルームを絞り込む
     管理者はこのコミュニティーの詳細を規定していません。
-    %2$sによって%1$sから除外されました
-    %2$sによって%1$sへの参加を禁止されました
+    %2$sによって%1$sから追放されました
+    %2$sによって%1$sからブロックされました
     理由:%1$s
     再参加
     端末を振って不具合を報告
@@ -817,44 +817,44 @@
         %d名のメンバー
     
     
-        %dルーム
+        %d個のルーム
     
-    アバターを読み込み
-    %1$のホームサーバーを使用し続けるには、利用規約を読み、同意する必要があります。
+    開封確認用のアバター
+    %1$sのホームサーバーの使用を継続するには、利用規約を確認し、同意する必要があります。
     エラー
-    アバターに通知を表示
-    今すぐ見る
+    通知用のアバター
+    今すぐ確認
     アカウントを停止
     この操作により、あなたのアカウントは永久に使えなくなります。あなたはログインできなくなり、誰も同じユーザーIDを再登録できなくなります。アカウントが参加している全てのルームを退出し、IDサーバーからアカウントの詳細は削除されます。 この操作は取り消せません。
 \n
 \nアカウントを停止しても、 デフォルトではあなたが送信したメッセージの履歴は消去されません。メッセージの履歴の消去を望む場合は、以下のボックスにチェックを入れてください。
 \n
-\nMatrixのメッセージの見え方は、電子メールと同様のものです。メッセージの履歴を消去すると、あなたが送信したメッセージは、新規または未登録のユーザーには共有されることはありませんが、既にメッセージを取得している登録ユーザーは、今後もそのコピーにアクセスできます。
-    アカウントを停止したときに、自分の送信した全てのメッセージの履歴を消去してください(警告: この操作により、今後のユーザーは会話を不完全な形で見ることになります)
-    続けるには、パスワードを入力してください:
+\nMatrixのメッセージの見え方は、電子メールと同様のものです。メッセージの履歴を消去すると、あなたが送信したメッセージは、新規または未登録のユーザーに共有されることはありませんが、既にメッセージを取得している登録ユーザーは、今後もそのコピーにアクセスできます。
+    アカウントを停止するときに、自分の送信した全てのメッセージの履歴を消去してください(警告: この操作により、今後のユーザーは会話を不完全な形で見ることになります)
+    続行するには、パスワードを入力してください:
     アカウントを停止
     パスワードを入力してください。
-    このルームは置き換えられており、アクティブではありません。
-    ここで会話が続いています
+    このルームは交換されており、使用されていません。
+    こちらから継続中の会話を確認
     このルームは別の会話の続きです
-    より古いメッセージを見るには、ここをクリックしてください
-    リソース制限の超過
+    以前のメッセージを見るには、ここをクリックしてください
+    リソース制限を超えました
     管理者に連絡
-    あなたのサービス管理者に連絡
+    サービス管理者に連絡してください
     このホームサーバーはリソース制限の1つを超過しているため、 ユーザーがログインできなくなることがあります
     このホームサーバーはリソース制限の1つを超過しています。
      このホームサーバーは月間アクティブユーザーの上限に達しているため、 ユーザーがログインできなくなることがあります
     このホームサーバーは月間アクティブユーザーの上限に達しています。
-    この上限を上げるには%sしてください。
-    このサービスを使い続けるには%sしてください。
-    最初にルームのメンバーのみを読み込むことでパフォーマンスを向上。
-    あなたのホームサーバーはルームのメンバーの簡易読み込みをサポートしていません。後で試してください。
-    ルームのメンバーの簡易読み込み
+    この制限を上げるには、%sしてください。
+    このサービスを使い続けるには、%sしてください。
+    最初に表示されるルームのメンバーのみを読み込むことでパフォーマンスを向上。
+    あなたのホームサーバーは、ルームのメンバーの遅延読み込みをサポートしていません。後で試してください。
+    ルームのメンバーの遅延読み込み
     申し訳ありません、エラーが発生しました
     バージョン %s
     エクスポートされた鍵を暗号化するパスフレーズを作成してください。 鍵をインポートするには、同一のパスフレーズを入力する必要があります。
     パスフレーズの作成
-    パスフレーズは一致する必要があります
+    パスフレーズが一致していません
     情報領域を表示
     常に
     エラーの場合のみ
@@ -941,7 +941,7 @@
     返信
     メッセージが削除されました
     削除済のメッセージを表示
-    削除されたメッセージの代わりに削除されたという通知を表示します。
+    削除されたメッセージに関する通知を表示
     ユーザーによって削除されたイベント
     新しいルームを作成
     変更
@@ -962,8 +962,8 @@
     ダイレクトメッセージ
     (編集済)
     会話を検索…
-    Matrix ID から追加
-    ユーザー名または ID で検索…
+    Matrix IDから追加
+    ユーザー名またはIDで検索…
     全てのメッセージ (音量大)
     全てのメッセージ
     メンションのみ
@@ -980,7 +980,7 @@
     暗号化を有効にする
     いったん有効にすると、暗号化を無効にすることはできません。
     セキュリティー
-    詳細
+    詳しく知る
     その他の設定
     管理者としての操作
     ルームの設定
@@ -1043,17 +1043,17 @@
     破棄
     再生
     一時停止
-    消去
+    閉じる
     スキップ
     完了
     無視
     レビュー
     待機しています…
     サムネイルを暗号化しています…
-    サムネイルを送信しています (%1$s/%2$s)
+    サムネイルを送信しています(%2$s個のうち%1$s個)
     ファイルを暗号化しています…
-    ファイルを送信しています (%1$s/%2$s)
-    ファイル %1$sをダウンロードしています…
+    ファイルを送信しています(%2$s個のうち%1$s個)
+    ファイル %1$s をダウンロードしています…
     ファイル
     連絡先
     カメラ
@@ -1128,7 +1128,7 @@
     このURLからホームサーバーに接続できませんでした、ご確認ください
     有効なMatrixサーバーのアドレスではありません
     このURLは検索結果に表示できません、ご確認ください
-    この電話番号は既に使用されています。
+    この電話番号は既に登録されています。
     アカウント復旧用のメールアドレスを設定します。後からオプションでメールアドレスや電話番号を使用して知人に見つけてもらえるようにできます。
     シングルサインオンを使用してサインイン
     HDを使用する
@@ -1169,8 +1169,8 @@
     タイムラインで非表示のイベントを表示
     QRコードをスキャン
     QRコード画像
-    QR コード
-    QR コードによる追加
+    QRコード
+    QRコードによる追加
     コードを共有
     ${app_name} で会話しましょう:%s
     友達を招待
@@ -1454,7 +1454,7 @@
     ディスカバリー(発見)
     これにより、現在のキーまたはフレーズが置き換えられます。
     新しいセキュリティーキーを生成するか、既存のバックアップに新しいセキュリティーフレーズを設定します。
-    サーバー上の暗号鍵をバックアップすることにより、暗号化されたメッセージとデータへのアクセスが失われるのを予防します。
+    サーバー上の暗号鍵をバックアップして、暗号化されたメッセージとデータへのアクセスが失われるのを防ぎましょう。
     メッセージ作成画面に絵文字キーボードを開くためのボタンを追加
     絵文字キーボードを表示
     アバターと表示名の変更を含む。
@@ -1542,11 +1542,11 @@
     鍵のバックアップを使用開始
     パスフレーズが弱すぎます
     パスフレーズを入力してください
-    Google PlayサービスのAPKが見つかりませんでした。通知がうまく機能しない場合があります。
+    Google PlayサービスのAPKが見つかりませんでした。通知が適切に機能しない場合があります。
     ユーザー名を入力してください。
     無視
     共有
-    続けるには利用規約を承認する必要があります。
+    続行するには利用規約を承認する必要があります。
     全てブロック
     許可
     ルームID
@@ -1590,7 +1590,7 @@
     安全バックアップを設定
     鍵のバックアップで管理
     鍵のバックアップを使用
-    暗号化されたメッセージ及びデータへのアクセスを喪失しないための安全措置
+    暗号化されたメッセージとデータへのアクセスが失われるのを防ぎましょう
     鍵のバックアップを使用開始
     自分でした
     メッセージの鍵の新しい安全なバックアップが検出されました。
@@ -1627,7 +1627,7 @@
     鍵をダウンロードしています…
     リカバリーキーを計算しています…
     バックアップを復元しています:
-    ネットワークエラー: 接続を確認して再試行してください。
+    ネットワークエラー:接続を確認して再試行してください。
     このパスフレーズではバックアップを復号化できませんでした。正しいリカバリーパスフレーズを入力したことを確認してください。
     リカバリーキーを喪失しましたか? 設定で新しいリカバリーキーを設定できます。
     メッセージの復旧
@@ -1664,7 +1664,7 @@
     または、リカバリーキーでバックアップを保護し、安全な場所に保存してください。
     鍵のコピーを暗号化してホームサーバーに保存します。バックアップを保護するためにパスフレーズを設定してください。
 \n
-\n最高度のセキュリティーのために、アカウントのパスワードと異なるものに設定することが大切です。
+\n最大限のセキュリティーを確保するために、アカウントのパスワードと異なるものに設定することが大切です。
     パスフレーズを使用してバックアップを保護します。
     暗号化されたメッセージは、エンドツーエンドの暗号化によって保護されています。これらの暗号化されたメッセージを読むための鍵を持っているのは、あなたと受信者だけです。
 \n
@@ -1718,7 +1718,7 @@
     
     既に一覧に載っているサーバーです
     サーバーまたはそのルーム一覧が見つかりません
-    検索したい新しいサーバーの名前を入力してください。
+    探索したい新しいサーバーの名前を入力してください。
     新しいサーバーを追加
     あなたのサーバー
     暗号化されたメッセージの復元
@@ -1759,7 +1759,7 @@
     ゲストの参加を許可
     ルームへのアクセス
     履歴の閲覧権限に関する変更は、今後、このルームで表示されるメッセージにのみ適用されます。既存の履歴の見え方には影響しません。
-    無視して続行
+    無視して続行する
     毎回確認
     招待
     おすすめのルーム
@@ -1778,7 +1778,7 @@
     アカウント復旧用のメールアドレスを設定します。後からオプションで知人に見つけてもらえるようにできます。
     メールアドレスを設定
     メールアドレスを確認しました
-    検出可能なメールアドレス
+    発見可能なメールアドレス
     続行するには利用規約を承認してください
     ホームサーバーの利用規約を承認したら、再試行してください。
     次に
@@ -1791,18 +1791,18 @@
     ユーザー名やパスワードが正しくありません。 入力したパスワードは、スペースで開始または終了していますので、ご確認ください。
     そのユーザー名は既に使用されています
     ユーザー名
-    ユーザー名または電子メール
+    ユーザー名またはメールアドレス
     %sでサインイン
     %sでサインアップ
-    %sで続ける
+    %sで続行
     または
-    SSOを続行します
+    シングルサインオンで続行
     サインイン
     サインアップ
     Element Matrix Servicesに接続
     Matrix IDでサインイン
     Matrix IDでサインイン
-    もっと詳しく知る
+    詳しく知る
     その他
     カスタムと高度な設定
     組織向けのプレミアムホスティング
@@ -1814,16 +1814,16 @@
     エクスペリエンスを拡張およびカスタマイズ
     暗号化して会話をプライベートに保つ
     直接またはグループで連絡先とチャット
-    あなたの会話。 それを所有する。
+    自分の会話は、自分のものに。
     アカウント復旧用のメールアドレスを設定します。後からオプションで知人に見つけてもらえるようにできます。
     ここが%sとのダイレクトメッセージのスタート地点です。
     変更履歴はありません
     メッセージの変更履歴
-    ファイル %1$sをダウンロードしました!
-    ビデオの圧縮%d%%
+    ファイル %1$s をダウンロードしました!
+    ビデオを%d%%圧縮しています
     画像を圧縮しています…
     暗号化されたルームで完全な履歴を表示
-    フィードバックを与える
+    フィードバックを送信
     フィードバックを送信できませんでした (%s)
     ありがとうございます、あなたのフィードバックは正常に送信されました
     追加で確認が必要な事項がある場合は、連絡可
@@ -1837,13 +1837,13 @@
     app_id:
     push_key:
     登録されたプッシュゲートウェイはありません
-    プッシュルールが定義されていません
+    プッシュ通知に関するルールが定義されていません
     プッシュ通知に関するルール
     エキスパート
     クイックリアクション
-    あなたは既にこのルームを見ています!
+    あなたは既にこのルームを見ています!
     その他のサードパーティーの使用に関する掲示
-    Matrix SDK バージョン
+    Matrix SDKバージョン
     ファイル\"%1$s\"からエンドツーエンド暗号鍵をインポートします。
     鍵のバックアップデータの取得中にエラーが発生しました
     信頼情報の取得中にエラーが発生しました
@@ -1888,7 +1888,7 @@
     不明なエラー
     ユーザーの不一致
     鍵の不一致
-    無効なメッセージを受信しました
+    不正なメッセージを受信しました
     セッションに予期しないメッセージが表示されました
     SASが一致しませんでした
     ハッシュコミットメントが一致しませんでした
@@ -1904,23 +1904,23 @@
 \n%s
     リクエストはキャンセルされました
     鍵の検証
-    レガシー検証を使います。
-    何も表示されませんか?全てのクライアントがインタラクティブ検証をまだサポートしているわけではありません。その場合はレガシー検証を使用してください。
+    レガシー検証を使用する。
+    何も表示されませんか?インタラクティブな検証をサポートしてないクライアントを使っているかもしれません。その場合は、レガシー検証を使用してください。
     了解
-    このユーザーとのメッセージはエンドツーエンドで暗号化され第三者が読むことはできません。
+    このユーザーとのメッセージはエンドツーエンドで暗号化され、第三者が読むことはできません。
     完了しました!
     相手が確認するのを待っています…
     リクエストを見る
     検証リクエストを受信しました。
     相手の画面に次の番号が表示されていることを確認して、このセッションを検証
-    相手の画面に次の絵文字が表示されることを確認して、このセッションを確認します
+    相手の画面に次の絵文字が表示されることを確認して、このセッションを検証
     このセッションを検証すると、信頼済としてマークされ、自分も相手に信頼済としてマークされます。
-    このセッションを検証して、信頼済としてマークします。相手のセッションを信頼すると、さらに安心してエンドツーエンド暗号化を使用することができます。
-    入力された検証要求
-    検証を開始します
-    最大限のセキュリティーを確保するために、これを対面で行うか、別の信頼済の通信手段を用いることを推奨します。
+    このセッションを検証して、信頼済としてマークします。相手のセッションを信頼すると、より一層安心してエンドツーエンド暗号化を使用することができます。
+    検証リクエストがあります
+    検証を開始
+    セキュリティを最大限に高めるために、これを対面で行うか、他の信頼できる通信手段を使用することを推奨します。
     短い文字列を比較して検証します。
-    認証情報が不正または期限切れのため、ログアウトされました。
+    認証情報が不正または期限切れのため、ログアウトしました。
     構成を使用
     ${app_name}がuserIdドメイン\"%1$s\"のカスタムサーバー構成を検出しました。
 \n%2$s
@@ -1976,26 +1976,26 @@
     権限がありません
     音声メッセージを送信するには、マイクの権限を許可してください。
     この操作を実行するには、システム設定からカメラの権限を許可してください。
-    この操作を実行するための権限が不足しています。システム設定から権限を付与してください。
+    この操作を実行するための権限がありません。システム設定から権限を付与してください。
     IDサーバーに接続できませんでした
     IDサーバーのURLを入力
-    連絡先を発見するために、あなたの連絡先のデータ(電話番号や電子メール)を、設定されたIDサーバー(%1$s)に送信することを承認しますか?
+    連絡先を発見するために、あなたの連絡先のデータ(電話番号やメールアドレス)を、設定されたIDサーバー(%1$s)に送信することを承認しますか?
 \n
 \nプライバシーの保護のため、データは送信前にハッシュ化されます。
-    メールと電話番号を送信
+    メールアドレスと電話番号を送信
     同意する
     同意を撤回
-    あなたの連絡先から他のユーザーを発見するために、電子メールや電話番号をこのIDサーバーに送信することに同意していません。
-    あなたの連絡先から他のユーザーを発見するために、このIDサーバーにメールや電話番号を送信することに同意しています。
+    あなたの連絡先から他のユーザーを発見するために、メールアドレスや電話番号をこのIDサーバーに送信することに同意していません。
+    あなたの連絡先から他のユーザーを発見するために、メールアドレスや電話番号をこのIDサーバーに送信することに同意しています。
     メールと電話番号を送信
     未確認
     %sに確認メールを送りました。まず、メールを確認してリンクをクリックしてください
     %sに確認のためのメールを送りました。メールにて確認リンクをクリックしてください
-    検出可能な電話番号
+    発見可能な電話番号
     IDサーバーとの接続を解除すると、他のユーザーによって発見されなくなり、また、メールアドレスや電話で他のユーザーを招待することができなくなります。
     電話番号を追加すると、発見可能に設定する電話番号を選択できるようになります。
     メールアドレスを追加すると、発見可能に設定するメールアドレスを選択できるようになります。
-    現在、IDサーバーを使用していません。あなたの知っている連絡先を発見したり、その連絡先から発見されるようにするには、以下を設定してください。
+    現在、IDサーバーを使用していません。あなたの知っている連絡先を発見したり、その連絡先から発見されるようにするには、以下でIDサーバーを設定してください。
     あなたは現在%1$sを使って連絡先を見つけたり、連絡先から見つけられるようにしています。
     IDサーバーを変更
     IDサーバーの設定
@@ -2011,7 +2011,7 @@
     連絡先
     最近の
     入力すると検索結果が表示されます
-    結果が見つかりません、matrix IDを使ってサーバー上で検索してください。
+    結果が見つかりません。Matrix IDを使ってサーバー上で検索してください。
     クリップボードにコピーされたリンク
     メイン画面に未読通知専用のタブを追加する。
     ルーム名を検索
@@ -2033,9 +2033,9 @@
 \n- あなたの端末が使用しているインターネット接続
 \n
 \n設定画面からパスワードとリカバリーキーを早急に変更することを推奨します。
-    Eメール
+    電子メール
     アドレス
-    継続
+    続行する
     ファイル
     このユーザーはスペースから追放されます。
 \n
@@ -2064,7 +2064,7 @@
     現在のルームのスレッドを全て表示
     ユーザーをブロックすると、ユーザーはこのスペースから追放され、二度と参加できなくなります。
     PINコードを有効にする
-    これを招待者のみ参加可能に設定しました。
+    これを「招待者のみ参加可能」に設定しました。
     ルームの設定
     コンテンツが報告されました
     ヘルプとサポート
@@ -2077,13 +2077,13 @@
     ユーザーを自動的に招待
     ユーザー
     ブロックを解除すると、ユーザーはスペースに再び参加できるようになります。
-    このルームを招待者のみ参加可能に設定しました。
+    このルームを「招待者のみ参加可能」に設定しました。
     低優先度から削除
     低優先度に追加
     スパムとして報告済
     このルームにファイルはありません
     このルームにメディアはありません
-    公開ルームをアップグレード
+    公開されたルームをアップグレード
     非公開スペース
     公開スペース
     送信済
@@ -2119,9 +2119,9 @@
     警告
     警告
     成功しました!
-    続ける
+    続行する
     警告!
-    ロケーション
+    位置情報
     メディア
     投票終了
     投票を終了しますか?
@@ -2138,7 +2138,7 @@
     ルームのアップグレードには権限が必要です
     アップグレード
     アップグレードが必要です
-    非公開ルームをアップグレード
+    非公開のルームをアップグレード
     音声メッセージを一時停止
     音声メッセージを有効にする
     このメールアドレスをアカウントにリンク
@@ -2149,20 +2149,20 @@
     投票を終了
     選択肢%1$d
     選択肢を作成
-    ロケーション
-    ロケーションを共有
-    ロケーションの共有を有効にする
+    位置情報
+    位置情報を共有
+    位置情報の共有を有効にする
     地図を読み込めませんでした
     スレッドで返信
-    ロケーションを共有
-    ロケーションを共有
+    位置情報を共有
+    位置情報を共有
     カメラを開く
     画像と動画を送信
     ファイルをアップロード
     連絡先を開く
     ステッカーを送信
     投票を作成
-    ロケーションを共有
+    位置情報を共有
     スペースに関する変更を行うために必要な役割を更新する権限がありません
     スペースに関する変更を行うために必要な役割を選択
     スペースに関する変更を行うために必要な役割を表示し更新します。
@@ -2176,7 +2176,7 @@
     応答がありません
     スレッドへのリンクをコピー
     有効にする
-    もっと知る
+    詳しく知る
     あなたのIDサーバーのポリシー
     新しいルームを作成
     認証コードが正しくありません。
@@ -2205,7 +2205,7 @@
     暗号化が正しく設定されていないため、メッセージを送ることができません。クリックして設定を開いてください。
     暗号化が正しく設定されていないため、メッセージを送ることができません。管理者に連絡して、暗号化を正しい状態に復元してください。
     %2$dの%1$d
-    あなたは既にこのスレッドを見ています!
+    あなたは既にこのスレッドを見ています!
     ルームに表示
     ルームに表示
     スレッドを表示
@@ -2261,8 +2261,8 @@
     添付ファイルを送信
     詳細なログを有効にする。
     メールアドレスか電話番号でアカウントを見つけてもらえるようにするには、IDサーバー(%s)の利用規約への同意が必要です。
-    ${app_name}はロケーションにアクセスできませんでした
-    ${app_name}はロケーションにアクセスできませんでした。後でもう一度やり直してください。
+    ${app_name}は位置情報にアクセスできませんでした
+    ${app_name}は位置情報にアクセスできませんでした。後でもう一度やり直してください。
     音声メッセージ(%1$s)
     推奨のルームバージョンへとアップグレード
     音声メッセージを録音
@@ -2292,7 +2292,7 @@
     イベントを送信しました!
     送信に失敗した全てのメッセージを削除
     失敗しました
-    公開スペース
+    公開されたスペース
     公開されたルーム
     アバターを削除
     アバターを変更
@@ -2309,14 +2309,14 @@
     PINコードを忘れましたか?
     PINコードを入力
     PINコードを確認
-    ユーザーのロケーションをタイムラインに表示
-    一度有効にすると、ロケーションはどのルームにも送れるようになります
+    ユーザーの位置情報をタイムラインに表示
+    一度有効にすると、位置情報はどのルームにも送れるようになります
     サードパーティー製ライブラリー
     これはいつでも設定から無効にできます
     私たちは、情報を第三者と共有することはありません
     私たちは、アカウントのデータを記録したり分析したりすることはありません
     Elementの改善を手伝う
-    リンクを知っている人がアクセスできるようにこのルームを設定しました。
+    このルームを「リンクを知っている人が参加可能」に設定しました。
     どのユーザーも無視していません
     キーワードを入力するとリアクションを検索できます。
     変更を加えませんでした
@@ -2349,7 +2349,7 @@
     シェイクを検出しました!
     電話を振って、しきい値を試してください
     検出のしきい値
-    予期しないエラーが生じた際に、${app_name}はより頻繁にクラッシュするかもしれません
+    予期しないエラーが生じた際に、${app_name}はより頻繁にクラッシュする可能性があります
     素早くクラッシュ
     開発者モードは隠された機能を有効にするため、アプリケーションが不安定になる恐れがあります。開発者向けです!
     復号エラーが生じた際に、自動的にログを送信
@@ -2361,14 +2361,14 @@
     誰がアクセスできますか?
     通知は%1$sで管理できます。
     暗号化されたルームでのメンションとキーワードによる通知は、携帯端末では利用できません。
-    ユーザーを無視し、そのメッセージを非表示にします
+    ユーザーを無視し、そのメッセージを非表示に設定
     %sとのコマンドは認識されていますが、スレッドではサポートされていません。
     誰でもスペースを発見し参加できます
     法的情報
     ユーザーに関する情報を表示
     このルームにおいてのみアバターを変更
     このルームにおいてのみ表示名を変更
-    ユーザーの無視を解除し、以後のメッセージを表示します
+    ユーザーの無視を解除し、以後のメッセージを表示
     続行するには%sを入力してください
     復旧用のパスフレーズ
     有効なリカバリーキーではありません
@@ -2399,8 +2399,8 @@
     招待者のみ参加可能。個人やチームに最適
     スペースを作成
     連絡先をスペースに招待
-    IDサーバーは利用規約がありません
-    あなたの連絡先はプライベートです。端末の連絡先からユーザーを発見できるように、連絡先の情報をIDサーバーへ送信する許可が必要です。
+    IDサーバーには利用規約がありません
+    あなたの連絡先は非公開です。端末の連絡先からユーザーを発見するためには、連絡先の情報をIDサーバーに送信する許可が必要です。
     ディスカバリー設定を開く
     既読時間
     クロス証明を有効にする
@@ -2410,9 +2410,9 @@
     %sが参加しました。
     このルームで使用されている暗号化はサポートされていません
     暗号化が正しく設定されていません
-    %sにテキストメッセージを送信しました。メッセージにある確認コードを入力してください。
+    %sにテキストメッセージを送信しました。メッセージ内の確認コードを入力してください。
     選択したIDサーバーには利用規約がありません。サービス提供者を信頼している場合にのみ続行してください
-    現在IDサーバー %1$s でメールアドレスや電話番号を共有しています。共有を停止するには %2$s に再接続する必要があります。
+    現在IDサーバー %1$s でメールアドレスや電話番号を共有しています。共有を停止するには%2$sに再接続する必要があります。
     ルームを作成し設定しました。
     QRコードを読み取り、新しいダイレクトメッセージを作成
     鍵のインポートに失敗しました
@@ -2429,7 +2429,7 @@
     この投票を削除してよろしいですか?一度削除すると復元することはできません。
     共有データの取り扱いに失敗しました
     回転とクロップ
-    ルームを探す
+    ルームを探索
     既存のルームとスペースを追加
     スレッドのメッセージを有効にする
     おすすめに追加
@@ -2479,21 +2479,21 @@
     検証済
     未送信のメッセージを削除
     カスタムイベントを送信
-    ルームの状態を調べる
+    ルームの状態を探索
     開封確認メッセージを表示
     通知しない
     ファイルから鍵をインポート
     未確認
     制限は不明です。
     サーバーのファイルアップロードの制限
-    自分の会話は、自分のものに。
-    %1$sが、このルームを、招待者のみ参加可能に設定しました。
+    自分の会話は、自分のもの。
+    %1$sがこのルームを「招待者のみ参加可能」に設定しました。
     %1$sが部屋をリンクを持っているユーザーにアクセスできるように設定しました。
     選択したメッセージをネタバレとして送信
     ${app_name} がエンドツーエンド暗号鍵をディスクに保存する許可を要求しています。
 \n
 \n鍵を手動でエクスポートできるように、次のポップアップでアクセスを許可してください。
-    %1$sはリンクを知っている人がアクセスできるようにこのルームを設定しました。
+    %1$sはこのルームを「リンクを知っている人が参加可能」に設定しました。
     初めに設定画面でIDサーバーの利用規約を承認してください。
     初めにIDサーバーを設定してください。
     
@@ -2507,7 +2507,7 @@
     降雪❄️を送る
     あなたのチームのメッセージングに。
     エンドツーエンドで暗号化され、電話番号不要。広告やデータマイニング無し。
-    会話の保存先を自分で決められ、自分で管理できる独立したコミュニケーション。Matrixを基に。
+    会話の保存先を自分で決められ、自分で管理できる独立したコミュニケーション。Matrixをもとに。
     お宅での対面会話と同じぐらいのプライバシーを提供する、セキュアで独立したコミュニケーション。
     チームワークを効率よくしましょう。
     セキュアメッセージング
@@ -2553,7 +2553,7 @@
     新しいセッションが認証されました。セッションは暗号化されたメッセージにアクセスでき、他のユーザーには信頼済として表示されます。
     いったん有効にすると、暗号化を無効にすることはできません。
     このルームを同じホームサーバー上で組織内のチームとのコラボレーションにのみ使用するなら、このオプションを有効にするといいかもしれません。これは後から変更できません。
-    %sに属していないユーザーによるこのルームへの参加を、今後永久的に拒否
+    %sに属していないユーザーによるこのルームへの参加を、今後永久に拒否
     プレーンテキストメッセージの前に ( ͡° ͜ʖ ͡°) を付ける
     このメールアドレスのドメインの登録は許可されていません
     スペースを作成しています…
@@ -2578,7 +2578,7 @@
     既存のサーバーに参加しますか?
     この質問をスキップ
     友達と家族
-    %1$sがこれを招待者のみ参加可能に設定しました。
+    %1$sがこれを「招待者のみ参加可能」に設定しました。
     メッセージを送信できませんでした
     ウィジェットを開く
     %1$sに転送
@@ -2630,13 +2630,13 @@
     コードを%1$sに送信しました。以下に入力して認証してください。
     このメールアドレスはどのアカウントにも登録されていません
     パスワードを変更すると、すべてのセッションでのエンドツーエンド暗号鍵がリセットされ、暗号化されたメッセージ履歴が読めなくなります。パスワードを再設定する前に、鍵のバックアップを設定するか、他のセッションから鍵をエクスポートしておいてください。
-    パスワードの再設定を確認するために認証メールを送信しました。
+    パスワードの再設定を確認するために認証メールを送信します。
     このメールアドレスはどのアカウントにも属していません。
     このアプリではこのホームサーバーにアカウントを作成できません。
 \n
 \nウェブクライエントを使用してアカウント登録しますか?
     申し訳ありませんが、このサーバーはアカウントの新規登録を受け入れていません。
-    このアプリではこのホームサーバーにサインインできません。このホームサーバーは次のサインイン方法に対応しています: %1$s
+    このアプリではこのホームサーバーにサインインできません。このホームサーバーは次のサインインの方法に対応しています:%1$s
 \n
 \nウェブクライエントを使用してサインインしますか?
     %1$sを読み込み中にエラーが発生しました(%2$d)
@@ -2695,7 +2695,7 @@
     カメラを停止
     セキュリティーキーを保存
     リカバリーキー
-    位置を共有しました
+    位置情報を共有しました
     %sでリアクションしました
     検証終了
     次のいずれかのセキュリティが破られている可能性があります。
@@ -2705,25 +2705,25 @@
 \n - あなたか相手のインターネット接続
 \n - あなたか相手の端末
     セキュアではない
-    この緑の盾のシンボルが信頼できるユーザーの印です。部屋のセキュリティを確認するには、全ての参加者に信頼できることを確認してください。
-    セキュリティを最大限に高めるには、対面で行うか他の信頼できる通信媒体を利用してください。
+    信頼済のユーザーには、この緑の盾のシンボルが表示されます。全てのユーザーについて信頼の設定作業を行うと、ルームの安全性を確保することができます。
+    セキュリティを最大限に高めるには、対面で行うか、他の信頼できる通信手段を使用してください。
     次の絵文字が相手の画面にも同じ順番で現れるのを確認し、このユーザーを検証してください。
     信頼できないサインイン
     使用できない文字が含まれています
-    Elementの改善と課題抽出のために、匿名の使用分析データを収集させてくださいませんか?複数の端末での使用を分析するために、あなたの全端末共通のランダムな識別子を生成します。
+    Elementの改善と課題抽出のために、匿名の使用状況データの送信をお願いします。複数の端末での使用を分析するために、あなたの全端末共通のランダムな識別子を生成します。
 \n
 \n%sで利用規約を閲覧できます。
-    最初の検索結果のみ表示中。文字をもっと入力してください…
+    最初の検索結果のみ表示しています。文字をもっと入力してください…
     matrix.toリンクのフォーマットが正しくありませんでした
     注意!この端末には暗号鍵を含む個人情報が保存されています。
 \n
-\nこの端末での使用を終了したり、他のアカウントにサインインしたい場合、このデータをクリアしてください。
-    この端末に現在保存されているすべてのデータをクリアしますか?
+\nこの端末での使用を終了、または他のアカウントにサインインしたい場合、このデータをクリアしてください。
+    この端末に現在保存されている全てのデータをクリアしますか?
 \nアカウントデータとメッセージにアクセスするにはもう一度サインインしてください。
     現在のセッションはユーザー %1$s のものですが、あなたが提供している認証情報はユーザー %2$s のものです。この操作は${app_name}ではサポートされていません。
 \nまずデータをクリアし、その後、別のアカウントにサインインしてください。
     暗号化されたメッセージがどの端末でも読めるように、サインインしてこの端末にのみ保存されている暗号鍵を取り戻してください。
-    あなたのホームサーバー(%1$s)の管理者があなたを%2$sのアカウントからサインアウトしています。(%3$s)
+    あなたのホームサーバー(%1$s)の管理者があなたを%2$sのアカウントからサインアウトしました(%3$s)。
     いくつかの原因が考えられます:
 \n
 \n• 他のセッションでパスワードを変更している。
@@ -2739,7 +2739,7 @@
     このホームサーバーのバージョンは古すぎるため、接続できません。管理者にアップグレードを要請してください。
     ホームサーバーのバージョンが古すぎます
     ただいま%1$sにメールを送信しました。
-\nアカウント登録を続けるにはメールに含まれたリンクをクリックしてください。
+\nアカウント登録を続行するにはメール内のリンクをクリックしてください。
     CAPTCHA認証を行ってください
     アカウントがまだ登録されていません。
 \n
@@ -2750,7 +2750,7 @@
     セキュリティーフレーズを使用
     セキュリティーキーは、パスワードマネージャーもしくは金庫のような安全な場所で保管してください。
     セキュリティーキーは、パスワードマネージャーもしくは金庫のような安全な場所で保管してください。
-    セキュリティーキーを生成し、パスワードマネージャーもしくは金庫のような安全な場所で保管してください。
+    セキュリティーキーを生成します。パスワードマネージャーもしくは金庫のような安全な場所で保管してください。
     セキュアバックアップ
     セキュアバックアップを設定
     会話を開く
@@ -2759,7 +2759,7 @@
     %sの利用規約を開く
     ユーザーによる同意は与えられていません。
     代わりに、他のIDサーバーのURLを入力できます
-    サーバー上の暗号化キーをバックアップすることにより、暗号化されたメッセージとデータへのアクセスが失われるのを防ぎます。
+    サーバー上の暗号鍵をバックアップして、暗号化されたメッセージとデータへのアクセスが失われるのを防ぎましょう。
     いまキャンセルすると、ログインできなくなった際に、暗号化されたメッセージとデータを失ってしまう可能性があります。
 \n
 \nまた、設定から、安全なバックアップの設定や鍵の管理を行うことができます。
@@ -2768,7 +2768,7 @@
     自己署名キーを同期しています
     ユーザーキーを同期しています
     マスターキーを同期しています
-    SSSS デフォルトキーを規定しています
+    SSSSデフォルトキーを規定しています
     安全な鍵をパスフレーズから生成しています
     メッセージキーを生成
     暗号化されたメッセージのロックを解除
@@ -2810,4 +2810,12 @@
         いま検証できる%d個の端末を表示
     
     この操作を実行するには ${app_name}に認証情報を入力する必要があります。
+    あなただけが知っている秘密のパスワードを入力してください。バックアップ用にセキュリティーキーを生成します。
+    暗号化されたメッセージにアクセスするには、ログインを検証し、本人確認を行う必要があります。
+    暗号化されたメッセージにアクセスするには、あなたの他のセッションからログインを検証し、本人確認を行う必要があります。
+    詳しく知る
+    セキュリティーを確保するために、使い捨てコードをチェックして、%sを検証してください。
+    よりセキュリティーを確保するために、両方の端末で使い捨てコードをチェックして、%sを検証してください。
+\n
+\n最大限のセキュリティーを確保するために、ご自身の目で直接確かめて行ってください。
 
\ No newline at end of file

From 50685448246dac5a867334e779e2cd80dae82d35 Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Tue, 22 Feb 2022 01:01:12 +0000
Subject: [PATCH 540/581] Translated using Weblate (Japanese)

Currently translated at 91.6% (2552 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index b604f11442f..b170753754c 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -1579,7 +1579,7 @@
     電話番号の認証中にエラーが発生しました。
     リンクを共有
     %sに招待
-    Eメールで招待
+    電子メールで招待
     詳細
     連絡先を招待
     無視して参加

From 8bfcb1d9d8992059bfd0e1ed64a81aa2e83a3303 Mon Sep 17 00:00:00 2001
From: Onuray Sahin 
Date: Tue, 22 Feb 2022 12:05:45 +0300
Subject: [PATCH 541/581] Allow creating disclosed polls.

---
 .../im/vector/app/features/poll/create/CreatePollController.kt  | 2 --
 1 file changed, 2 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/poll/create/CreatePollController.kt b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollController.kt
index 6ccf7fc6aa3..b4f61dbc1f4 100644
--- a/vector/src/main/java/im/vector/app/features/poll/create/CreatePollController.kt
+++ b/vector/src/main/java/im/vector/app/features/poll/create/CreatePollController.kt
@@ -54,7 +54,6 @@ class CreatePollController @Inject constructor(
             title(host.stringProvider.getString(R.string.poll_type_title).toEpoxyCharSequence())
         }
 
-        /*
         pollTypeSelectionItem {
             id("poll_type_selection")
             pollType(currentState.pollType)
@@ -68,7 +67,6 @@ class CreatePollController @Inject constructor(
                 )
             }
         }
-         */
 
         genericItem {
             id("question_title")

From a858e89ca1d0ea12858bd5841ce2e30959e976ce Mon Sep 17 00:00:00 2001
From: Onuray Sahin 
Date: Tue, 22 Feb 2022 12:15:37 +0300
Subject: [PATCH 542/581] Changelog added.

---
 changelog.d/5290.feature | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 changelog.d/5290.feature

diff --git a/changelog.d/5290.feature b/changelog.d/5290.feature
new file mode 100644
index 00000000000..6f7e9aea7fd
--- /dev/null
+++ b/changelog.d/5290.feature
@@ -0,0 +1 @@
+Support creating disclosed polls
\ No newline at end of file

From 89e97748bc617a8cae0e9e48fa987b4393397112 Mon Sep 17 00:00:00 2001
From: NIkita Fedrunov 
Date: Tue, 22 Feb 2022 10:24:27 +0100
Subject: [PATCH 543/581] ScreenEvent removed

---
 .../app/core/platform/VectorBaseActivity.kt   |  3 +-
 .../VectorBaseBottomSheetDialogFragment.kt    |  3 +-
 .../app/core/platform/VectorBaseFragment.kt   |  3 +-
 .../features/analytics/screen/ScreenEvent.kt  | 46 -------------------
 .../features/call/dialpad/DialPadFragment.kt  |  4 +-
 .../vector/app/features/home/HomeActivity.kt  |  3 +-
 .../home/room/detail/RoomDetailActivity.kt    |  3 +-
 .../settings/VectorSettingsBaseFragment.kt    |  3 +-
 8 files changed, 7 insertions(+), 61 deletions(-)
 delete mode 100644 vector/src/main/java/im/vector/app/features/analytics/screen/ScreenEvent.kt

diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt
index 6c49a1ff01f..2c161feb378 100644
--- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt
+++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt
@@ -68,7 +68,6 @@ import im.vector.app.features.MainActivity
 import im.vector.app.features.MainActivityArgs
 import im.vector.app.features.analytics.AnalyticsTracker
 import im.vector.app.features.analytics.plan.MobileScreen
-import im.vector.app.features.analytics.screen.ScreenEvent
 import im.vector.app.features.configuration.VectorConfiguration
 import im.vector.app.features.consent.ConsentNotGivenHelper
 import im.vector.app.features.navigation.Navigator
@@ -337,7 +336,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), Maver
         super.onResume()
         Timber.i("onResume Activity ${javaClass.simpleName}")
         analyticsScreenName?.let {
-            ScreenEvent(it).send(analyticsTracker)
+            analyticsTracker.screen(MobileScreen(screenName = it))
         }
         configurationViewModel.onActivityResumed()
 
diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt
index 11bfcdd9ef5..ddc281fdd14 100644
--- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt
+++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt
@@ -39,7 +39,6 @@ import im.vector.app.core.extensions.toMvRxBundle
 import im.vector.app.core.utils.DimensionConverter
 import im.vector.app.features.analytics.AnalyticsTracker
 import im.vector.app.features.analytics.plan.MobileScreen
-import im.vector.app.features.analytics.screen.ScreenEvent
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import reactivecircus.flowbinding.android.view.clicks
@@ -139,7 +138,7 @@ abstract class VectorBaseBottomSheetDialogFragment : BottomShe
         super.onResume()
         Timber.i("onResume BottomSheet ${javaClass.simpleName}")
         analyticsScreenName?.let {
-            ScreenEvent(it).send(analyticsTracker)
+            analyticsTracker.screen(MobileScreen(screenName = it))
         }
     }
 
diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt
index 7b8eda76ff4..70b265ff9fb 100644
--- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt
+++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt
@@ -45,7 +45,6 @@ import im.vector.app.core.extensions.toMvRxBundle
 import im.vector.app.core.utils.ToolbarConfig
 import im.vector.app.features.analytics.AnalyticsTracker
 import im.vector.app.features.analytics.plan.MobileScreen
-import im.vector.app.features.analytics.screen.ScreenEvent
 import im.vector.app.features.navigation.Navigator
 import im.vector.lib.ui.styles.dialogs.MaterialProgressDialog
 import kotlinx.coroutines.flow.launchIn
@@ -145,7 +144,7 @@ abstract class VectorBaseFragment : Fragment(), MavericksView
         super.onResume()
         Timber.i("onResume Fragment ${javaClass.simpleName}")
         analyticsScreenName?.let {
-            ScreenEvent(it).send(analyticsTracker)
+            analyticsTracker.screen(MobileScreen(screenName = it))
         }
     }
 
diff --git a/vector/src/main/java/im/vector/app/features/analytics/screen/ScreenEvent.kt b/vector/src/main/java/im/vector/app/features/analytics/screen/ScreenEvent.kt
deleted file mode 100644
index 400bede415b..00000000000
--- a/vector/src/main/java/im/vector/app/features/analytics/screen/ScreenEvent.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * 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.analytics.screen
-
-import im.vector.app.features.analytics.AnalyticsTracker
-import im.vector.app.features.analytics.plan.MobileScreen
-import timber.log.Timber
-
-/**
- * Track a screen display. Unique usage.
- */
-class ScreenEvent(val screenName: MobileScreen.ScreenName) {
-    // Protection to avoid multiple sending
-    private var isSent = false
-
-    /**
-     * @param screenNameOverride can be used to override the screen name passed in constructor parameter
-     */
-    fun send(analyticsTracker: AnalyticsTracker,
-             screenNameOverride: MobileScreen.ScreenName? = null) {
-        if (isSent) {
-            Timber.w("Event $screenName Already sent!")
-            return
-        }
-        isSent = true
-        analyticsTracker.screen(
-                MobileScreen(
-                        screenName = screenNameOverride ?: screenName
-                )
-        )
-    }
-}
diff --git a/vector/src/main/java/im/vector/app/features/call/dialpad/DialPadFragment.kt b/vector/src/main/java/im/vector/app/features/call/dialpad/DialPadFragment.kt
index 9d77680ff52..606ba1d1e90 100644
--- a/vector/src/main/java/im/vector/app/features/call/dialpad/DialPadFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/call/dialpad/DialPadFragment.kt
@@ -41,7 +41,6 @@ import im.vector.app.R
 import im.vector.app.core.extensions.singletonEntryPoint
 import im.vector.app.features.analytics.AnalyticsTracker
 import im.vector.app.features.analytics.plan.MobileScreen
-import im.vector.app.features.analytics.screen.ScreenEvent
 import im.vector.app.features.themes.ThemeUtils
 
 class DialPadFragment : Fragment(), TextWatcher {
@@ -66,10 +65,9 @@ class DialPadFragment : Fragment(), TextWatcher {
         analyticsTracker = singletonEntryPoint.analyticsTracker()
     }
 
-    private var screenEvent: ScreenEvent? = null
     override fun onResume() {
         super.onResume()
-        ScreenEvent(MobileScreen.ScreenName.Dialpad).send(analyticsTracker)
+        analyticsTracker.screen(MobileScreen(screenName = MobileScreen.ScreenName.Dialpad))
     }
 
     override fun onCreateView(
diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt
index b7bfdef21c6..47f1a9208b7 100644
--- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt
@@ -49,7 +49,6 @@ import im.vector.app.features.MainActivity
 import im.vector.app.features.MainActivityArgs
 import im.vector.app.features.analytics.accountdata.AnalyticsAccountDataViewModel
 import im.vector.app.features.analytics.plan.MobileScreen
-import im.vector.app.features.analytics.screen.ScreenEvent
 import im.vector.app.features.disclaimer.showDisclaimerDialog
 import im.vector.app.features.matrixto.MatrixToBottomSheet
 import im.vector.app.features.navigation.Navigator
@@ -164,7 +163,7 @@ class HomeActivity :
 
     private val drawerListener = object : DrawerLayout.SimpleDrawerListener() {
         override fun onDrawerOpened(drawerView: View) {
-            ScreenEvent(MobileScreen.ScreenName.Sidebar).send(analyticsTracker)
+            analyticsTracker.screen(MobileScreen(screenName = MobileScreen.ScreenName.Sidebar))
         }
 
         override fun onDrawerStateChanged(newState: Int) {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt
index f40bee44db8..aa4ee825dc3 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailActivity.kt
@@ -36,7 +36,6 @@ import im.vector.app.core.extensions.replaceFragment
 import im.vector.app.core.platform.VectorBaseActivity
 import im.vector.app.databinding.ActivityRoomDetailBinding
 import im.vector.app.features.analytics.plan.MobileScreen
-import im.vector.app.features.analytics.screen.ScreenEvent
 import im.vector.app.features.home.room.breadcrumbs.BreadcrumbsFragment
 import im.vector.app.features.home.room.detail.arguments.TimelineArgs
 import im.vector.app.features.home.room.detail.timeline.helper.VoiceMessagePlaybackTracker
@@ -159,7 +158,7 @@ class RoomDetailActivity :
 
     private val drawerListener = object : DrawerLayout.SimpleDrawerListener() {
         override fun onDrawerOpened(drawerView: View) {
-            ScreenEvent(MobileScreen.ScreenName.Breadcrumbs).send(analyticsTracker)
+            analyticsTracker.screen(MobileScreen(screenName = MobileScreen.ScreenName.Breadcrumbs))
         }
 
         override fun onDrawerStateChanged(newState: Int) {
diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsBaseFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsBaseFragment.kt
index c907168954c..dae234eecc6 100644
--- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsBaseFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsBaseFragment.kt
@@ -31,7 +31,6 @@ import im.vector.app.core.platform.VectorBaseActivity
 import im.vector.app.core.utils.toast
 import im.vector.app.features.analytics.AnalyticsTracker
 import im.vector.app.features.analytics.plan.MobileScreen
-import im.vector.app.features.analytics.screen.ScreenEvent
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import org.matrix.android.sdk.api.session.Session
@@ -91,7 +90,7 @@ abstract class VectorSettingsBaseFragment : PreferenceFragmentCompat(), Maverick
         super.onResume()
         Timber.i("onResume Fragment ${javaClass.simpleName}")
         analyticsScreenName?.let {
-            ScreenEvent(it).send(analyticsTracker)
+            analyticsTracker.screen(MobileScreen(screenName = it))
         }
         vectorActivity.supportActionBar?.setTitle(titleRes)
         // find the view from parent activity

From bad45799b97d7b832a7398bc9af54930c16ea321 Mon Sep 17 00:00:00 2001
From: Adam Brown 
Date: Tue, 22 Feb 2022 09:45:20 +0000
Subject: [PATCH 544/581] reducing line length (using existing formatting
 style)

---
 .../main/java/im/vector/app/features/home/AvatarRenderer.kt  | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt b/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt
index 5b96caa3f10..3678808b2d2 100644
--- a/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt
+++ b/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt
@@ -167,7 +167,10 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active
 
     @AnyThread
     @Throws
-    fun adaptiveShortcutDrawable(glideRequests: GlideRequests, matrixItem: MatrixItem, iconSize: Int, adaptiveIconSize: Int, adaptiveIconOuterSides: Float): Bitmap {
+    fun adaptiveShortcutDrawable(glideRequests: GlideRequests,
+                                 matrixItem: MatrixItem, iconSize: Int,
+                                 adaptiveIconSize: Int,
+                                 adaptiveIconOuterSides: Float): Bitmap {
         return glideRequests
                 .asBitmap()
                 .avatarOrText(matrixItem, iconSize)

From 9726c8ae07d9d23af2bb8fd2448bdef3c08fd053 Mon Sep 17 00:00:00 2001
From: Joe Sagawa 
Date: Tue, 22 Feb 2022 09:54:25 +0000
Subject: [PATCH 545/581] Translated using Weblate (Japanese)

Currently translated at 91.6% (2553 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index b170753754c..c920754ca49 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2779,9 +2779,9 @@
     ここで送受信されるメッセージはエンドツーエンド暗号化されています。
 \n
 \nメッセージは安全に保護されており、メッセージのロックを解除するための固有の鍵は、あなたと受信者だけが持っています。
-    このルームのメッセージはエンドツーエンド暗号化されています。
+    この部屋のメッセージはエンドツーエンド暗号化されています。
 \n
-\nメッセージは安全に保護されており、メッセージのロックを解除するための固有の鍵は、あなたと受信者だけが持っています。
+\nメッセージは安全に保護されており、メッセージのロックを解除するための固有の鍵を持っているのはあなたと受信者だけです。
     ステートキー
     リカバリーキーを以下に保存
     送信者が意図的に鍵を送信しなかったため、このメッセージにアクセスすることができません
@@ -2815,7 +2815,7 @@
     暗号化されたメッセージにアクセスするには、あなたの他のセッションからログインを検証し、本人確認を行う必要があります。
     詳しく知る
     セキュリティーを確保するために、使い捨てコードをチェックして、%sを検証してください。
-    よりセキュリティーを確保するために、両方の端末で使い捨てコードをチェックして、%sを検証してください。
+    セキュリティーを高めるために、自分と相手の端末でコードが一致していることを確認して、%sを検証しましょう。
 \n
-\n最大限のセキュリティーを確保するために、ご自身の目で直接確かめて行ってください。
+\n対面で行うのが最もセキュアです。
 
\ No newline at end of file

From a9493db5841bc7f8dbdebf82be799a74957c3489 Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Tue, 22 Feb 2022 09:18:20 +0000
Subject: [PATCH 546/581] Translated using Weblate (Japanese)

Currently translated at 91.6% (2553 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index c920754ca49..ac43dd3e8b9 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -1459,7 +1459,7 @@
     絵文字キーボードを表示
     アバターと表示名の変更を含む。
     アカウントのイベントを表示
-    招待、削除、ブロックは影響を受けません。
+    招待、追放、ブロックは影響を受けません。
     招待/参加/退出/追放/ブロックに関するイベントや、アバター/表示名の変更などを含む。
     参加・退出イベントを表示
     /confettiコマンドを使用するか、❄️または🎉を含むメッセージを送信
@@ -1759,7 +1759,7 @@
     ゲストの参加を許可
     ルームへのアクセス
     履歴の閲覧権限に関する変更は、今後、このルームで表示されるメッセージにのみ適用されます。既存の履歴の見え方には影響しません。
-    無視して続行する
+    無視して続行
     毎回確認
     招待
     おすすめのルーム
@@ -2818,4 +2818,5 @@
     セキュリティーを高めるために、自分と相手の端末でコードが一致していることを確認して、%sを検証しましょう。
 \n
 \n対面で行うのが最もセキュアです。
+    暗号化が正しく設定されていません。
 
\ No newline at end of file

From b699701b5c86e58ee127ccc52128a83120ed10ec Mon Sep 17 00:00:00 2001
From: Joe Sagawa 
Date: Tue, 22 Feb 2022 09:58:17 +0000
Subject: [PATCH 547/581] Translated using Weblate (Japanese)

Currently translated at 91.7% (2555 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index ac43dd3e8b9..489b1da9156 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2778,10 +2778,10 @@
     質問は空にできません
     ここで送受信されるメッセージはエンドツーエンド暗号化されています。
 \n
-\nメッセージは安全に保護されており、メッセージのロックを解除するための固有の鍵は、あなたと受信者だけが持っています。
+\nメッセージは安全に保護されており、メッセージのロックを解除できる固有の鍵を持っているのはあなたと受信者だけです。
     この部屋のメッセージはエンドツーエンド暗号化されています。
 \n
-\nメッセージは安全に保護されており、メッセージのロックを解除するための固有の鍵を持っているのはあなたと受信者だけです。
+\nメッセージは安全に保護されており、メッセージのロックを解除できる固有の鍵を持っているのはあなたと受信者だけです。
     ステートキー
     リカバリーキーを以下に保存
     送信者が意図的に鍵を送信しなかったため、このメッセージにアクセスすることができません
@@ -2818,5 +2818,7 @@
     セキュリティーを高めるために、自分と相手の端末でコードが一致していることを確認して、%sを検証しましょう。
 \n
 \n対面で行うのが最もセキュアです。
-    暗号化が正しく設定されていません。
+    暗号化の設定が正しくありません。
+    暗号化を復元する
+    暗号化を有効な状態に取り戻すために、管理者に連絡してください。
 
\ No newline at end of file

From f187bffc11104c6f8326d8d5c4ee3e147abb2786 Mon Sep 17 00:00:00 2001
From: Sonia 
Date: Tue, 22 Feb 2022 11:11:31 +0100
Subject: [PATCH 548/581] changes roomUnreadIndicator colour in item_room.xml

---
 vector/src/main/res/layout/item_room.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/vector/src/main/res/layout/item_room.xml b/vector/src/main/res/layout/item_room.xml
index 68e3a85008f..7dc7984b4a1 100644
--- a/vector/src/main/res/layout/item_room.xml
+++ b/vector/src/main/res/layout/item_room.xml
@@ -15,7 +15,7 @@
         android:id="@+id/roomUnreadIndicator"
         android:layout_width="4dp"
         android:layout_height="0dp"
-        android:background="?colorSecondary"
+        android:background="?vctr_content_secondary"
         android:visibility="gone"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintStart_toStartOf="parent"

From 344bc0e3fe2eb99435c32a3f1d71eb253274b9b7 Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Tue, 22 Feb 2022 10:20:38 +0000
Subject: [PATCH 549/581] Translated using Weblate (Japanese)

Currently translated at 92.0% (2564 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 489b1da9156..4bc5ff4a298 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -278,7 +278,7 @@
     言語
     インターフェース
     認証を確認中
-    電子メールを確認して、本文中のURLをクリックしてください。 完了したら「続行する」をクリックしてください。
+    電子メールを確認して、本文中のURLをクリックしてください。完了したら「続行する」をクリックしてください。
     このメールアドレスは既に使われています。
     あなたのパスワードは更新されました
     国を選択
@@ -1875,7 +1875,7 @@
     ルーム
     会話
     ここで未読メッセージに追いつく
-    ホームへようこそ!
+    ホームにようこそ!
     未読メッセージはありません
     未読はありません!
     %sがセッションの検証を要求しています
@@ -2819,6 +2819,6 @@
 \n
 \n対面で行うのが最もセキュアです。
     暗号化の設定が正しくありません。
-    暗号化を復元する
+    暗号化を復元
     暗号化を有効な状態に取り戻すために、管理者に連絡してください。
 
\ No newline at end of file

From bc8a88c351ea8bfbb1d2ba72e7bdd0561250ec70 Mon Sep 17 00:00:00 2001
From: Joe Sagawa 
Date: Tue, 22 Feb 2022 10:09:36 +0000
Subject: [PATCH 550/581] Translated using Weblate (Japanese)

Currently translated at 92.0% (2564 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 4bc5ff4a298..e7a5d53d4a5 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2814,11 +2814,21 @@
     暗号化されたメッセージにアクセスするには、ログインを検証し、本人確認を行う必要があります。
     暗号化されたメッセージにアクセスするには、あなたの他のセッションからログインを検証し、本人確認を行う必要があります。
     詳しく知る
-    セキュリティーを確保するために、使い捨てコードをチェックして、%sを検証してください。
+    セキュリティー高めるために、使い捨てコードが一致しているのを確認して、%sを検証しましょう。
     セキュリティーを高めるために、自分と相手の端末でコードが一致していることを確認して、%sを検証しましょう。
 \n
 \n対面で行うのが最もセキュアです。
     暗号化の設定が正しくありません。
     暗号化を復元
     暗号化を有効な状態に取り戻すために、管理者に連絡してください。
+    このユーザーとのメッセージがエンドツーエンド暗号化されており、第三者には読めません。
+    このコードを相手の画面に現れているコードと比較してください。
+    絵文字を比較して、同じ順番に現れているのを確認してください。
+    対面で行うか、他の信頼できる通信媒体を利用するのが最もセキュアです。
+    選択されたエモートを虹色にして送信します
+    選択されたテキストを虹色にして送信します
+    ${app_name}がID%1$sのイベントを処理中にエラーが発生しました
+    ${app_name}は%1$sという種類のメッセージに対応していません
+    ${app_name}は%1$sという種類のイベントに対応していません
+    既読通知へ移動
 
\ No newline at end of file

From ff0b0f293b62e1020265002cdea254d8a58835dc Mon Sep 17 00:00:00 2001
From: Sonia0912 <41631672+Sonia0912@users.noreply.github.com>
Date: Tue, 22 Feb 2022 11:45:51 +0100
Subject: [PATCH 551/581] adds changelog file

---
 changelog.d/5294.misc | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 changelog.d/5294.misc

diff --git a/changelog.d/5294.misc b/changelog.d/5294.misc
new file mode 100644
index 00000000000..857110efab8
--- /dev/null
+++ b/changelog.d/5294.misc
@@ -0,0 +1 @@
+Changes unread marker in room list from green to grey
\ No newline at end of file

From fcca75ee235edfa442bfb0c3b4298a42b2827311 Mon Sep 17 00:00:00 2001
From: ganfra 
Date: Tue, 22 Feb 2022 12:45:10 +0100
Subject: [PATCH 552/581] Realm: remove usage of freeze as it was not necessary
 (unique thread)

---
 .../session/room/timeline/TimelineChunk.kt    | 21 +++++++++----------
 1 file changed, 10 insertions(+), 11 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt
index 8507b63d1f7..25957de1b58 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt
@@ -90,8 +90,7 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity,
     private val timelineEventsChangeListener =
             OrderedRealmCollectionChangeListener { results: RealmResults, changeSet: OrderedCollectionChangeSet ->
                 Timber.v("on timeline events chunk update")
-                val frozenResults = results.freeze()
-                handleDatabaseChangeSet(frozenResults, changeSet)
+                handleDatabaseChangeSet(results, changeSet)
             }
 
     private var timelineEventEntities: RealmResults = chunkEntity.sortedTimelineEvents(timelineSettings.rootThreadEventId)
@@ -287,7 +286,7 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity,
      * @return the number of events loaded. If we are in a thread timeline it also returns
      * whether or not we reached the end/root message
      */
-    private suspend fun loadFromStorage(count: Int, direction: Timeline.Direction): LoadedFromStorage {
+    private fun loadFromStorage(count: Int, direction: Timeline.Direction): LoadedFromStorage {
         val displayIndex = getNextDisplayIndex(direction) ?: return LoadedFromStorage()
         val baseQuery = timelineEventEntities.where()
 
@@ -428,10 +427,10 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity,
      * This method is responsible for managing insertions and updates of events on this chunk.
      *
      */
-    private fun handleDatabaseChangeSet(frozenResults: RealmResults, changeSet: OrderedCollectionChangeSet) {
+    private fun handleDatabaseChangeSet(results: RealmResults, changeSet: OrderedCollectionChangeSet) {
         val insertions = changeSet.insertionRanges
         for (range in insertions) {
-            val newItems = frozenResults
+            val newItems = results
                     .subList(range.startIndex, range.startIndex + range.length)
                     .map { it.buildAndDecryptIfNeeded() }
             builtEventsIndexes.entries.filter { it.value >= range.startIndex }.forEach { it.setValue(it.value + range.length) }
@@ -447,7 +446,7 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity,
         val modifications = changeSet.changeRanges
         for (range in modifications) {
             for (modificationIndex in (range.startIndex until range.startIndex + range.length)) {
-                val updatedEntity = frozenResults[modificationIndex] ?: continue
+                val updatedEntity = results[modificationIndex] ?: continue
                 try {
                     builtEvents[modificationIndex] = updatedEntity.buildAndDecryptIfNeeded()
                 } catch (failure: Throwable) {
@@ -458,20 +457,20 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity,
         if (insertions.isNotEmpty() || modifications.isNotEmpty()) {
             onBuiltEvents(true)
         }
+
     }
 
     private fun getNextDisplayIndex(direction: Timeline.Direction): Int? {
-        val frozenTimelineEvents = timelineEventEntities.freeze()
-        if (frozenTimelineEvents.isEmpty()) {
+        if (timelineEventEntities.isEmpty()) {
             return null
         }
         return if (builtEvents.isEmpty()) {
             if (initialEventId != null) {
-                frozenTimelineEvents.where().equalTo(TimelineEventEntityFields.EVENT_ID, initialEventId).findFirst()?.displayIndex
+                timelineEventEntities.where().equalTo(TimelineEventEntityFields.EVENT_ID, initialEventId).findFirst()?.displayIndex
             } else if (direction == Timeline.Direction.BACKWARDS) {
-                frozenTimelineEvents.first(null)?.displayIndex
+                timelineEventEntities.first(null)?.displayIndex
             } else {
-                frozenTimelineEvents.last(null)?.displayIndex
+                timelineEventEntities.last(null)?.displayIndex
             }
         } else if (direction == Timeline.Direction.FORWARDS) {
             builtEvents.first().displayIndex + 1

From d27acfa64f7b5d014eb54888b764f0d078dfb4f4 Mon Sep 17 00:00:00 2001
From: ganfra 
Date: Tue, 22 Feb 2022 12:45:54 +0100
Subject: [PATCH 553/581] Read receipts: use RoomMember instead of User and
 avoid creating realm instance each time

---
 .../sdk/api/session/room/model/ReadReceipt.kt  |  4 +---
 .../mapper/ReadReceiptsSummaryMapper.kt        | 18 ++++++++----------
 .../database/mapper/TimelineEventMapper.kt     |  2 +-
 .../detail/timeline/TimelineEventController.kt |  2 +-
 .../factory/ReadReceiptsItemFactory.kt         |  2 +-
 5 files changed, 12 insertions(+), 16 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/ReadReceipt.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/ReadReceipt.kt
index 67cb9600c82..5639730219e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/ReadReceipt.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/ReadReceipt.kt
@@ -16,9 +16,7 @@
 
 package org.matrix.android.sdk.api.session.room.model
 
-import org.matrix.android.sdk.api.session.user.model.User
-
 data class ReadReceipt(
-        val user: User,
+        val roomMember: RoomMemberSummary,
         val originServerTs: Long
 )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/ReadReceiptsSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/ReadReceiptsSummaryMapper.kt
index 5413dd3d71c..5aaa49b9e82 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/ReadReceiptsSummaryMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/ReadReceiptsSummaryMapper.kt
@@ -19,7 +19,7 @@ package org.matrix.android.sdk.internal.database.mapper
 import org.matrix.android.sdk.api.session.room.model.ReadReceipt
 import org.matrix.android.sdk.internal.database.RealmSessionProvider
 import org.matrix.android.sdk.internal.database.model.ReadReceiptsSummaryEntity
-import org.matrix.android.sdk.internal.database.model.UserEntity
+import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity
 import org.matrix.android.sdk.internal.database.query.where
 import javax.inject.Inject
 
@@ -29,14 +29,12 @@ internal class ReadReceiptsSummaryMapper @Inject constructor(private val realmSe
         if (readReceiptsSummaryEntity == null) {
             return emptyList()
         }
-        return realmSessionProvider.withRealm { realm ->
-            val readReceipts = readReceiptsSummaryEntity.readReceipts
-            readReceipts
-                    .mapNotNull {
-                        val user = UserEntity.where(realm, it.userId).findFirst()
-                                ?: return@mapNotNull null
-                        ReadReceipt(user.asDomain(), it.originServerTs.toLong())
-                    }
-        }
+        val readReceipts = readReceiptsSummaryEntity.readReceipts
+        return readReceipts
+                .mapNotNull {
+                    val roomMember = RoomMemberSummaryEntity.where(readReceiptsSummaryEntity.realm, roomId = it.roomId, userId = it.userId).findFirst()
+                            ?: return@mapNotNull null
+                    ReadReceipt(roomMember.asDomain(), it.originServerTs.toLong())
+                }
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/TimelineEventMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/TimelineEventMapper.kt
index f3bea68c26e..55c7f2a8ee6 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/TimelineEventMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/TimelineEventMapper.kt
@@ -48,7 +48,7 @@ internal class TimelineEventMapper @Inject constructor(private val readReceiptsS
                 ),
                 readReceipts = readReceipts
                         ?.distinctBy {
-                            it.user
+                            it.roomMember
                         }?.sortedByDescending {
                             it.originServerTs
                         }.orEmpty()
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt
index e3f162dfd4a..43fa9e0c2ec 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt
@@ -516,7 +516,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
             val event = itr.previous()
             timelineEventsGroups.addOrIgnore(event)
             val currentReadReceipts = ArrayList(event.readReceipts).filter {
-                it.user.userId != session.myUserId
+                it.roomMember.userId != session.myUserId
             }
             if (timelineEventVisibilityHelper.shouldShowEvent(
                             timelineEvent = event,
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/ReadReceiptsItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/ReadReceiptsItemFactory.kt
index d477a3d40ed..e66dd4b0430 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/ReadReceiptsItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/ReadReceiptsItemFactory.kt
@@ -36,7 +36,7 @@ class ReadReceiptsItemFactory @Inject constructor(private val avatarRenderer: Av
         }
         val readReceiptsData = readReceipts
                 .map {
-                    ReadReceiptData(it.user.userId, it.user.avatarUrl, it.user.displayName, it.originServerTs)
+                    ReadReceiptData(it.roomMember.userId, it.roomMember.avatarUrl, it.roomMember.displayName, it.originServerTs)
                 }
                 .toList()
 

From 6998e846f9951a66cbe3d1bcdafdad56c52ae9c8 Mon Sep 17 00:00:00 2001
From: Adam Brown 
Date: Tue, 22 Feb 2022 11:41:47 +0000
Subject: [PATCH 554/581] adding missing instance of the qr scanner args being
 passed

---
 .../app/features/createdirect/CreateDirectRoomActivity.kt   | 3 ++-
 .../im/vector/app/features/qrcode/QrCodeScannerFragment.kt  | 6 +++---
 2 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt
index a6b34eda25f..9df4f52d0f0 100644
--- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt
@@ -152,7 +152,8 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
 
     private val permissionCameraLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
         if (allGranted) {
-            addFragment(views.container, QrCodeScannerFragment::class.java)
+            val args = QrScannerArgs(showExtraButtons = false, R.string.add_by_qr_code)
+            addFragment(views.container, QrCodeScannerFragment::class.java, args)
         } else if (deniedPermanently) {
             onPermissionDeniedSnackbar(R.string.permissions_denied_qr_code)
         }
diff --git a/vector/src/main/java/im/vector/app/features/qrcode/QrCodeScannerFragment.kt b/vector/src/main/java/im/vector/app/features/qrcode/QrCodeScannerFragment.kt
index c514a1c8aa2..0f438a77cdd 100644
--- a/vector/src/main/java/im/vector/app/features/qrcode/QrCodeScannerFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/qrcode/QrCodeScannerFragment.kt
@@ -56,7 +56,7 @@ data class QrScannerArgs(
 open class QrCodeScannerFragment @Inject constructor() : VectorBaseFragment(), ZXingScannerView.ResultHandler {
 
     private val qrViewModel: QrCodeScannerViewModel by activityViewModel()
-    private val scannerArgs: QrScannerArgs? by args()
+    private val scannerArgs: QrScannerArgs by args()
 
     override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentQrCodeScannerBinding {
         return FragmentQrCodeScannerBinding.inflate(inflater, container, false)
@@ -93,13 +93,13 @@ open class QrCodeScannerFragment @Inject constructor() : VectorBaseFragment
+        scannerArgs.showExtraButtons.let { showButtons ->
             views.userCodeMyCodeButton.isVisible = showButtons
             views.userCodeOpenGalleryButton.isVisible = showButtons
 

From 909c3a667a7e649cafc9328473821d0f141deecb Mon Sep 17 00:00:00 2001
From: Adam Brown 
Date: Tue, 22 Feb 2022 11:52:33 +0000
Subject: [PATCH 555/581] removing unneeded open scope

---
 .../java/im/vector/app/features/qrcode/QrCodeScannerFragment.kt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/vector/src/main/java/im/vector/app/features/qrcode/QrCodeScannerFragment.kt b/vector/src/main/java/im/vector/app/features/qrcode/QrCodeScannerFragment.kt
index 0f438a77cdd..9dc7fa6548d 100644
--- a/vector/src/main/java/im/vector/app/features/qrcode/QrCodeScannerFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/qrcode/QrCodeScannerFragment.kt
@@ -53,7 +53,7 @@ data class QrScannerArgs(
         @StringRes val titleRes: Int
 ) : Parcelable
 
-open class QrCodeScannerFragment @Inject constructor() : VectorBaseFragment(), ZXingScannerView.ResultHandler {
+class QrCodeScannerFragment @Inject constructor() : VectorBaseFragment(), ZXingScannerView.ResultHandler {
 
     private val qrViewModel: QrCodeScannerViewModel by activityViewModel()
     private val scannerArgs: QrScannerArgs by args()

From 16e372304328c149afc253619c790ccbd67208c2 Mon Sep 17 00:00:00 2001
From: Adam Brown 
Date: Tue, 22 Feb 2022 11:55:44 +0000
Subject: [PATCH 556/581] adding changelog entry

---
 changelog.d/5295.bugfix | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 changelog.d/5295.bugfix

diff --git a/changelog.d/5295.bugfix b/changelog.d/5295.bugfix
new file mode 100644
index 00000000000..c5e55cde5d0
--- /dev/null
+++ b/changelog.d/5295.bugfix
@@ -0,0 +1 @@
+Fixing crash when adding room by QR code after accepting the camera permission for the first time
\ No newline at end of file

From 80d19fa49780217e8d0b8c3cd8e496422742cd63 Mon Sep 17 00:00:00 2001
From: ganfra 
Date: Tue, 22 Feb 2022 13:00:22 +0100
Subject: [PATCH 557/581] Realm transactions: use Realm.WRITE_EXECUTOR (and use
 in Create/Join Room tasks)

---
 .../sdk/internal/database/AsyncTransaction.kt | 42 ++++++++-----------
 .../session/room/create/CreateRoomTask.kt     |  3 +-
 .../room/membership/joining/JoinRoomTask.kt   |  5 +--
 3 files changed, 22 insertions(+), 28 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/AsyncTransaction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/AsyncTransaction.kt
index d5a96f5ba19..ebc9bcce5ad 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/AsyncTransaction.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/AsyncTransaction.kt
@@ -19,11 +19,9 @@ import com.zhuinden.monarchy.Monarchy
 import io.realm.Realm
 import io.realm.RealmConfiguration
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.asCoroutineDispatcher
 import kotlinx.coroutines.isActive
 import kotlinx.coroutines.launch
-import kotlinx.coroutines.sync.Semaphore
-import kotlinx.coroutines.sync.withPermit
 import kotlinx.coroutines.withContext
 import timber.log.Timber
 
@@ -37,30 +35,26 @@ internal fun  CoroutineScope.asyncTransaction(realmConfiguration: RealmConfig
     }
 }
 
-private val realmSemaphore = Semaphore(1)
-
 suspend fun  awaitTransaction(config: RealmConfiguration, transaction: suspend (realm: Realm) -> T): T {
-    return realmSemaphore.withPermit {
-        withContext(Dispatchers.IO) {
-            Realm.getInstance(config).use { bgRealm ->
-                bgRealm.beginTransaction()
-                val result: T
-                try {
-                    val start = System.currentTimeMillis()
-                    result = transaction(bgRealm)
-                    if (isActive) {
-                        bgRealm.commitTransaction()
-                        val end = System.currentTimeMillis()
-                        val time = end - start
-                        Timber.v("Execute transaction in $time millis")
-                    }
-                } finally {
-                    if (bgRealm.isInTransaction) {
-                        bgRealm.cancelTransaction()
-                    }
+    return withContext(Realm.WRITE_EXECUTOR.asCoroutineDispatcher()) {
+        Realm.getInstance(config).use { bgRealm ->
+            bgRealm.beginTransaction()
+            val result: T
+            try {
+                val start = System.currentTimeMillis()
+                result = transaction(bgRealm)
+                if (isActive) {
+                    bgRealm.commitTransaction()
+                    val end = System.currentTimeMillis()
+                    val time = end - start
+                    Timber.v("Execute transaction in $time millis")
+                }
+            } finally {
+                if (bgRealm.isInTransaction) {
+                    bgRealm.cancelTransaction()
                 }
-                result
             }
+            result
         }
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt
index ac6e0562b0a..4377bcbd556 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt
@@ -28,6 +28,7 @@ import org.matrix.android.sdk.api.session.room.model.Membership
 import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
 import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset
 import org.matrix.android.sdk.internal.database.awaitNotEmptyResult
+import org.matrix.android.sdk.internal.database.awaitTransaction
 import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
 import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
 import org.matrix.android.sdk.internal.database.query.where
@@ -105,7 +106,7 @@ internal class DefaultCreateRoomTask @Inject constructor(
             throw CreateRoomFailure.CreatedWithTimeout(roomId)
         }
 
-        Realm.getInstance(realmConfiguration).executeTransactionAsync {
+        awaitTransaction(realmConfiguration){
             RoomSummaryEntity.where(it, roomId).findFirst()?.lastActivityTime = System.currentTimeMillis()
         }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt
index 82fea237dbd..13fc6e37089 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt
@@ -24,6 +24,7 @@ import org.matrix.android.sdk.api.session.room.failure.JoinRoomFailure
 import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
 import org.matrix.android.sdk.api.session.room.model.Membership
 import org.matrix.android.sdk.internal.database.awaitNotEmptyResult
+import org.matrix.android.sdk.internal.database.awaitTransaction
 import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
 import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
 import org.matrix.android.sdk.internal.database.query.where
@@ -89,11 +90,9 @@ internal class DefaultJoinRoomTask @Inject constructor(
         } catch (exception: TimeoutCancellationException) {
             throw JoinRoomFailure.JoinedWithTimeout
         }
-
-        Realm.getInstance(realmConfiguration).executeTransactionAsync {
+        awaitTransaction(realmConfiguration){
             RoomSummaryEntity.where(it, roomId).findFirst()?.lastActivityTime = System.currentTimeMillis()
         }
-
         setReadMarkers(roomId)
     }
 

From 7ba86f032aa1a495dcbd1abe2676f1f02c5d6a37 Mon Sep 17 00:00:00 2001
From: waclaw66 
Date: Mon, 21 Feb 2022 13:32:09 +0000
Subject: [PATCH 558/581] Translated using Weblate (Czech)

Currently translated at 100.0% (2785 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/cs/
---
 vector/src/main/res/values-cs/strings.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/vector/src/main/res/values-cs/strings.xml b/vector/src/main/res/values-cs/strings.xml
index 6004d94ee79..fd6a180e48b 100644
--- a/vector/src/main/res/values-cs/strings.xml
+++ b/vector/src/main/res/values-cs/strings.xml
@@ -3136,7 +3136,7 @@
     Sdílet polohu
     Výsledky se zobrazí až po ukončení hlasování
     Uzavřené hlasování
-    Hlasující vidí výsledky ihned po hlasování
+    Hlasující uvidí výsledky ihned po hlasování
     Otevřené hlasování
     Typ hlasování
     UPRAVIT HLASOVÁNÍ

From f5593860cfd50718d3bc15ac69971298b5442ade Mon Sep 17 00:00:00 2001
From: Joe Sagawa 
Date: Tue, 22 Feb 2022 12:36:33 +0000
Subject: [PATCH 559/581] Translated using Weblate (Japanese)

Currently translated at 92.9% (2589 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 37 ++++++++++++++++++++++-
 1 file changed, 36 insertions(+), 1 deletion(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index e7a5d53d4a5..5fa9c84f41d 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2803,7 +2803,7 @@
     絵文字を比較して検証
     絵文字を比較して検証
     対面でない場合は、代わりに絵文字を比較してください
-    あなたのホームサーバーは、最大%sのサイズの添付ファイルを受け付けています。
+    あなたのホームサーバーで許容されている添付ファイルの最大サイズは%sです。
     新しいパスワードを確認するには下記のリンクを開いてください。リンクにアクセスしてから、以下をクリックしてください。
     PINコードを再設定するには「PINコードを忘れた」をタップしてログアウトし、その後再設定してください。
     
@@ -2831,4 +2831,39 @@
     ${app_name}は%1$sという種類のメッセージに対応していません
     ${app_name}は%1$sという種類のイベントに対応していません
     既読通知へ移動
+    大切に保護しましょう
+    完了!
+    アカウントパスワードと違うものにしてください。
+    確認のため、%sをもう一度入力してください。
+    続行するには%sを入力してください。
+    %sを確認
+    %sを設定
+    検証を中止しました
+    今中止すれば、%1$s(%2$s)の検証をしません。検証は相手のユーザープロフィールからもう一度開始できます。
+    中止すれば、新しい端末では暗号化されたメッセージが読めないし他のユーザーに信頼されません
+    中止すれば、この端末では暗号化されたメッセージが読めないし他のユーザーに信頼されません
+    自分ではない
+    新しいセッションを検証して、暗号化されたメッセージにアクセスできるようにしましょう。
+    タップして確認及び検証
+    新しいログイン。あなたですか?
+    ${app_name} Android
+    部屋の管理者によって削除されています、理由:%1$s
+    ユーザーによって削除されています、理由:
+    削除した理由
+    この添付ファイルを%1$sに送信しますか?
+    秘密ストレージは信頼されている端末でのみアクセスしましょう
+    秘密ストレージのパスフレーズを入力してください
+    既存のセッションにアクセスできない場合
+    シンプルなアンケートを作ります
+    %1$sというタイプのアカウントデータを削除しますか?
+\n
+\n予期せぬトラブルを起こす可能性があるので注意してください。
+    %1$s(%2$s)が新しいセッションでサインインしました:
+    このセッションは%1$s(%2$s)によって検証されているので、メッセージのセキュリティは信頼できます。
+    既存のセッションでこのセッションを検証して、暗号化されたメッセージへアクセスできるようにしましょう。
+    他のユーザーに信頼されない可能性があります
+    あなたはこのセッションを検証しているので、メッセージのセキュリティは信頼できます。
+    利用可能な暗号情報がありません
+    既定のバージョン
+    非公開部屋とダイレクトメッセージにおけるエンドツーエンド暗号化はあなたのサーバーの管理者によって既定として無効にされています。
 
\ No newline at end of file

From 8628619578fd52eb974840468436cf6086aba1af Mon Sep 17 00:00:00 2001
From: Suguru Hirahara 
Date: Tue, 22 Feb 2022 10:38:44 +0000
Subject: [PATCH 560/581] Translated using Weblate (Japanese)

Currently translated at 92.9% (2589 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/ja/
---
 vector/src/main/res/values-ja/strings.xml | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 5fa9c84f41d..f1e8322730d 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2814,17 +2814,17 @@
     暗号化されたメッセージにアクセスするには、ログインを検証し、本人確認を行う必要があります。
     暗号化されたメッセージにアクセスするには、あなたの他のセッションからログインを検証し、本人確認を行う必要があります。
     詳しく知る
-    セキュリティー高めるために、使い捨てコードが一致しているのを確認して、%sを検証しましょう。
+    セキュリティーを高めるために、使い捨てコードが一致しているのを確認して、%sを検証しましょう。
     セキュリティーを高めるために、自分と相手の端末でコードが一致していることを確認して、%sを検証しましょう。
 \n
 \n対面で行うのが最もセキュアです。
     暗号化の設定が正しくありません。
     暗号化を復元
     暗号化を有効な状態に取り戻すために、管理者に連絡してください。
-    このユーザーとのメッセージがエンドツーエンド暗号化されており、第三者には読めません。
+    このユーザーとのメッセージはエンドツーエンド暗号化されており、第三者には読めません。
     このコードを相手の画面に現れているコードと比較してください。
     絵文字を比較して、同じ順番に現れているのを確認してください。
-    対面で行うか、他の信頼できる通信媒体を利用するのが最もセキュアです。
+    セキュリティーを高めるために、対面で行うか、他の信頼できる通信手段を利用しましょう。
     選択されたエモートを虹色にして送信します
     選択されたテキストを虹色にして送信します
     ${app_name}がID%1$sのイベントを処理中にエラーが発生しました

From bfd8f6c3ff2f974fc4162db964bd87ec88b1fd44 Mon Sep 17 00:00:00 2001
From: Edward Gera 
Date: Mon, 21 Feb 2022 15:13:40 +0000
Subject: [PATCH 561/581] Translated using Weblate (Hebrew)

Currently translated at 84.2% (2345 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/he/
---
 vector/src/main/res/values-iw/strings.xml | 35 +++++++++++++++++++++--
 1 file changed, 33 insertions(+), 2 deletions(-)

diff --git a/vector/src/main/res/values-iw/strings.xml b/vector/src/main/res/values-iw/strings.xml
index 48bc5a675d0..7db0d5a0cba 100644
--- a/vector/src/main/res/values-iw/strings.xml
+++ b/vector/src/main/res/values-iw/strings.xml
@@ -2545,8 +2545,8 @@
     %s שינה את רשימות ה-ACL של השרת עבור החדר הזה.
     • מותרים שרתים התואמים ל-%s.
     • שרתים התואמים %s אסורים.
-    אתה מגדיר את רשימות ה-ACL של השרת עבור החדר הזה.
-    %s הגדר את רשימות ה-ACL של השרת עבור החדר הזה.
+    אתה מגדיר את ה-ACL של השרת עבור החדר הזה.
+    %s הגדר את ה-ACL של השרת עבור החדר הזה.
     שדרגת כאן.
     %s שודרג כאן.
     שדרגת את החדר הזה.
@@ -2557,4 +2557,35 @@
     כל אחד.
     כל חברי החדר.
     כל חברי החדר, מהנקודה בה הצטרפו.
+    מפה
+    שתף מיקום
+    מיקום
+    שתף מיקום
+    התוצאות נחשפות רק לאחר מסיום הסקר
+    סקר סגור
+    מצביעים רואים את התוצאות לאחר ההצבעה
+    פתח סקר
+    סוג סקר
+    ערוך סקר
+    ערוך סקר
+    האם את/ה בטוח שברצונך להסיר את הסקר\? לא ניתן לשחזור לאחר הסרה.
+    הסר סקר
+    הסקר הסתיים
+    נא הצביעו
+    שתף מיקום
+    צור סקר
+    פתח אנשי קשר
+    שלח סטיקר
+    העלה קובץ
+    שלח תמונות וסרטונים
+    פתח מצלמה
+    הצג בועות הודעה
+    טעינת המפה נכשלה
+    מיפוי מיקומי משתמשים בציר הזמן
+    לאחר ההפעלה, תוכל לשלוח את המיקום שלך לכל חדר
+    אפשר שיתוף מיקום
+    לפתוח בעזרת
+    ${app_name} לא הצליח לגשת למיקום שלך. בבקשה נסה שוב מאוחר יותר.
+    ${app_name} לא הצליח לגשת למיקום שלך
+    שתף מיקום
 
\ No newline at end of file

From 79a53c68b08f0f48a7dcb0c65485cbc345c5c190 Mon Sep 17 00:00:00 2001
From: yaronsb 
Date: Mon, 21 Feb 2022 14:07:26 +0000
Subject: [PATCH 562/581] Translated using Weblate (Hebrew)

Currently translated at 84.2% (2345 of 2785 strings)

Translation: Element Android/Element Android App
Translate-URL: https://translate.element.io/projects/element-android/element-app/he/
---
 vector/src/main/res/values-iw/strings.xml | 229 +++++++++++++++++++---
 1 file changed, 203 insertions(+), 26 deletions(-)

diff --git a/vector/src/main/res/values-iw/strings.xml b/vector/src/main/res/values-iw/strings.xml
index 7db0d5a0cba..744e3d726b2 100644
--- a/vector/src/main/res/values-iw/strings.xml
+++ b/vector/src/main/res/values-iw/strings.xml
@@ -126,8 +126,8 @@
     
         משתמש %d
         %d משתמשים
-        מעט
-        אחר
+        %d משתמשים
+        %d משתמש
     
     אין חדרים ציבורים
     אין חדרים
@@ -158,7 +158,7 @@
     התחל שיחה קולית
     התחל צ\'אט חדש
     חיפוש
-    קישור לשרת הזיהוי
+    קישור שרת הזהות
     קישור לשרת הבית
     יציאה
     כניסה
@@ -216,7 +216,7 @@
     שרת זיהוי:
     שרת הבית:
     שם משתמש כבר נמצא בשימוש
-    שרת בית זה רוצה לוודא שאתה לא רובוט
+    שרת הבית רוצה לוודא שאתה לא רובוט
     הרשמה באמצעות דוא\"ל ומספר טלפון בבת אחת עדיין אינה נתמכת עד להתקנת ה- API. רק מספר הטלפון ייקח בחשבון.
 \n
 \nתוכל להוסיף את הדוא\"ל שלך לפרופיל שלך בהגדרות.
@@ -293,7 +293,7 @@
     הזמנה זו נשלחה אל%s, שאינה משויכת לחשבון זה.
 \nייתכן שתרצה להתחבר עם חשבון אחר, או להוסיף דוא\"ל זה לחשבונך.
     הוזמנת להצטרף לחדר זה על ידי%s
-    קפיצה להודעה הראשונה שלא נקראה.
+    עבור להודעה שלא נקראה
     מסנכרן…
     פתח כותרת
     חברי רשימה
@@ -348,7 +348,7 @@
     שִׂיחָה
     בחר רינגטון לשיחות:
     רינגטון שיחה נכנסת
-    ישתמש ב-%s כסיוע כאשר השרת הביתי שלך אינו מציע כזה (כתובת ה- IP שלך תשותף במהלך שיחה)
+    ישתמש ב-%s כסיוע כאשר השרת הבית שלך אינו מציע כזה (כתובת ה- IP שלך תשותף במהלך שיחה)
     אפשר שרת עזרה לשיחות
     השתמש ברינגטון ברירת המחדל של אלמנט לשיחות נכנסות
     בקש אישור לפני שמתחילים בשיחה
@@ -368,10 +368,10 @@
     מקור
     שלח כ
     
-        שינוי בפרט חברות
-        שינוי בפרטי חברות
-        שינוי בפרטי חברות
-        שינוי בפרטי חברות
+        %d שינוי בפרט חברות
+        %d שינוי בפרטי חברות
+        %d שינוי בפרטי חברות
+        %d שינוי בפרטי חברות
     
     רשימת קבוצות
     קרא את רשימת הקבלות
@@ -510,6 +510,7 @@
     בטל הורדה
     בטל העלאה
     האם ברצונך להסתיר את כל ההודעות ממשתמש זה\?
+\n
 \nשים לב שפעולה זו תפעיל מחדש את האפליקציה והיא עשויה להימשך זמן מה.
     סיבה לדיווח על תוכן זה
     הצטרף
@@ -537,12 +538,12 @@
     אל תתן אמון
     אמון
     
-        הודעה חדשה
-        הודעות חדשות
-        הודעות חדשות
-        הודעות חדשות
+        %d הודעה חדשה
+        d% הודעות חדשות
+        %d הודעות חדשות
+        %d הודעות חדשות
     
-    אין לך הרשאה לפרסם בחדר זה
+    אין לך הרשאה לפרסם בחדר זה.
     הקובץ לא נמצא
     מחק הודעות שלא נשלחו
     שלח שוב הודעות שלא נשלחו
@@ -565,7 +566,7 @@
     האם אתה בטוח שברצונך להזמין את%s לצ\'אט זה\?
     סיבה
     הסרת נידוי ממשתמש יאפשר לו להצטרף שוב לחדר.
-    איסור על המשתמש יבעט בהם מחדר זה וימנע מהם להצטרף שוב.
+    חסימת משתמש תסיר אותם מהחדר הזה ותמנע ממנו להצטרף שוב.
     הסר נידוי ממשתמש
     סיבות לנידוי
     נדה משתמש
@@ -710,16 +711,16 @@
     
     חבר אחד
     
-        חבר
-        חברים
-        חברים
-        חברים
+        %d חבר
+        %d חברים
+        %d חברים
+        %d חברים
     
     
-        חבר פעיל
-        חברים פעילים
-        חברים פעילים
-        חברים פעילים
+        %d חבר פעיל
+        %d חברים פעילים
+        %d חברים פעילים
+        %d חברים פעילים
     
     הוסף חבר
     התחל אימות
@@ -2429,7 +2430,7 @@
     %1$s הצטרף לחדר. סיבה: %2$s
     %1$s הזמין אותך. סיבה: %2$s
     הזמנת את %1$s. סיבה: %2$s
-    %1$s הפעילה הצפנה מקצה לקצה (אלגוריתם לא מזוהה %2$s).
+    %1$s הפעיל הצפנה מקצה לקצה (אלגוריתם לא מזוהה %2$s).
     הפעלת הצפנה מקצה לקצה (אלגוריתם לא מזוהה %1$s).
     %1$s הזמין את %2$s. סיבה: %3$s
     ההזמנה שלך. סיבה: %1$s
@@ -2507,7 +2508,7 @@
     הסרת את ווידג\'ט %1$s
     %1$s הסיר את %2$s ווידג\'ט
     הוספת ווידג\'ט %1$s
-    %1$s הוסיף %2$s ווידג\'ט
+    %1$s הוסיף את%2$s ווידג\'ט
     קיבלת את ההזמנה עבור %1$s
     %1$s קיבל את ההזמנה עבור %2$s
     ביטלת את ההזמנה עבור %1$s
@@ -2588,4 +2589,180 @@
     ${app_name} לא הצליח לגשת למיקום שלך. בבקשה נסה שוב מאוחר יותר.
     ${app_name} לא הצליח לגשת למיקום שלך
     שתף מיקום
+    אפשר התראת דוא\"ל עבור %s
+    מילות מפתח לא יכולות להכיל \'%s\'
+    מילות מפתח אינן יכולות להתחיל ב-\'.\'
+    הוסף מילת מפתח חדשה
+    מילות המפתח שלך
+    הודע לי עבור
+    אחר
+    אזכורים ומילות מפתח
+    התראת ברירת מחדל
+    כדי לקבל אימייל עם התראה, אנא שיוך דוא\"ל לחשבון Matrix שלך
+    הודעה באימייל
+    הסשן יצא מהחשבון!
+    החדר הוצא!
+    אף אחד
+    אזכורים ומילות מפתח בלבד
+    מתוך שרשור
+    טיפ: הקש ארוכות על הודעה והשתמש ב-\"%s\".
+    שרשורים עוזרים לשמור על השיחות שלך בנושא וקל למעקב.
+    שמור על דיונים מאורגנים באמצעות שרשורים
+    מציג את כל השרשורים שבהם השתתפת
+    השרשורים שלי
+    הצג את כל השרשורים בחדר זה
+    כל השרשורים
+    מסנן
+    שרשורים
+    שרשורים
+    סנן שרשורים בחדר
+    שדרג המרחב
+    שנה את שם המרחב
+    אפשר הצפנת מרחב
+    שנה את הכתובת הראשית של המרחב
+    שנה את דמות המרחב
+    אין לך הרשאה לעדכן את התפקידים הנדרשים כדי לשנות חלקים שונים במרחב הזה
+    בחר את התפקידים הדרושים כדי לשנות חלקים שונים במרחב הזה
+    הרשאות אזור
+    הצג ועדכן את התפקידים הנדרשים לשינוי חלקים שונים של האזור.
+    ההצפנה הוגדרה בצורה שגויה כך שאינך יכול לשלוח הודעות. לחץ כדי לפתוח את ההגדרות.
+    ההצפנה הוגדרה בצורה שגויה כך שאינך יכול לשלוח הודעות. אנא צור קשר עם מנהל מערכת כדי לשחזר את ההצפנה למצב חוקי.
+    ביטול החסימה של המשתמש יאפשר לו להצטרף שוב למרחב.
+    חסימת משתמש תסיר אותם מהמרחב הזה ותמנע ממנו להצטרף שוב.
+    המשתמש יוסר מהמרחב הזה.
+\n
+\nכדי למנוע מהם להצטרף שוב, עליך לחסום אותם במקום זאת.
+    המשך בכל זאת
+    מסיים שיחה…
+    אין מענה
+    המשתמש אליו התקשרת עסוק.
+    מנוי תפוס
+    החזקת השיחה
+    %s החזיק את השיחה
+    להחזיק
+    לְהַמשִׁיך
+    שיחת שמע עם %s
+    שיחת וידאו עם %s
+    
+        שיחת וידאו שלא נענתה
+        %d שיחות וידאו שלא נענו
+        %d שיחות וידאו שלא נענו
+        שיחת וידאו שלא נענתה
+    
+    
+        שיחה שלא נענתה
+        "%d שיחות  שלא נענו"
+        "%d שיחות  שלא נענו"
+        שיחה שלא נענתה
+    
+    צלצול שיחה…
+    לא מורשה, חסרים אישורי אימות חוקיים
+    בחר שרת בית
+    לא ניתן להגיע לשרת ביתי בכתובת ה- %s. אנא בדוק את הקישור שלך או בחר שרת ביתי באופן ידני.
+    השתמש כברירת מחדל ואל תשאל שוב
+    תמיד תשאל
+    כתובת ה-API של שרת הבית
+    רווחים
+    מזמין
+    הצג את כל החדרים בספריית החדרים, כולל חדרים עם תוכן מפורש.
+    הצג חדרים עם תוכן מפורש
+    ספריית חדרים
+    חדרים מוצעים
+    ערך חדש
+    העתק קישור לשרשור
+    צפיה בחדר
+    לא כעת
+    אפשר
+    חזור
+    החלף
+    הצג שרשורים
+    חסרות הרשאות
+    כדי לשלוח הודעות קוליות, אנא הענק הרשאה למיקרופון.
+    כדי לבצע פעולה זו, אנא הענק למצלמה הרשאה מהגדרות המערכת.
+    חסרות חלק מההרשאות לביצוע פעולה זו, אנא הענק את ההרשאות מהגדרות המערכת.
+    רווחים
+    למד עוד
+    מאזין להתראות
+    • שרתים התואמים את כתובות ה-IP נאסרו כעת.
+    • כעת מותרים שרתים התואמים את כתובות ה-IP.
+    • שרתים התואמים את כתובות ה-IP אסורות.
+    • מותר להשתמש בשרתים התואמים את כתובות ה-IP.
+    הפעלת הצפנה מקצה לקצה.
+    %1$s הפעיל/ה הצפנה מקצה לקצה.
+    מנעת מאורחים להצטרף לחדר.
+    %1$s מנע מאורחים להצטרף לחדר.
+    מנעת מאורחים להצטרף לחדר.
+    %1$s מנע מאורחים להצטרף לחדר.
+    אפשרת לאורחים להצטרף לכאן.
+    %1$s איפשר לאורחים להצטרף לכאן.
+    אפשרת לאורחים להצטרף לחדר.
+    %1$s אפשר/ה לאורחים להצטרף לחדר.
+    שינית את הכתובות של החדר הזה.
+    %1$s שינה את הכתובות של החדר הזה.
+    שינית את הכתובת הראשית והחלופית לחדר הזה.
+    %1$s שינה את הכתובת הראשית והאלטרנטיבית של החדר הזה.
+    שינית את הכתובות החלופיות של החדר הזה.
+    %1$s שינה את הכתובות החלופיות עבור החדר הזה.
+    
+        הסרת את הכתובת החלופית %1$s עבור החדר הזה.
+        הסרת את הכתובות החלופיות %1$s עבור החדר הזה.
+        הסרת את הכתובות החלופיות %1$s עבור החדר הזה.
+        הסרת את הכתובת החלופית %1$s עבור החדר הזה.
+    
+    
+        %1$s הסיר את הכתובת החלופית %2$s עבור החדר הזה.
+        %1$s הסיר את הכתובות החלופיות %2$s עבור החדר הזה.
+        %1$s הסיר את הכתובות החלופיות %2$s עבור החדר הזה.
+        %1$s הסיר את הכתובת החלופית %2$s עבור החדר הזה.
+    
+    
+        הוספת את הכתובת החלופית %1$s לחדר הזה.
+        הוספת את הכתובות החלופיות %1$s לחדר הזה.
+        הוספת את הכתובות החלופיות %1$s לחדר הזה.
+        הוספת את הכתובת החלופית %1$s לחדר הזה.
+    
+    
+        %1$s הוסיף את הכתובת החלופית %2$s לחדר הזה.
+        %1$s הוסיף את הכתובות החלופיות %2$s לחדר הזה.
+        %1$s הוסיף את הכתובות החלופיות %2$s לחדר הזה.
+        %1$s הוסיף את הכתובת החלופית %2$s לחדר הזה.
+    
+    הסרת את הכתובת הראשית של החדר הזה.
+    %1$s הסיר את הכתובת הראשית של החדר הזה.
+    הגדרת הכתובת הראשית של חדר זה ל-%1$s.
+    %1$s הגדיר את הכתובת הראשית של החדר הזה ל-%2$s.
+    הוספת את %1$s והסרת את %2$s ככתובות לחדר הזה.
+    %1$s הוסיף את %2$s והסיר את %3$s ככתובות לחדר הזה.
+    
+        הסרת את %1$s ככתובת לחדר הזה.
+        הסרת את %1$s ככתובות לחדר הזה.
+        הסרת את %1$s ככתובות לחדר הזה.
+        הסרת את %1$s ככתובת לחדר הזה.
+    
+    
+        %1$s הסיר את %2$s ככתובת לחדר הזה.
+        %1$s הסיר את %2$s ככתובות עבור החדר הזה.
+        %1$s הסיר את %2$s ככתובות עבור החדר הזה.
+        %1$s הסיר את %2$s ככתובת עבור החדר הזה.
+    
+    
+        הוספת את %1$s ככתובת לחדר הזה.
+        הוספת את %1$s ככתובות לחדר הזה.
+        הוספת את %1$s ככתובות לחדר הזה.
+        הוספת את %1$s ככתובת לחדר הזה.
+    
+    
+        %1$s הוסיף את %2$s ככתובת לחדר הזה.
+        %1$s הוסיפו את %2$s ככתובות לחדר הזה.
+        %1$s הוסיפו את %2$s ככתובות לחדר הזה.
+        %1$s הוסיפו את %2$s ככתובות לחדר הזה.
+    
+    משכת את ההזמנה של %1$s. סיבה: %2$s
+    %1$s משך את ההזמנה של %2$s. סיבה: %3$s
+    קיבלת את ההזמנה עבור %1$s. סיבה: %2$s
+    %1$s קיבל את ההזמנה עבור %2$s. סיבה: %3$s
+    ביטלת את ההזמנה של %1$s להצטרף לחדר. סיבה: %2$s
+    %1$s ביטל את ההזמנה של %2$s להצטרף לחדר. סיבה: %3$s
+    שלחת הזמנה אל %1$s להצטרף לחדר. סיבה: %2$s
+    %1$s שלח הזמנה אל %2$s להצטרף לחדר. סיבה: %3$s
 
\ No newline at end of file

From 4cc80162cacf10a4b63b58ed30536de608436195 Mon Sep 17 00:00:00 2001
From: ganfra 
Date: Tue, 22 Feb 2022 14:23:45 +0100
Subject: [PATCH 563/581] Clean and add Changelog

---
 changelog.d/5297.misc                                          | 1 +
 .../android/sdk/internal/session/room/create/CreateRoomTask.kt | 3 +--
 .../internal/session/room/membership/joining/JoinRoomTask.kt   | 3 +--
 .../sdk/internal/session/room/timeline/TimelineChunk.kt        | 1 -
 4 files changed, 3 insertions(+), 5 deletions(-)
 create mode 100644 changelog.d/5297.misc

diff --git a/changelog.d/5297.misc b/changelog.d/5297.misc
new file mode 100644
index 00000000000..f45490ce3dd
--- /dev/null
+++ b/changelog.d/5297.misc
@@ -0,0 +1 @@
+Improve some internal realm usages.
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt
index 4377bcbd556..9bd15a02671 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt
@@ -17,7 +17,6 @@
 package org.matrix.android.sdk.internal.session.room.create
 
 import com.zhuinden.monarchy.Monarchy
-import io.realm.Realm
 import io.realm.RealmConfiguration
 import kotlinx.coroutines.TimeoutCancellationException
 import org.matrix.android.sdk.api.failure.Failure
@@ -106,7 +105,7 @@ internal class DefaultCreateRoomTask @Inject constructor(
             throw CreateRoomFailure.CreatedWithTimeout(roomId)
         }
 
-        awaitTransaction(realmConfiguration){
+        awaitTransaction(realmConfiguration) {
             RoomSummaryEntity.where(it, roomId).findFirst()?.lastActivityTime = System.currentTimeMillis()
         }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt
index 13fc6e37089..22a46b6cfc9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt
@@ -16,7 +16,6 @@
 
 package org.matrix.android.sdk.internal.session.room.membership.joining
 
-import io.realm.Realm
 import io.realm.RealmConfiguration
 import kotlinx.coroutines.TimeoutCancellationException
 import org.matrix.android.sdk.api.session.events.model.toContent
@@ -90,7 +89,7 @@ internal class DefaultJoinRoomTask @Inject constructor(
         } catch (exception: TimeoutCancellationException) {
             throw JoinRoomFailure.JoinedWithTimeout
         }
-        awaitTransaction(realmConfiguration){
+        awaitTransaction(realmConfiguration) {
             RoomSummaryEntity.where(it, roomId).findFirst()?.lastActivityTime = System.currentTimeMillis()
         }
         setReadMarkers(roomId)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt
index 25957de1b58..c0dc31fcf8a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt
@@ -457,7 +457,6 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity,
         if (insertions.isNotEmpty() || modifications.isNotEmpty()) {
             onBuiltEvents(true)
         }
-
     }
 
     private fun getNextDisplayIndex(direction: Timeline.Direction): Int? {

From c4d9ba24d3f139f474b616d8e05186c5dd43800d Mon Sep 17 00:00:00 2001
From: Benoit Marty 
Date: Tue, 22 Feb 2022 15:27:44 +0100
Subject: [PATCH 564/581] Remove duplicated string - keep the latest version.

---
 vector/src/main/res/values-ja/strings.xml | 1 -
 1 file changed, 1 deletion(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index f1e8322730d..50ffa35f89c 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2488,7 +2488,6 @@
     サーバーのファイルアップロードの制限
     自分の会話は、自分のもの。
     %1$sがこのルームを「招待者のみ参加可能」に設定しました。
-    %1$sが部屋をリンクを持っているユーザーにアクセスできるように設定しました。
     選択したメッセージをネタバレとして送信
     ${app_name} がエンドツーエンド暗号鍵をディスクに保存する許可を要求しています。
 \n

From ad2ee0e9bce6388f5d39a0e41a32c4b208e68d1e Mon Sep 17 00:00:00 2001
From: Benoit Marty 
Date: Tue, 22 Feb 2022 15:29:52 +0100
Subject: [PATCH 565/581] Remove unused string

---
 vector/src/main/res/values-cs/strings.xml     | 1 -
 vector/src/main/res/values-de/strings.xml     | 1 -
 vector/src/main/res/values-eo/strings.xml     | 1 -
 vector/src/main/res/values-es/strings.xml     | 1 -
 vector/src/main/res/values-et/strings.xml     | 1 -
 vector/src/main/res/values-fa/strings.xml     | 1 -
 vector/src/main/res/values-fi/strings.xml     | 1 -
 vector/src/main/res/values-fr-rCA/strings.xml | 1 -
 vector/src/main/res/values-fr/strings.xml     | 1 -
 vector/src/main/res/values-hu/strings.xml     | 1 -
 vector/src/main/res/values-in/strings.xml     | 1 -
 vector/src/main/res/values-it/strings.xml     | 1 -
 vector/src/main/res/values-ja/strings.xml     | 1 -
 vector/src/main/res/values-lv/strings.xml     | 1 -
 vector/src/main/res/values-nl/strings.xml     | 1 -
 vector/src/main/res/values-pl/strings.xml     | 1 -
 vector/src/main/res/values-pt-rBR/strings.xml | 1 -
 vector/src/main/res/values-ru/strings.xml     | 1 -
 vector/src/main/res/values-sk/strings.xml     | 1 -
 vector/src/main/res/values-sq/strings.xml     | 1 -
 vector/src/main/res/values-sv/strings.xml     | 1 -
 vector/src/main/res/values-uk/strings.xml     | 1 -
 vector/src/main/res/values-vi/strings.xml     | 1 -
 vector/src/main/res/values-zh-rCN/strings.xml | 1 -
 vector/src/main/res/values-zh-rTW/strings.xml | 1 -
 vector/src/main/res/values/strings.xml        | 2 --
 26 files changed, 27 deletions(-)

diff --git a/vector/src/main/res/values-cs/strings.xml b/vector/src/main/res/values-cs/strings.xml
index fd6a180e48b..9070bbdb2ea 100644
--- a/vector/src/main/res/values-cs/strings.xml
+++ b/vector/src/main/res/values-cs/strings.xml
@@ -2700,7 +2700,6 @@
     Na jakých tématech pracujete\?
     Založíme pro ně místnosti. Můžete přidat další později.
     Doplňte nějaké podrobnosti, aby jej lidé mohli identifikovat. Můžete je kdykoli změnit.
-    Prostory jsou nový způsob organizace místností a lidí
     Prostory
     Jste zváni
     Vítejte v prostorech!
diff --git a/vector/src/main/res/values-de/strings.xml b/vector/src/main/res/values-de/strings.xml
index 5852e2de317..54d69277e6c 100644
--- a/vector/src/main/res/values-de/strings.xml
+++ b/vector/src/main/res/values-de/strings.xml
@@ -2692,7 +2692,6 @@
     Meine Teamkameraden und ich
     Ein privater Space um deine Räume zu organisieren
     Um einem bereits existierenden Space beizutreten, benötigst du eine Einladung.
-    Wir haben Spaces entwickelt, damit ihr eure Räume besser organisieren könnt
     Dein privater Space
     Dein öffentlicher Space
     Betrete einen Space mit der angegebenen ID
diff --git a/vector/src/main/res/values-eo/strings.xml b/vector/src/main/res/values-eo/strings.xml
index 4a55ada4022..fd97994323c 100644
--- a/vector/src/main/res/values-eo/strings.xml
+++ b/vector/src/main/res/values-eo/strings.xml
@@ -2695,7 +2695,6 @@
     Por aliĝi al jam ekzistanta aro, vi bezonos inviton.
     Vi povos ŝanĝi ĉi tion poste
     Kian aron volas vi krei\?
-    Aroj estas nova maniero grupigi ĉambrojn kaj personojn
     Via privata aro
     Via publika aro
     Aldoni aron
diff --git a/vector/src/main/res/values-es/strings.xml b/vector/src/main/res/values-es/strings.xml
index 6392d243fe2..071c7731e53 100644
--- a/vector/src/main/res/values-es/strings.xml
+++ b/vector/src/main/res/values-es/strings.xml
@@ -2499,7 +2499,6 @@ Por favor permite el acceso en la próxima ventana emergente para descubrir usua
     Añade un Espacio
     Crea un Espacio
     Crear un espacio
-    Los Espacios son una nueva forma de agrupar salas y personas
     Sincronización inicial:
 \nEsperando respuesta del servidor…
     El mensaje no se pudo enviar por un error
diff --git a/vector/src/main/res/values-et/strings.xml b/vector/src/main/res/values-et/strings.xml
index 2da0cc9125c..e74f7ce6f72 100644
--- a/vector/src/main/res/values-et/strings.xml
+++ b/vector/src/main/res/values-et/strings.xml
@@ -2700,7 +2700,6 @@
     Olemasoleva kogukonnakeskusega liitumiseks vajad sa kutset.
     Sa võid seda hiljem muuta
     Missugust kogukonnakeskust sooviksid sa luua\?
-    Kogukonnakeskused on uus viis jututubade ja inimeste ühendamiseks
     Sinu privaatne kogukonnakeskus
     Sinu avalik kogukonnakeskus
     Lisa kogukonnakeskus
diff --git a/vector/src/main/res/values-fa/strings.xml b/vector/src/main/res/values-fa/strings.xml
index 1c912b9fd6f..b8b7038d371 100644
--- a/vector/src/main/res/values-fa/strings.xml
+++ b/vector/src/main/res/values-fa/strings.xml
@@ -2704,7 +2704,6 @@
     برای پیوستن به یک فضای موجود، نیاز به دعوت دارید.
     می‌توانید بعداً این را تغییر دهید
     می‌خواهید چه نوع فضایی ایجاد کنید؟
-    فضاها راهی جدید برای گروه‌بندی اتاق‌ها و افراد است
     فضای خصوصیتان
     فضا عمومیتان
     افزودن فضا
diff --git a/vector/src/main/res/values-fi/strings.xml b/vector/src/main/res/values-fi/strings.xml
index 0712d15e4aa..724fe7759ff 100644
--- a/vector/src/main/res/values-fi/strings.xml
+++ b/vector/src/main/res/values-fi/strings.xml
@@ -2236,7 +2236,6 @@
     Tarvitset kutsun liittyäksesi olemassa olevaan avaruuteen.
     Voit muuttaa tämän myöhemmin
     Minkä tyyppisen avaruuden haluat luoda\?
-    Avaruudet ovat uusi tapa ryhmitellä huoneita ja ihmisiä
     Yksityinen avaruutesi
     Julkinen avaruutesi
     Lisää avaruus
diff --git a/vector/src/main/res/values-fr-rCA/strings.xml b/vector/src/main/res/values-fr-rCA/strings.xml
index 0cf789c04f4..4f57799d801 100644
--- a/vector/src/main/res/values-fr-rCA/strings.xml
+++ b/vector/src/main/res/values-fr-rCA/strings.xml
@@ -2661,7 +2661,6 @@
     Pour rejoindre un espace existant, il vous faut une invitation.
     Vous pouvez changer ceci plus tard
     Quel type d’espace voulez-vous créer\?
-    Les espaces sont un nouveau moyen de regrouper les salons et les personnes
     Votre espace privé
     Votre espace public
     Ajouter un espace
diff --git a/vector/src/main/res/values-fr/strings.xml b/vector/src/main/res/values-fr/strings.xml
index 08dcb09190c..12ca5e46bdc 100644
--- a/vector/src/main/res/values-fr/strings.xml
+++ b/vector/src/main/res/values-fr/strings.xml
@@ -2648,7 +2648,6 @@
     Message envoyé
     Vous pouvez changer ceci plus tard
     Quel type d’espace voulez-vous créer \?
-    Les espaces sont un nouveau moyen de regrouper les salons et les personnes
     Votre espace privé
     Votre espace public
     Ajouter un espace
diff --git a/vector/src/main/res/values-hu/strings.xml b/vector/src/main/res/values-hu/strings.xml
index b2067441198..ac136bc0f04 100644
--- a/vector/src/main/res/values-hu/strings.xml
+++ b/vector/src/main/res/values-hu/strings.xml
@@ -2506,7 +2506,6 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze
     Létező térbe való belépéshez meghívó szükséges.
     Ezt később meg lehet változtatni
     Milyen típusú teret szeretnél készíteni\?
-    A Terek használata egy új lehetőség a szobák és felhasználók csoportosítására
     Privát tér
     Nyilvános tér
     Tér hozzáadása
diff --git a/vector/src/main/res/values-in/strings.xml b/vector/src/main/res/values-in/strings.xml
index 78c5b2708c1..5c8d06229f9 100644
--- a/vector/src/main/res/values-in/strings.xml
+++ b/vector/src/main/res/values-in/strings.xml
@@ -2435,7 +2435,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
     Untuk bergabung ke space yang sudah ada, Anda membutuhkan undangan ke space itu.
     Anda dapat mengubahnya nanti
     Tipe space apa yang Anda ingin buat\?
-    Space adalah cara baru untuk mengelompokkan ruangan dan pengguna
     Space privat Anda
     Space publik Anda
     Tambahkan Space
diff --git a/vector/src/main/res/values-it/strings.xml b/vector/src/main/res/values-it/strings.xml
index 7e6433dca40..dfbf2d84f64 100644
--- a/vector/src/main/res/values-it/strings.xml
+++ b/vector/src/main/res/values-it/strings.xml
@@ -2690,7 +2690,6 @@
     Per unirti ad uno Spazio già esistente, ti serve un invito.
     Puoi cambiarlo in seguito
     Che tipo di Spazio vuoi creare\?
-    Gli Spazi sono un nuovo modo di raggruppare stanze e contatti
     Il tuo Spazio privato
     Il tuo Spazio pubblico
     Aggiungi Spazio
diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 50ffa35f89c..eb1d1336d87 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2521,7 +2521,6 @@
     正当な参加者が%sにアクセスできることを確認してください。これは後から変更できます。
     新規:スペースの参加者が非公開のルームを検索し、参加できるようになりました
     参加者を追加
-    スペースは、ルームや連絡先をグループ化する新しい方法です
     
         %d人の知り合いがすでに参加しています
     
diff --git a/vector/src/main/res/values-lv/strings.xml b/vector/src/main/res/values-lv/strings.xml
index 703ec4f229a..e5c808e2e5d 100644
--- a/vector/src/main/res/values-lv/strings.xml
+++ b/vector/src/main/res/values-lv/strings.xml
@@ -2358,7 +2358,6 @@ Nākotnē šī pārbaudes procedūra plānota sarežģītāka.
     Lai pievienotos esošai Telpai, ir nepieciešams ielūgums.
     To var mainīt vēlāk
     Kādu Telpu vēlaties izveidot\?
-    Telpas ir jauns veids, kā grupēt istabas un cilvēkus
     Jūsu privātā Telpa
     Jūsu publiskā Telpa
     Pievienot Telpu
diff --git a/vector/src/main/res/values-nl/strings.xml b/vector/src/main/res/values-nl/strings.xml
index 819958feb35..51c4d296880 100644
--- a/vector/src/main/res/values-nl/strings.xml
+++ b/vector/src/main/res/values-nl/strings.xml
@@ -3045,7 +3045,6 @@
     Met wie werkt u samen\?
     U kunt dit later wijzigen
     Wat voor soort space wilt u creëren\?
-    Spaces zijn een nieuwe manier om kamers en mensen te groeperen
     Uw privé space
     Uw openbare space
     Space toevoegen
diff --git a/vector/src/main/res/values-pl/strings.xml b/vector/src/main/res/values-pl/strings.xml
index f3b3888a205..a72331453f0 100644
--- a/vector/src/main/res/values-pl/strings.xml
+++ b/vector/src/main/res/values-pl/strings.xml
@@ -2725,7 +2725,6 @@
     Aby dołączyć do istniejącej przestrzeni, potrzebujesz zaproszenia.
     Możesz zmienić to później
     Jaki rodzaj przestrzeni chcesz stworzyć\?
-    Przestrzenie są nowym sposobem na organizację pokojów i osób
     Wiadomość wysłana
     Wstępna synchronizacja:
 \nPobieranie danych…
diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/vector/src/main/res/values-pt-rBR/strings.xml
index cda7517f6b7..f18198bbf43 100644
--- a/vector/src/main/res/values-pt-rBR/strings.xml
+++ b/vector/src/main/res/values-pt-rBR/strings.xml
@@ -2755,7 +2755,6 @@
     Para se juntar a um espaço existente, você precisa de um convite.
     Você pode mudar isto mais tarde
     Que tipo de espaço você quer criar\?
-    Espaços são uma nova forma de agrupar salas e pessoas
     Seu espaço privado
     Seu espaço público
     Adicionar Espaço
diff --git a/vector/src/main/res/values-ru/strings.xml b/vector/src/main/res/values-ru/strings.xml
index f164afc11bf..67f4ebf0dab 100644
--- a/vector/src/main/res/values-ru/strings.xml
+++ b/vector/src/main/res/values-ru/strings.xml
@@ -2822,7 +2822,6 @@
     Чтобы присоединиться к существующему пространству, вам необходимо получить приглашение.
     Вы сможете изменить это позже
     Какой тип пространства вы хотите создать\?
-    Пространства - это новый способ группировки комнат и людей
     Ваше приватное пространство
     Ваше публичное пространство
     Добавить пространство
diff --git a/vector/src/main/res/values-sk/strings.xml b/vector/src/main/res/values-sk/strings.xml
index dd410031a8c..bd15a87f830 100644
--- a/vector/src/main/res/values-sk/strings.xml
+++ b/vector/src/main/res/values-sk/strings.xml
@@ -2586,7 +2586,6 @@
     Ak sa chcete pripojiť k existujúcemu priestoru, potrebujete pozvánku.
     Neskôr to môžete zmeniť
     Aký typ priestoru chcete vytvoriť\?
-    Priestory sú novým spôsobom spájania miestností a ľudí
     Nesprávne používateľské meno a/alebo heslo. Zadané heslo začína alebo končí medzerami, skontrolujte ho.
     Používate beta verziu Priestorov. Vaša spätná väzba pomôže pri tvorbe ďalších verzií. Vaša platforma a používateľské meno budú zaznamenané, aby sme mohli čo najlepšie využiť vašu spätnú väzbu.
     Ďalšie priestory alebo miestnosti, o ktorých možno neviete
diff --git a/vector/src/main/res/values-sq/strings.xml b/vector/src/main/res/values-sq/strings.xml
index 1f49d5ba43f..81e5187efa7 100644
--- a/vector/src/main/res/values-sq/strings.xml
+++ b/vector/src/main/res/values-sq/strings.xml
@@ -2689,7 +2689,6 @@
     Që të hyni në një hapësirë ekzistuese, ju duhet një ftesë.
     Këtë mund ta ndryshoni më vonë
     Ç’lloj hapësire doni të krijoni\?
-    Hapësirat janë një mënyrë e re për të grupuar dhoma dhe persona
     Hapësira juaj private
     Hapësira juaj publike
     Shtoni Hapësirë
diff --git a/vector/src/main/res/values-sv/strings.xml b/vector/src/main/res/values-sv/strings.xml
index 45e3b550071..b1c3f0de55f 100644
--- a/vector/src/main/res/values-sv/strings.xml
+++ b/vector/src/main/res/values-sv/strings.xml
@@ -2660,7 +2660,6 @@
     För att gå med i ett existerande utrymme så behöver du en inbjudan.
     Detta kan ändras senare
     Vilket sorts utrymme vill du skapa\?
-    Utrymmen är ett nytt sätt att gruppera rum och personer
     Ditt privata utrymme
     Ditt offentliga utrymme
     Lägg till utrymme
diff --git a/vector/src/main/res/values-uk/strings.xml b/vector/src/main/res/values-uk/strings.xml
index 25703d19cbd..e1678e64d05 100644
--- a/vector/src/main/res/values-uk/strings.xml
+++ b/vector/src/main/res/values-uk/strings.xml
@@ -2024,7 +2024,6 @@
     Налаштувати безпечне резервне копіювання
     Безпечне резервне копіювання
     Простори — це новий спосіб згуртувати кімнати та людей.
-    Простори — це новий спосіб згуртувати кімнати та людей
     Надіслати відгук
     Можете зв’язатися зі мною, якщо у вас виникнуть додаткові запитання
     Лише запрошені, найкраще для себе та команд
diff --git a/vector/src/main/res/values-vi/strings.xml b/vector/src/main/res/values-vi/strings.xml
index c71e59d6857..6673cb4e4d8 100644
--- a/vector/src/main/res/values-vi/strings.xml
+++ b/vector/src/main/res/values-vi/strings.xml
@@ -1838,7 +1838,6 @@
     Để tham gia một Space hiện có, bạn cần một lời mời.
     Bạn có thể thay đổi điều này sau
     Bạn muốn tạo ra loại Space nào\?
-    Space là một cách mới để nhóm phòng và con người
     Space riêng tư của bạn
     Space công cộng của bạn
     Thêm Space
diff --git a/vector/src/main/res/values-zh-rCN/strings.xml b/vector/src/main/res/values-zh-rCN/strings.xml
index 953ae5c91a1..dfc24d92937 100644
--- a/vector/src/main/res/values-zh-rCN/strings.xml
+++ b/vector/src/main/res/values-zh-rCN/strings.xml
@@ -2556,7 +2556,6 @@
     正在寻找不在 %s 中的人?
     %s 邀请了你
     你被邀请
-    空间是一种将聊天室和人们进行分组的新方式
     空间是一种将聊天室和人们进行重新分组的新方式。
     欢迎来到空间!
     添加聊天室
diff --git a/vector/src/main/res/values-zh-rTW/strings.xml b/vector/src/main/res/values-zh-rTW/strings.xml
index ec901985ea0..b485f07c4c3 100644
--- a/vector/src/main/res/values-zh-rTW/strings.xml
+++ b/vector/src/main/res/values-zh-rTW/strings.xml
@@ -2650,7 +2650,6 @@
     要加入既有的空間,您需要邀請。
     您可以稍後再更改
     您想要建立哪種類型的空間?
-    空間是一種將聊天室與人們分組的新方式
     您的私人空間
     您的公開空間
     新增空間
diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml
index edf9ef189b9..43c8b9605ab 100644
--- a/vector/src/main/res/values/strings.xml
+++ b/vector/src/main/res/values/strings.xml
@@ -3524,8 +3524,6 @@
     Add Space
     Your public space
     Your private space
-    
-    Spaces are a new way to group rooms and people
     What type of space do you want to create?
     You can change this later
     To join an existing space, you need an invite.

From 2f048d8f8885a4b6ff06e8060eb13ddcdc1aea74 Mon Sep 17 00:00:00 2001
From: Benoit Marty 
Date: Tue, 22 Feb 2022 15:32:39 +0100
Subject: [PATCH 566/581] Remove unused string

---
 vector/src/main/res/values-cs/strings.xml     |  5 -----
 vector/src/main/res/values-de/strings.xml     |  5 -----
 vector/src/main/res/values-es/strings.xml     |  5 -----
 vector/src/main/res/values-et/strings.xml     |  5 -----
 vector/src/main/res/values-fa/strings.xml     |  5 -----
 vector/src/main/res/values-fr/strings.xml     |  5 -----
 vector/src/main/res/values-hu/strings.xml     |  5 -----
 vector/src/main/res/values-in/strings.xml     |  5 -----
 vector/src/main/res/values-it/strings.xml     |  5 -----
 vector/src/main/res/values-ja/strings.xml     |  2 --
 vector/src/main/res/values-nl/strings.xml     |  5 -----
 vector/src/main/res/values-pt-rBR/strings.xml |  5 -----
 vector/src/main/res/values-ru/strings.xml     |  5 -----
 vector/src/main/res/values-sk/strings.xml     |  5 -----
 vector/src/main/res/values-sq/strings.xml     |  5 -----
 vector/src/main/res/values-sv/strings.xml     |  5 -----
 vector/src/main/res/values-uk/strings.xml     |  5 -----
 vector/src/main/res/values-vi/strings.xml     |  5 -----
 vector/src/main/res/values-zh-rCN/strings.xml |  5 -----
 vector/src/main/res/values-zh-rTW/strings.xml |  5 -----
 vector/src/main/res/values/strings.xml        | 11 -----------
 21 files changed, 108 deletions(-)

diff --git a/vector/src/main/res/values-cs/strings.xml b/vector/src/main/res/values-cs/strings.xml
index 9070bbdb2ea..ae1e3868181 100644
--- a/vector/src/main/res/values-cs/strings.xml
+++ b/vector/src/main/res/values-cs/strings.xml
@@ -2917,11 +2917,6 @@
     Propojte tento e-mail se svým účtem
     Pozvánka do této místnosti byla odeslána na adresu %s, která není spojena s vaším účtem
     Pozvánka do tohoto prostoru byla odeslána na adresu %s, která není spojena s vaším účtem
-    Chcete-li členům prostoru pomoci najít soukromou místnost a připojit se k ní, přejděte do nastavení dané místnosti klepnutím na avatar.
-    Pomozte členům prostoru najít soukromé místnosti
-    Díky tomu mohou místnosti zůstat soukromé a zároveň je mohou lidé v prostoru najít a připojit se k nim. Všechny nové místnosti v prostoru budou mít tuto možnost k dispozici.
-    Pomozte lidem v prostorech, aby sami našli soukromé místnosti a připojili se k nim, není třeba všechny zvát ručně.
-    Novinka: Nechat lidi v prostorech vyhledat a připojit se k soukromým místnostem
     Skupinový hovor zahájen
     Všechny místnosti, ve kterých se nacházíte, se zobrazí v Úvodu.
     Zobrazit všechny místnosti v Úvodu
diff --git a/vector/src/main/res/values-de/strings.xml b/vector/src/main/res/values-de/strings.xml
index 54d69277e6c..d2523da0119 100644
--- a/vector/src/main/res/values-de/strings.xml
+++ b/vector/src/main/res/values-de/strings.xml
@@ -2895,7 +2895,6 @@
     Deine Schlüsselwörter
     Verknüpfe diese E-Mail-Adresse mit deinem Konto
     Die Einladung zu diesem Raum wurde an %s gesendet, welche nicht mit deinem Konto verknüpft ist
-    Hilf Space-Mitgliedern private Räume zu finden
     Auf deinem Mobilgerät wirst du keine Benachrichtigungen für Erwähnungen und Schlüsselwörter in verschlüsselten Räumen erhalten.
     Existierende Spaces hinzufügen
     Existierende Räume hinzufügen
@@ -3014,10 +3013,6 @@
     LaTeX-Mathematik aktivieren
     %s in den Einstellungen, um Einladungen direkt in Element zu erhalten.
     Diese Einladung zu diesem Raum wurde an %s gesendet, der nicht mit deinem Konto verbunden ist
-    Um Spacemitgliedern zu helfen, einen privaten Raum zu finden und ihm beizutreten, gehe zu den Einstellungen des Raums, indem du auf den Avatar tippst.
-    Dies macht es für Räume einfach, privat in einem Space zu bleiben, während die Leute im Space diese finden und ihnen beitreten können. Alle neuen Räume in einem Raum haben diese Option zur Verfügung.
-    Hilf Personen in Spaces, private Räume selbst zu finden und ihnen beizutreten, damit du nicht alle einzeln einladen musst.
-    Neu: Personen in Spaces können private Räume finden und ihnen beitreten
     Das System sendet automatisch Protokolle, wenn ein Fehler bei der Entschlüsselung auftritt
     Entschlüsselungsfehler automatisch melden.
     Auffindbarkeit (%s)
diff --git a/vector/src/main/res/values-es/strings.xml b/vector/src/main/res/values-es/strings.xml
index 071c7731e53..3b4c7617576 100644
--- a/vector/src/main/res/values-es/strings.xml
+++ b/vector/src/main/res/values-es/strings.xml
@@ -2938,11 +2938,6 @@ Por favor permite el acceso en la próxima ventana emergente para descubrir usua
     Vincula este correo electrónico con tu cuenta
     Esta invitación a este espacio se envió a %s que no está asociado con su cuenta
     Esta invitación a esta sala se envió a %s que no está asociado con su cuenta
-    Para ayudar a los miembros del espacio a encontrar y unirse a una sala privada, vaya a la configuración de esa sala tocando el avatar.
-    Ayuda a los miembros del espacio a encontrar salas privadas
-    Esto facilita que las habitaciones se mantengan privadas de un espacio, al tiempo que permite que las personas en el espacio las encuentren y se unan. Todas las habitaciones nuevas de un espacio tendrán esta opción disponible.
-    Ayude a las personas en los espacios a encontrar y unirse a salas privadas por sí mismas, sin necesidad de invitar a todos manualmente.
-    Nuevo: Permita que las personas en los espacios encuentren y se unan a salas privadas
     Tenga en cuenta que la mejora creará una nueva versión de la habitación. Todos los mensajes actuales permanecerán en esta sala archivada.
     Cualquiera en un espacio para padres podrá encontrar y unirse a esta sala, sin necesidad de invitar a todos manualmente. Podrás cambiar esto en la configuración de la habitación en cualquier momento.
     Cualquiera en %s podrá encontrar y unirse a esta sala, sin necesidad de invitar a todos manualmente. Podrás cambiar esto en la configuración de la habitación en cualquier momento.
diff --git a/vector/src/main/res/values-et/strings.xml b/vector/src/main/res/values-et/strings.xml
index e74f7ce6f72..c17bc1844ba 100644
--- a/vector/src/main/res/values-et/strings.xml
+++ b/vector/src/main/res/values-et/strings.xml
@@ -2865,11 +2865,6 @@
     Seo see e-posti aadress oma kasutajakontoga
     See kutse siia kogukonnakeskusesse saadeti aadressile %s, mis ei ole seotud sinu kontoga
     See kutse siia jututuppa saadeti aadressile %s, mis ei ole seotud sinu kontoga
-    Selleks, et kogukonnakeskuse liikmed saaks privaatseid jututube leida ning nendega liituda ava tunnuspildile klõpsides jututoa seadistused.
-    Aita kogukonnakeskuse liitmetel leida privaatseid jututube
-    See muudab lihtsaks, et jututoad jääksid kogukonnakeskuse piires privaatseks, kuid lasevad kogukonnakeskuses viibivatel inimestel need üles leida ja nendega liituda. Kõik kogukonnakeskuse uued jututoad on selle võimalusega.
-    Aita kogukonnakeskuse liikmetel endil leida privaatseid jututube ning nendega liituda nii et sa ei pea kõigile eraldi kutset saatma.
-    Uus funktsionaalsus: Võimalda kogukonnakeskuse liikmetel leida privaatseid jututube ning nendega liituda
     Rühmakõne algas
     Kõik sinu jututoad on nähtavad avalehel.
     Näita kõiki jututubasid avalehel
diff --git a/vector/src/main/res/values-fa/strings.xml b/vector/src/main/res/values-fa/strings.xml
index b8b7038d371..f76bf594a1b 100644
--- a/vector/src/main/res/values-fa/strings.xml
+++ b/vector/src/main/res/values-fa/strings.xml
@@ -2863,11 +2863,6 @@
     این رایانامه را به حسابتان پیوند دهید
     این دعوت به این فضا به %s فرستاده شده که با حسابتان در ارتباط نیست
     این دعوت به این اتاق به %s فرستاده شده که با حسابتان در ارتباط نیست
-    برای کمک به اعضای فضا برای یافتن و پیوستن به یک اتاق خصوصی، با زدن روی چهرک، به تنظیمات اتاق بروید.
-    کمک به اعضای فضا برای یافتن اتاق‌های خصوصی
-    این کار، خصوصی ماندن اتاق‌ها در فضا را، در عین اجازه به افراد داخل فضا برای یافتن و پیوستن، آسان می‌کند. تمامی اتاق‌های جدید در یک فضا، این گزینه را موجود دارند.
-    به افراد کمک‌کنید خودشان اتاق‌های خصوصی را یافته و بپیوندند. نیازی به دعوت دستی همه نیست.
-    جدید: بکذارید افراد در فضا، اتاق‌های خصوصی را یافته و بپیوندند
     تماس گروهی آغاز شد
     این اتاق را از %1$s به %2$s ارتقا خواهید داد.
     تمام اتاق‌هایی که‌در آن‌هایید، در خانه نشان داده خواهند شد.
diff --git a/vector/src/main/res/values-fr/strings.xml b/vector/src/main/res/values-fr/strings.xml
index 12ca5e46bdc..d92e498b9dd 100644
--- a/vector/src/main/res/values-fr/strings.xml
+++ b/vector/src/main/res/values-fr/strings.xml
@@ -2865,11 +2865,6 @@
     Lier cet e-mail à votre compte
     Cette invitation à cette espace a été envoyée à %s qui n’est pas associé à votre compte
     Cette invitation à ce salon a été envoyée à %s qui n’est pas associé à votre compte
-    Pour aider les membres de l’espace à trouver des salons privés, allez aux paramètres du salons en touchant l’avatar.
-    Aidez les membres de l’espace à trouver des salons privés
-    Cela permet de garder facilement un salon privé dans un espace, tout en laissant la possibilité aux gens de trouver l’espace et de le rejoindre. Tous les nouveaux salons de cet espace auront cette option disponible.
-    Aide les personnes dans les espaces à trouver et rejoindre des salons privés tout seuls, sans avoir à les inviter manuellement.
-    Nouveau : aidez les personnes dans les espaces à trouver et rejoindre des salons privés
     L’appel de groupe a démarré
     Tous les salons dans lesquels vous vous trouvez seront affichés sur l’Accueil.
     Montrer tous les salons dans Accueil
diff --git a/vector/src/main/res/values-hu/strings.xml b/vector/src/main/res/values-hu/strings.xml
index ac136bc0f04..078237ef3bc 100644
--- a/vector/src/main/res/values-hu/strings.xml
+++ b/vector/src/main/res/values-hu/strings.xml
@@ -2860,12 +2860,7 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze
     %s a Beállításokba a közvetlen meghívások fogadásához az Elemenetben.
     Ehhez a térhez a meghívó ide lett elküldve: %s, ami nincs összefüggésben a fiókoddal
     Ehhez a szobához a meghívó ide lett elküldve: %s, ami nincs összefüggésben a fiókoddal
-    A Tér tagságnak privát szoba megtalálásában és belépésben való segítséghez menj a szoba beállításaiba a profilképre való koppintással.
     Ennek az e-mailnek a fiókhoz való kötése
-    Segíts a tér tagságának privát szobák megtalálásában
-    A szobák egyszerűbben maradhatnak privátok a téren kívül, amíg a tér tagsága megtalálhatja és beléphet oda. Minden új szoba a téren rendelkezik ezzel a beállítási lehetőséggel.
-    Segíts a téren az embereknek privát szobák megtalálásába és a belépésben, nem szükséges a személyes meghívó.
-    Új: Engedd az embereknek a privát szobák megtalálását és a belépést
     Csoportos hívás elkezdődött
     Minden szoba amibe beléptél megjelenik a Kezdő téren.
     Minden szoba megjelenítése a Kezdő téren
diff --git a/vector/src/main/res/values-in/strings.xml b/vector/src/main/res/values-in/strings.xml
index 5c8d06229f9..bc533a12dde 100644
--- a/vector/src/main/res/values-in/strings.xml
+++ b/vector/src/main/res/values-in/strings.xml
@@ -2300,9 +2300,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
     Email
     Lanjut
     Email verifikasi akan dikirim ke kotak masuk Anda untuk mengkonfirmasi pengaturan kata sandi baru Anda.
-    Ini membuatnya mudah untuk ruangan tetap privat di ruangan, sambil membiarkan orang-orang di space dapat menemukan dan bergabung ke ruangannya. Semua ruangan baru di space akan memiliki opsi ini.
-    Bantu orang-orang di space untuk menemukan dan bergabung ruangan privat sendiri, tidak perlu mengundang semua secara manual.
-    Baru: Izinkan orang-orang di space untuk menemukan dan bergabung ruangan privat
     Dicatat bahwa meningkatkan akan membuat versi baru dari ruangannya. Semua pesan saat ini akan tetap di ruangan yang diarsip.
     Siapa saja di induk ruangan dapat menemukan dan bergabung ke ruangan ini — tidak perlu mengundang semua secara manual. Anda dapat mengubahnya di pengaturan ruangan kapan saja.
     Siapa saja di %s dapat menemukan dan bergabung ke ruangan ini — tidak perlu mengundang semua secara manual. Anda dapat mengubahnya di pengaturan ruangan kapan saja.
@@ -2627,8 +2624,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
     Tautkan email ini ke akun Anda
     Undangan space ini telah dikirim ke %s yang tidak diasosiasikan dengan akun Anda
     Undangan ruangan ini telah dikirim ke %s yang tidak diasosiasikan dengan akun Anda
-    Untuk membantu anggota space menemukan dan bergabung ke ruangan privat, pergilah ke pengaturan ruangannya dengan mengetuk pada avatarnya.
-    Bantu anggota space untuk menemukan ruangan privat
     Periksa ulang tautan ini
     Mohon pilih sebuah kata sandi.
     Mohon pilih sebuah nama pengguna.
diff --git a/vector/src/main/res/values-it/strings.xml b/vector/src/main/res/values-it/strings.xml
index dfbf2d84f64..46bd61afdff 100644
--- a/vector/src/main/res/values-it/strings.xml
+++ b/vector/src/main/res/values-it/strings.xml
@@ -2854,11 +2854,6 @@
     Collega questa email con il tuo account
     Questo invito per questo spazio è stato inviato a %s, la quale non è associata al tuo account
     Questo invito per questa stanza è stato inviato a %s, la quale non è associata al tuo account
-    Per aiutare i membri dello spazio a trovare ed entrare in una stanza privata, vai nelle impostazioni di quella stanza toccando il suo avatar.
-    Aiuta i membri dello spazio a trovare stanze private
-    Ciò rende facile mantenere private le stanze in uno spazio, mentre le persone potranno trovarle ed unirsi. Tutte le stanze nuove in uno spazio avranno questa opzione disponibile.
-    Aiuta le persone negli spazi a trovare ed entrare nelle stanze private da sole, non c\'è bisogno di invitarle a mano.
-    Novità: consenti alle persone negli spazi di trovare ed entrare nelle stanze private
     Chiamata di gruppo iniziata
     Tutte le stanze in cui sei appariranno nella pagina principale.
     Mostra tutte le stanze nella pagina principale
diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index eb1d1336d87..6ea7b83aa24 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2453,7 +2453,6 @@
     %1$sにより%2$sに
     質問あるいはトピック
     投票の質問あるいはトピック
-    スペースのメンバーが非公開のルームを発見できるよう手伝う
     少々お待ちください。少し時間がかかるかもしれません。
     ルームのバージョン 👓
     不安定
@@ -2519,7 +2518,6 @@
     %sして、このルームを皆に紹介しましょう。
     このコードを皆と共有し、スキャンして追加してもらい、会話を始めましょう。
     正当な参加者が%sにアクセスできることを確認してください。これは後から変更できます。
-    新規:スペースの参加者が非公開のルームを検索し、参加できるようになりました
     参加者を追加
     
         %d人の知り合いがすでに参加しています
diff --git a/vector/src/main/res/values-nl/strings.xml b/vector/src/main/res/values-nl/strings.xml
index 51c4d296880..2fbe83db1b4 100644
--- a/vector/src/main/res/values-nl/strings.xml
+++ b/vector/src/main/res/values-nl/strings.xml
@@ -2875,11 +2875,6 @@
     Space aanmaken…
     Aanmaken een space
     Gebeurtenis inhoud
-    Om leden van een kamer te helpen een privékamer te vinden en eraan deel te nemen, gat u naar de instellingen van die kamer door op de avatar te tikken.
-    Help leden van een space privékamers te vinden
-    Dit maakt het gemakkelijk voor kamers om privé te blijven voor een ruimte, terwijl mensen in de kamer ze kunnen vinden en erbij kunnen voegen. Alle nieuwe kamers in een kamer hebben deze optie beschikbaar.
-    Help mensen in kamers om zelf privékamers te vinden en eraan deel te nemen, het is niet nodig om iedereen handmatig uit te nodigen.
-    Nieuw: laat mensen in kamers, privékamers zoeken en er lid van worden
     Houd er rekening mee dat bij het upgraden een nieuwe versie van de kamer wordt gemaakt. Alle huidige berichten blijven in deze gearchiveerde kamer.
     Iedereen in een ouderkamer kan deze kamer vinden en er lid van worden. Het is niet nodig om iedereen handmatig uit te nodigen. U kunt dit op elk moment wijzigen in de kamer instellingen.
     Sta iedereen in %s toe om te zoeken en toegang te krijgen. U kunt ook andere kamers selecteren.
diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/vector/src/main/res/values-pt-rBR/strings.xml
index f18198bbf43..092460a80d9 100644
--- a/vector/src/main/res/values-pt-rBR/strings.xml
+++ b/vector/src/main/res/values-pt-rBR/strings.xml
@@ -2864,11 +2864,6 @@
     Linkar este email com sua conta
     Este convite para este espaço foi enviado para %s que não está associado com sua conta
     Este convite para esta sala foi enviado para %s que não está associado com sua conta
-    Para ajudar membros de espaço encontrarem e se juntarem uma sala privada, vá para as configurações dessa sala ao tocar no avatar.
-    Ajude membros de espaço encontrarem salas privadas
-    Isto facilita salas ficarem privadas a um espaço, enquanto deixa pessoas no espaço encontrarem e se juntarem a elas. Todas as salas novas em um espaço vão ter esta opção disponível.
-    Ajude pessoas em espaços a elas mesmas encontrarem e se juntarem a salas privadas, sem necessidade de manualmente convidar todo mundo.
-    Novo: Deixe pessoas em espaços encontrarem e se juntarem a salas privadas
     Chamada de grupo começada
     Todas as salas em que você está vão ser mostradas em Home.
     Mostrar todas as salas em Home
diff --git a/vector/src/main/res/values-ru/strings.xml b/vector/src/main/res/values-ru/strings.xml
index 67f4ebf0dab..5dbf5f42db5 100644
--- a/vector/src/main/res/values-ru/strings.xml
+++ b/vector/src/main/res/values-ru/strings.xml
@@ -2950,11 +2950,6 @@
     Свяжите этот адрес электронной почты с вашей учетной записью
     Приглашение в эту комнату было отправлено на %s, который не связан с вашей учетной записью
     Приглашение в это пространство было отправлено на %s, который не связан с вашей учетной записью
-    Чтобы помочь участникам пространства найти и присоединиться к частной комнате, перейдите в настройки этой комнаты, нажав на ее аватар.
-    Помогите участникам пространства в поиске частных комнат
-    Это позволяет комнатам оставаться приватными для пространства, в то же время позволяя людям в пространстве находить их и присоединяться к ним. Все новые комнаты в пространстве будут иметь эту возможность.
-    Помогите людям в пространствах самим находить и присоединяться к приватным комнатам, без необходимости вручную приглашать всех.
-    Новое: Позволяет людям в пространствах находить и присоединяться к приватным комнатам
     Начался групповой вызов
     Все комнаты, в которых вы находитесь, будут отображаться в Начале.
     Показать все комнаты в Начале
diff --git a/vector/src/main/res/values-sk/strings.xml b/vector/src/main/res/values-sk/strings.xml
index bd15a87f830..e0d87f48e4e 100644
--- a/vector/src/main/res/values-sk/strings.xml
+++ b/vector/src/main/res/values-sk/strings.xml
@@ -2405,8 +2405,6 @@
     Vyberte role potrebné na zmenu rôznych častí miestnosti
     Žiadna odpoveď
     Podržali ste hovor
-    Vďaka tomu môžu miestnosti zostať súkromné a zároveň ich môžu ľudia v priestore nájsť a pripojiť sa k nim. Túto možnosť budú mať k dispozícii všetky nové miestnosti v priestore.
-    Nové: Umožnite ľuďom v priestoroch nájsť a pripojiť sa k súkromným miestnostiam
     Umožniť komukoľvek v %s nájsť a získať prístup. Môžete vybrať aj iné priestory.
     Automaticky aktualizovať nadradený priestor
     Momentálne sa ľudia nemusia môcť pripojiť k súkromným miestnostiam, ktoré ste vytvorili.
@@ -2508,9 +2506,6 @@
     Toto je začiatok histórie vašich priamych správ s %s.
     Toto je začiatok tejto konverzácie.
     Toto je začiatok miestnosti %s.
-    Ak chcete pomôcť členom priestoru nájsť súkromnú miestnosť a pripojiť sa k nej, prejdite do nastavení danej miestnosti ťuknutím na obrázok.
-    Pomôcť členom priestoru nájsť súkromné miestnosti
-    Pomôžte ľuďom v priestoroch, aby sami našli súkromné miestnosti a pripojili sa k nim, aby nebolo potrebné každého ručne pozývať.
     Nepoznáte svoju prístupovú frázu pre zálohovanie kľúčov, môžete %s.
     Overte tohto používateľa potvrdením, že sa na jeho obrazovke zobrazujú nasledujúce jedinečné emotikony v rovnakom poradí.
     Zobrazenie niektorých užitočných informácií na pomoc pri ladení aplikácie
diff --git a/vector/src/main/res/values-sq/strings.xml b/vector/src/main/res/values-sq/strings.xml
index 81e5187efa7..4198be41135 100644
--- a/vector/src/main/res/values-sq/strings.xml
+++ b/vector/src/main/res/values-sq/strings.xml
@@ -2854,11 +2854,6 @@
     Lidheni këtë email me llogarinë tuaj
     Kjo ftesë për te kjo hapësirë u dërgua te %s që s’është i përshoqëruar me llogarinë tuaj
     Kjo ftesë për te kjo dhomë qe dërguar për %s që s’është i përshoqëruar me llogarinë tuaj
-    Për t’i ndihmuar anëtarët të gjejnë dhe hyjnë në një dhomë private, kaloni te rregullimet e asaj dhome duke prekur mbi avatarin.
-    Ndihmoni anëtarë hapësirash të gjejnë dhoma private
-    Kjo e bën të lehtë mbajtjen private të dhomave në një hapësirë, ndërkohë që u lejon njerëzve në hapësirë të gjejnë dhe hyjnë në të tilla. Krejt dhomat e reja në një hapësirë do ta ofrojnë këtë mundësi.
-    Ndihmojini njerëzit në hapësira të gjejnë dhe hyjnë vetë në dhoma private, pa pasur nevojë për ftesë dorazi të gjithkujt.
-    E re: Lejoni persona në hapësira të gjejnë dhe hyjnë në dhoma private
     U fillua thirrje grupi
     Krejt dhomat ku gjendeni do të shfaqen te Home.
     Shfaq krejt dhomat te Home
diff --git a/vector/src/main/res/values-sv/strings.xml b/vector/src/main/res/values-sv/strings.xml
index b1c3f0de55f..e0b41886b9a 100644
--- a/vector/src/main/res/values-sv/strings.xml
+++ b/vector/src/main/res/values-sv/strings.xml
@@ -2864,11 +2864,6 @@
     Länka den här e-postadressen med ditt konto
     Denna inbjudan till det här utrymmet skickades till %s, vilket inte är associerat med ditt konto
     Denna inbjudan till det här rummet skickades till %s, vilket inte är associerat med ditt konto
-    För att hjälpa utrymmesmedlemmar att hitta och gå med i ett privat rum, gå till rummets inställningar genom att trycka på avataren.
-    Hjälp utrymmesmedlemmar att hitta privata rum
-    Detta gör det enklare för rum att hållas privata till ett utrymme, medans personer i utrymmet kan hitta och gå med i dem. Alla nya rum i ett utrymme kommer ha det här alternativet tillgängligt.
-    Hjälp personer i utrymmen att hitta och gå med i privata rum själva, utan behov av att manuellt bjuda in alla.
-    Nytt: Låt personer i utrymmen hitta och gå med i privata rum
     Gruppsamtal påbörjat
     Visa alla rum i Hem
     Alla rum du är i kommer att visas i Hem.
diff --git a/vector/src/main/res/values-uk/strings.xml b/vector/src/main/res/values-uk/strings.xml
index e1678e64d05..d9215cc654c 100644
--- a/vector/src/main/res/values-uk/strings.xml
+++ b/vector/src/main/res/values-uk/strings.xml
@@ -2163,7 +2163,6 @@
     Відео
     має не надіслану чернетку
     Деякі повідомлення не було надіслано
-    Щоб допомогти учасникам простору знайти та приєднатися до приватної кімнати, перейдіть до її налаштувань, торкнувшись аватара.
     Видалити аватар
     Установити аватар
     Змінити аватар
@@ -2982,8 +2981,6 @@
 \n - Домашній сервер, до якого під\'єднаний користувач, якого ви перевірили
 \n - Ваше інтернет-з\'єднання або інтернет-з\'єднання вашого користувача
 \n - Ваш пристрій або пристрій іншого користувача
-    Допоможіть учасникам простору знаходити приватні кімнати
-    Нове: Дозволити людям у просторах знаходити та приєднуватися до приватних кімнат
     Лише в цю кімнату
     Вони зможуть переглядати %s
     Запрошення за іменем користувача або е-поштою
@@ -3018,8 +3015,6 @@
 \nСпробуйте згодом або запитайте адміністратора кімнати, чи є у вас доступ.
     Це запрошення до простору надіслане %s, не пов\'язаній із вашим обліковим записом
     Це запрошення до кімнати надіслане %s, не пов\'язаній із вашим обліковим записом
-    Кімнати можуть лишатися закритими для людей ззовні простору, водночас люди в просторі можуть знаходити їх і приєднуватися. Всі нові кімнати в просторі матимуть цю опцію.
-    Допомагає людям у просторах знаходити закриті кімнати й приєднуватися самостійно, без потреби вручну запрошувати всіх.
     Зауважте, що поліпшення створить нову версію кімнати. Всі наявні повідомлення залишаться в цій архівованій кімнаті.
     Будь-хто в батьківському просторі зможемо знайти кімнату й долучитись — нема потреба вручну запрошувати всіх. Можна змінити це в налаштуваннях кімнати будь-коли.
     Будь-хто в %s зможе знайти кімнату й долучитись — нема потреби вручну запрошувати всіх. Можна змінити це в налаштуваннях кімнати будь-коли.
diff --git a/vector/src/main/res/values-vi/strings.xml b/vector/src/main/res/values-vi/strings.xml
index 6673cb4e4d8..ad7c87b64f6 100644
--- a/vector/src/main/res/values-vi/strings.xml
+++ b/vector/src/main/res/values-vi/strings.xml
@@ -1709,11 +1709,6 @@
     Liên kết email này với tài khoản của bạn
     Lời mời này đến Space này đã được gửi đến %s không được liên kết với tài khoản của bạn
     Lời mời này đến phòng này đã được gửi đến %s không được liên kết với tài khoản của bạn
-    Để giúp các thành viên Space tìm và tham gia một phòng riêng, hãy vào cài đặt của căn phòng đó bằng cách nhấn vào hình đại diện.
-    Giúp các thành viên Space tìm phòng riêng
-    Điều này giúp các phòng dễ dàng giữ riêng tư cho một Space, đồng thời cho phép mọi người trong Space tìm và tham gia cùng họ. Tất cả các phòng mới trong một Space sẽ có tùy chọn này có sẵn.
-    Giúp mọi người trong Space tự tìm và tham gia phòng riêng, không cần phải tự mời mọi người.
-    Mới: Cho phép mọi người trong Space tìm và tham gia phòng riêng
     Xin lưu ý nâng cấp sẽ tạo ra một phiên bản mới của căn phòng. Tất cả các tin nhắn hiện tại sẽ ở trong phòng lưu trữ này.
     Bất cứ ai trong Space cha mẹ sẽ có thể tìm và tham gia căn phòng này - không cần phải mời mọi người theo cách thủ công. Bạn sẽ có thể thay đổi điều này trong cài đặt phòng bất cứ lúc nào.
     Bất kỳ ai trong %s sẽ có thể tìm và tham gia phòng này - không cần phải mời mọi người theo cách thủ công. Bạn sẽ có thể thay đổi điều này trong cài đặt phòng bất cứ lúc nào.
diff --git a/vector/src/main/res/values-zh-rCN/strings.xml b/vector/src/main/res/values-zh-rCN/strings.xml
index dfc24d92937..93132824919 100644
--- a/vector/src/main/res/values-zh-rCN/strings.xml
+++ b/vector/src/main/res/values-zh-rCN/strings.xml
@@ -2813,11 +2813,6 @@
     将此邮箱与您的账户相链接
     加入这个空间的邀请被发送至 %s,此邮箱未与您的账户相关联
     加入这个聊天室的邀请被发送至 %s,此邮箱未与您的账户相关联
-    要帮助空间成员找到并加入一个私人聊天室,只需点击头像进入聊天室设置。
-    帮助空间成员找到私人聊天室
-    这使聊天室很容易在空间中保持私密性,同时让空间中的人们找到并加入它们。空间中的所有新聊天室都有这个选项。
-    帮助空间里的人们自己找到和加入私人房间,不需要手动邀请每个人。
-    新:让人们在空间中找到并加入私人聊天室
     群通话已启动
     你所在的全部聊天室将显示在主页上。
     在主页上显示所有聊天室
diff --git a/vector/src/main/res/values-zh-rTW/strings.xml b/vector/src/main/res/values-zh-rTW/strings.xml
index b485f07c4c3..a5da6e876cd 100644
--- a/vector/src/main/res/values-zh-rTW/strings.xml
+++ b/vector/src/main/res/values-zh-rTW/strings.xml
@@ -2811,11 +2811,6 @@
     將此電子郵件與您的帳號連結
     此空間的邀請已傳送給與您的帳號無關的 %s
     此聊天室的邀請已傳送給與您的帳號無關的 %s
-    要協助空間成員尋找並加入私人聊天室,請點擊大頭照進入該聊天是的設定中。
-    協助空間成員尋找私人聊天室
-    這讓聊天室可以輕鬆地對空間維持隱密,同時讓空間中的夥伴找到並加入它們。空間中的所有新的聊天室都將提供此選項。
-    協助空間內的夥伴自己尋找私人聊天室,不需要手動邀請所有人。
-    新功能:讓空間中的人尋找並加入私人聊天室
     群組通話開始
     您所在的所有聊天室都會顯示在 Home 中。
     顯示 Home 中的所有聊天室
diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml
index 43c8b9605ab..b8ebd90fc99 100644
--- a/vector/src/main/res/values/strings.xml
+++ b/vector/src/main/res/values/strings.xml
@@ -3687,17 +3687,6 @@
 
     Please note upgrading will make a new version of the room. All current messages will stay in this archived room.
 
-    
-    New: Let people in spaces find and join private rooms
-    
-    Help people in spaces to find and join private rooms themselves, no need to manually invite everyone.
-    
-    This makes it easy for rooms to stay private to a space, while letting people in the space find and join them. All new rooms in a space will have this option available.
-    
-    Help space members find private rooms
-    
-    To help space members find and join a private room, go to that room’s settings by tapping on the avatar.
-
     
     This invite to this room was sent to %s which is not associated with your account
     

From 672e798e7c09c80d0395a5bb16cc6faa84841838 Mon Sep 17 00:00:00 2001
From: Benoit Marty 
Date: Tue, 22 Feb 2022 15:34:14 +0100
Subject: [PATCH 567/581] Remove unused string

---
 vector/src/main/res/values-bg/strings.xml     | 1 -
 vector/src/main/res/values-ca/strings.xml     | 1 -
 vector/src/main/res/values-cs/strings.xml     | 2 --
 vector/src/main/res/values-de/strings.xml     | 2 --
 vector/src/main/res/values-el/strings.xml     | 1 -
 vector/src/main/res/values-eo/strings.xml     | 1 -
 vector/src/main/res/values-es/strings.xml     | 1 -
 vector/src/main/res/values-et/strings.xml     | 2 --
 vector/src/main/res/values-fa/strings.xml     | 2 --
 vector/src/main/res/values-fi/strings.xml     | 1 -
 vector/src/main/res/values-fr-rCA/strings.xml | 1 -
 vector/src/main/res/values-fr/strings.xml     | 2 --
 vector/src/main/res/values-fy/strings.xml     | 1 -
 vector/src/main/res/values-hu/strings.xml     | 2 --
 vector/src/main/res/values-in/strings.xml     | 2 --
 vector/src/main/res/values-it/strings.xml     | 2 --
 vector/src/main/res/values-iw/strings.xml     | 1 -
 vector/src/main/res/values-ja/strings.xml     | 2 --
 vector/src/main/res/values-kab/strings.xml    | 1 -
 vector/src/main/res/values-lv/strings.xml     | 1 -
 vector/src/main/res/values-nb-rNO/strings.xml | 1 -
 vector/src/main/res/values-nl/strings.xml     | 2 --
 vector/src/main/res/values-pl/strings.xml     | 1 -
 vector/src/main/res/values-pt-rBR/strings.xml | 2 --
 vector/src/main/res/values-ru/strings.xml     | 2 --
 vector/src/main/res/values-sk/strings.xml     | 1 -
 vector/src/main/res/values-sq/strings.xml     | 1 -
 vector/src/main/res/values-sv/strings.xml     | 1 -
 vector/src/main/res/values-th/strings.xml     | 1 -
 vector/src/main/res/values-tr/strings.xml     | 2 --
 vector/src/main/res/values-uk/strings.xml     | 2 --
 vector/src/main/res/values-vi/strings.xml     | 1 -
 vector/src/main/res/values-zh-rCN/strings.xml | 1 -
 vector/src/main/res/values-zh-rTW/strings.xml | 2 --
 vector/src/main/res/values/strings.xml        | 4 ----
 35 files changed, 53 deletions(-)

diff --git a/vector/src/main/res/values-bg/strings.xml b/vector/src/main/res/values-bg/strings.xml
index edab57ba1b7..0e8b35a9b6e 100644
--- a/vector/src/main/res/values-bg/strings.xml
+++ b/vector/src/main/res/values-bg/strings.xml
@@ -2010,7 +2010,6 @@
     Не е добавен имейл адрес в профила ви
     Имейл адрес
     Не е добавен телефонен номер в профила ви
-    Търсенето в шифровани стаи все още не се поддържа.
     Филтрирай блокираните потребители
     Премахването на блокирането ще позволи на потребителя пак да влезе в стаята.
     Премахване на блокиране
diff --git a/vector/src/main/res/values-ca/strings.xml b/vector/src/main/res/values-ca/strings.xml
index 4a0e8c6a472..2ac886d3d1d 100644
--- a/vector/src/main/res/values-ca/strings.xml
+++ b/vector/src/main/res/values-ca/strings.xml
@@ -1464,7 +1464,6 @@
 \n
 \nVols iniciar sessió utilitzant un client web\?
     No pots fer això des d\'${app_name} per a mòbils
-    La cerca en sales xifrades encara no està disponible.
     La sala encara no s\'ha acabat de crear. Vols cancel·lar la seva creació\?
     No s\'ha trobat aquesta sala. Assegura\'t que existeixi.
     Mostra detalls com per exemple noms de sala i contingut dels missatges.
diff --git a/vector/src/main/res/values-cs/strings.xml b/vector/src/main/res/values-cs/strings.xml
index ae1e3868181..c7ae3aa034f 100644
--- a/vector/src/main/res/values-cs/strings.xml
+++ b/vector/src/main/res/values-cs/strings.xml
@@ -2456,7 +2456,6 @@
     Aplikace přijímá PUSH
     Aplikace čeká na PUSH
     Testovat push
-    Prohledávání šifrovaných místností ještě není podporováno.
     Filtrovat vykázané uživatele
     Nemáte oprávnění k zahájení hovoru
     Nemáte oprávnění k zahájení konferenčního hovoru
@@ -3113,7 +3112,6 @@
     Automaticky nahlašovat chyby dešifrování.
     Přepsat barvu přezdívky
     Již mám účet
-    Zlepšete týmovou komunikaci.
     Bezpečené zasílání zpráv.
     Máte vše pod kontrolou.
     Vlastněte své konverzace.
diff --git a/vector/src/main/res/values-de/strings.xml b/vector/src/main/res/values-de/strings.xml
index d2523da0119..e617540a951 100644
--- a/vector/src/main/res/values-de/strings.xml
+++ b/vector/src/main/res/values-de/strings.xml
@@ -2351,7 +2351,6 @@
     Die Applikation empfängt den PUSH
     Die Applikation wartet auf den PUSH
     Push testen
-    Die Suche in verschlüsselten Räumen wird noch nicht unterstützt.
     Gebannte Nutzer filtern
     Du bist nicht berechtigt einen Anruf zu starten
     Du hast keine Berechtigung ein Konferenzgespräch zu starten
@@ -3051,7 +3050,6 @@
         Endgültiges Ergebnis basiert auf %1$d Stimme
         Endgültiges Ergebnis basiert auf %1$d Stimmen
     
-    Element verbindet Datenschutz mit tollen Features.
     Verschlüsselung wiederherstellen
     Standort freigeben
     Standort freigeben
diff --git a/vector/src/main/res/values-el/strings.xml b/vector/src/main/res/values-el/strings.xml
index 48fca269d97..40eadb4bfed 100644
--- a/vector/src/main/res/values-el/strings.xml
+++ b/vector/src/main/res/values-el/strings.xml
@@ -593,7 +593,6 @@
     Ρυθμίσεις
     Λεπτομέρειες Δωματίου
     ΚΑΛΕΣΜΕΝΟΣ/Η
-    Η αναζήτηση σε κρυπτογραφημένα δωμάτια δεν υποστηρίζεται ακόμα.
     ΑΡΧΕΙΑ
     ΜΗΝΥΜΑΤΑ
     ΔΩΜΑΤΙΑ
diff --git a/vector/src/main/res/values-eo/strings.xml b/vector/src/main/res/values-eo/strings.xml
index fd97994323c..bdf8e88ed5d 100644
--- a/vector/src/main/res/values-eo/strings.xml
+++ b/vector/src/main/res/values-eo/strings.xml
@@ -1862,7 +1862,6 @@
         %1$s ĉambro trovita por %2$s
         %1$s ĉambroj trovitaj por %2$s
     
-    Serĉado en ĉifritaj ĉambroj ankoraŭ en estas subtenata.
     Filtri forbaritajn uzantojn
     Akceptu la atestilon nur se administranto de la servilo publikigis fingrospuron akordan kun tiu ĉi-supre.
     La atestilo ŝanĝiĝis de antaŭe fidata al alia, nefidata. Eble la servilo renovigis sian atestilon. Kontaktu la administranton de la servilo por ricevi la ĝustan fingrospuron.
diff --git a/vector/src/main/res/values-es/strings.xml b/vector/src/main/res/values-es/strings.xml
index 3b4c7617576..5de1aa8bc36 100644
--- a/vector/src/main/res/values-es/strings.xml
+++ b/vector/src/main/res/values-es/strings.xml
@@ -2358,7 +2358,6 @@ Por favor permite el acceso en la próxima ventana emergente para descubrir usua
     La aplicación está recibiendo PUSH
     La aplicación está esperando al PUSH
     Probar Push
-    Todavía no se puede hacer búsquedas en salas cifradas.
     Filtrar usuarios excluidos
     Enviar la historia de peticiones de claves compartidas
     No hay más resultados
diff --git a/vector/src/main/res/values-et/strings.xml b/vector/src/main/res/values-et/strings.xml
index c17bc1844ba..75e9eaabd71 100644
--- a/vector/src/main/res/values-et/strings.xml
+++ b/vector/src/main/res/values-et/strings.xml
@@ -2399,7 +2399,6 @@
     Rakendus võtab vastu tõuketeavitust
     Rakendus ootab tõuketeavitust
     Tõuketeavituse test
-    Otsing krüptitud jututubades ei ole veel toetatud.
     Otsi suhtluskeelu saanud kasutajaid
     Sul ei ole õigusi siin jututoas helistamiseks
     Sul ei ole piisavalt õigusi, et selles jututoas alustada konverentsikõnet
@@ -3054,7 +3053,6 @@
     Automaatselt teata dekrüptimise vigadest.
     Asenda hüüdnime värvid
     Mul on kasutajakonto juba olemas
-    Lase tiimidel vabalt tegutseda.
     Turvaline sõnumivahetus.
     Sul on kontroll oma andmete üle.
     Vestlused, mida sa tegelikult ka omad.
diff --git a/vector/src/main/res/values-fa/strings.xml b/vector/src/main/res/values-fa/strings.xml
index f76bf594a1b..1d514025b0b 100644
--- a/vector/src/main/res/values-fa/strings.xml
+++ b/vector/src/main/res/values-fa/strings.xml
@@ -1732,7 +1732,6 @@
     هیچ رایانامه‌ای به حسابتان افزوده نشده
     نشانی‌های رایانامه
     هیچ شماره تلفنی به حسابتان افزوده نشده
-    قابلیت جستجو در اتاق‌های رمزشده هنوز پیاده‌سازی نشده است.
     نتیجه‌ای در پی نداشت
     فیلترکردن کاربران مسدود شده
     لغو دانلود
@@ -3074,7 +3073,6 @@
     ایجاد حساب
     پیام‌رسانی برای گروهتان.
     مکان
-    قطع اسلک از تیم‌ها.
     پرداخت مکان‌های کاربری در خط زمانی
     پس از به کار افتادن، قادر خواهید بد مکانتان را به هر اتاقی بفرستید
     ${app_name} نتوانست به مکانتان دست یابد. لطفاً بعداً دوباره تلاش کنید.
diff --git a/vector/src/main/res/values-fi/strings.xml b/vector/src/main/res/values-fi/strings.xml
index 724fe7759ff..a4b217f504e 100644
--- a/vector/src/main/res/values-fi/strings.xml
+++ b/vector/src/main/res/values-fi/strings.xml
@@ -2517,7 +2517,6 @@
     Poistetaanko %s\?
     Ei mitään
     Vain maininnat ja avainsanat
-    Hakeminen salatuista huoneista ei ole vielä tuettu.
     Vaihda avaruuden nimeä
     Ota avaruuden salaus käyttöön
     Avaruuden käyttöoikeudet
diff --git a/vector/src/main/res/values-fr-rCA/strings.xml b/vector/src/main/res/values-fr-rCA/strings.xml
index 4f57799d801..58004d19327 100644
--- a/vector/src/main/res/values-fr-rCA/strings.xml
+++ b/vector/src/main/res/values-fr-rCA/strings.xml
@@ -1443,7 +1443,6 @@
     Notification sonore pour chaque message
     Recherche dans le répertoire…
     Parcourir le répertoire
-    La recherche dans les salons chiffrés n\'est pas encore prise en charge.
     FICHIERS
     PARTICIPANTS
     MESSAGES
diff --git a/vector/src/main/res/values-fr/strings.xml b/vector/src/main/res/values-fr/strings.xml
index d92e498b9dd..d51d644f0db 100644
--- a/vector/src/main/res/values-fr/strings.xml
+++ b/vector/src/main/res/values-fr/strings.xml
@@ -2339,7 +2339,6 @@
     Aucune adresse e-mail n’a été ajoutée à votre compte
     Adresses e-mail
     Aucun numéro de téléphone n’a été ajouté à votre compte
-    La recherche dans les salons chiffrés n\'est pas encore prise en charge.
     Filtrer les utilisateurs exclus
     Ne plus ignorer cet utilisateur aura pour effet de ré-afficher ses messages.
     expulser un utilisateur le supprimera de ce salon.
@@ -3054,7 +3053,6 @@
     Signalement automatique des erreurs de déchiffrement.
     Outrepasser la couleur du pseudo
     J’ai déjà un compte
-    Être vicTeams, ce n’est pas Slack vous voulez.
     Messagerie sécurisée.
     Vous êtes aux commandes.
     Contrôlez vos conversations.
diff --git a/vector/src/main/res/values-fy/strings.xml b/vector/src/main/res/values-fy/strings.xml
index bf6c65c92ec..c3fd5c36e56 100644
--- a/vector/src/main/res/values-fy/strings.xml
+++ b/vector/src/main/res/values-fy/strings.xml
@@ -1402,7 +1402,6 @@
         %d keamers
     
     Katalogus trochblêdzje
-    Sykjen yn fersifere keamers wurdt op dit stuit net stipe.
     BESTANNEN
     PERSOANEN
     BERJOCHTEN
diff --git a/vector/src/main/res/values-hu/strings.xml b/vector/src/main/res/values-hu/strings.xml
index 078237ef3bc..aad01c11f9f 100644
--- a/vector/src/main/res/values-hu/strings.xml
+++ b/vector/src/main/res/values-hu/strings.xml
@@ -2381,7 +2381,6 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze
     Push tesztelése
     Ellenőrizd, hogy rákattintottál arra a hivatkozásra amit e-mailben küldtünk neked.
     %s törlése\?
-    Titkosított szobákban való keresés egyelőre nem támogatott.
     Kitiltott felhasználók szűrése
     Téma megváltoztatása
     Szoba fejlesztése
@@ -3052,7 +3051,6 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze
     Kamera megnyitása
     Becenév színének megváltoztatása
     Már van fiókom
-    Rázd fel a csoportjaidat.
     Biztonságos üzenetküldés.
     Te irányítasz.
     Tartózkodási hely megosztása
diff --git a/vector/src/main/res/values-in/strings.xml b/vector/src/main/res/values-in/strings.xml
index bc533a12dde..5b86d5aa679 100644
--- a/vector/src/main/res/values-in/strings.xml
+++ b/vector/src/main/res/values-in/strings.xml
@@ -985,7 +985,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
     Anda tidak dapat melakukan ini dari ponsel ${app_name}
     Konfirmasi kata sandi Anda
     Tidak ada nomor telepon yang ditambahkan ke akun Anda
-    Pencarian di ruangan terenkripsi belum didukung.
     Saring pengguna yang dicekal
     Mengubah topik
     Meng-upgrade ruangan ini
@@ -2999,7 +2998,6 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
     Laporkan Kesalahan Dekripsi Secara Otomatis.
     Ubah warna nama tampilan
     Saya sudah punya akun
-    Bebaskan.
     Perpesanan yang aman.
     Anda dalam kendali.
     Miliki percakapan Anda.
diff --git a/vector/src/main/res/values-it/strings.xml b/vector/src/main/res/values-it/strings.xml
index 46bd61afdff..a07a02a66a6 100644
--- a/vector/src/main/res/values-it/strings.xml
+++ b/vector/src/main/res/values-it/strings.xml
@@ -2384,7 +2384,6 @@
     Ricezione push fallita. La soluzione può essere di reinstallare l\'applicazione.
     L\'applicazione sta ricevendo il PUSH
     Prova push
-    La ricerca in stanze cifrate non è ancora supportata.
     Filtra utenti banditi
     Non hai il permesso di avviare una chiamata
     Non hai il permesso di avviare una chiamata di gruppo
@@ -3044,7 +3043,6 @@
     Auto-segnala errori di decifrazione.
     Sovrascrivi colore nick
     Ho già un account
-    Riduci il carico ai team.
     Messaggistica sicura.
     Tu hai il controllo.
     Prendi il controllo delle tue conversazioni.
diff --git a/vector/src/main/res/values-iw/strings.xml b/vector/src/main/res/values-iw/strings.xml
index 744e3d726b2..b6f83d4822d 100644
--- a/vector/src/main/res/values-iw/strings.xml
+++ b/vector/src/main/res/values-iw/strings.xml
@@ -679,7 +679,6 @@
         %d חדרים
     
     חפש בתיקיות
-    עדיין אין תמיכה בחיפוש בחדרים מוצפנים.
     קבצים
     אנשים
     הודעות
diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 6ea7b83aa24..9dbad1bc224 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -1204,7 +1204,6 @@
     %sを削除しますか?
     認証が必要です
     パスワードを確認
-    暗号化されたルームでの検索は、まだサポートされていません。
     ブロックされたユーザーを絞り込む
     トピックを変更
     ルームをアップグレード
@@ -2507,7 +2506,6 @@
     エンドツーエンドで暗号化され、電話番号不要。広告やデータマイニング無し。
     会話の保存先を自分で決められ、自分で管理できる独立したコミュニケーション。Matrixをもとに。
     お宅での対面会話と同じぐらいのプライバシーを提供する、セキュアで独立したコミュニケーション。
-    チームワークを効率よくしましょう。
     セキュアメッセージング
     管理権を握るのは、あなたです。
     Elementの使用に関するヘルプ
diff --git a/vector/src/main/res/values-kab/strings.xml b/vector/src/main/res/values-kab/strings.xml
index 0f22997e23a..08bf1f913f2 100644
--- a/vector/src/main/res/values-kab/strings.xml
+++ b/vector/src/main/res/values-kab/strings.xml
@@ -2273,7 +2273,6 @@
     Ur tesɛiḍ ara tasiregt ad tebduḍ asiwel deg texxamt-a
     Issedmer s: %s
     Sekyed Push
-    Anadi deg texxamin yettwawgelhen ur yettusefrak ara akka tura.
     Sizdeg iseqdacen yettwagedlen
     Mudd tisirag i unekcum ɣer yinermisen-inek;inem.
     I usmiḍen n tengalt QR, tesriḍ ad tsirgeḍ anekcum ɣer tkamiṛat.
diff --git a/vector/src/main/res/values-lv/strings.xml b/vector/src/main/res/values-lv/strings.xml
index e5c808e2e5d..41fcd1e1fa3 100644
--- a/vector/src/main/res/values-lv/strings.xml
+++ b/vector/src/main/res/values-lv/strings.xml
@@ -1144,7 +1144,6 @@ Nākotnē šī pārbaudes procedūra plānota sarežģītāka.
     Apstipriniet paroli
     Jūsu kontam nav pievienots neviens tālruņa numurs
     Versija %s
-    Meklēšana šifrētās istabās pagaidām netiek atbalstīta.
     Mainīt tematu
     Mainīt atļaujas
     Nomainīt istabas nosaukumu
diff --git a/vector/src/main/res/values-nb-rNO/strings.xml b/vector/src/main/res/values-nb-rNO/strings.xml
index fa564a83c75..fcfba6f93cc 100644
--- a/vector/src/main/res/values-nb-rNO/strings.xml
+++ b/vector/src/main/res/values-nb-rNO/strings.xml
@@ -1018,7 +1018,6 @@
         %1$s rom funnet for %2$s
     
     Bla gjennom katalogen
-    Søking i krypterte rom støttes ikke ennå.
     FOLK
     Årsak til å rapportere dette innholdet
     Misdannet ID. Bør være en e-postadresse eller en Matrix ID som \'@localpart: domain\'
diff --git a/vector/src/main/res/values-nl/strings.xml b/vector/src/main/res/values-nl/strings.xml
index 2fbe83db1b4..84bd540e9bd 100644
--- a/vector/src/main/res/values-nl/strings.xml
+++ b/vector/src/main/res/values-nl/strings.xml
@@ -2286,7 +2286,6 @@
     De sessie is afgemeld!
     De kamer is verlaten!
     Alleen vermeldingen en trefwoorden
-    Zoeken in versleutelde kamers wordt nog niet ondersteund.
     Filter verbannen gebruikers
     Verander onderwerp
     Space upgraden
@@ -2869,7 +2868,6 @@
     Je wachtwoord is gereset.
     Ik heb mijn e-mailadres geverifieerd
     Tik op de link om uw nieuwe wachtwoord te bevestigen. Klik hieronder als u de link hebt gevolgd die erin staat.
-    Geef je teams meer ruimte.
     Word eigenaar van uw gesprekken.
     Space aanmaken
     Space aanmaken…
diff --git a/vector/src/main/res/values-pl/strings.xml b/vector/src/main/res/values-pl/strings.xml
index a72331453f0..fc852245ebe 100644
--- a/vector/src/main/res/values-pl/strings.xml
+++ b/vector/src/main/res/values-pl/strings.xml
@@ -2187,7 +2187,6 @@
     Żaden adres e-mail nie został dodany do Twojego konta
     Adres e-mail
     Nie dodano żadnego numeru telefonu do Twojego konta
-    Wyszukiwanie w pokojach stosujących szyfrowanie nie jest obecnie wspierane.
     Filtruj zbanowanych użytkowników
     Odbanowanie użytkownika pozwoli mu na ponowne dołączenie do pokoju.
     Zbanuj użytkownika
diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/vector/src/main/res/values-pt-rBR/strings.xml
index 092460a80d9..cff40afaa85 100644
--- a/vector/src/main/res/values-pt-rBR/strings.xml
+++ b/vector/src/main/res/values-pt-rBR/strings.xml
@@ -2391,7 +2391,6 @@
     Falha para receber push. Solução podia ser reinstalar o aplicativo.
     O aplicativo está recebendo PUSH
     O aplicativo está esperando pelo PUSH
-    Pesquisar em salas encriptadas não é suportado ainda.
     Você não tem permissão para começar uma chamada
     Você não tem permissão para começar uma chamada de conferência
     Resetar
@@ -3054,7 +3053,6 @@
     Auro Reportar Erros de Decriptação.
     Sobrepor cor de nick
     Eu já tenho uma conta
-    Dê liberdade a times.
     Mensageria segura.
     Você está em controle.
     Tenha posse de suas conversas.
diff --git a/vector/src/main/res/values-ru/strings.xml b/vector/src/main/res/values-ru/strings.xml
index 5dbf5f42db5..d5e878f9542 100644
--- a/vector/src/main/res/values-ru/strings.xml
+++ b/vector/src/main/res/values-ru/strings.xml
@@ -2457,7 +2457,6 @@
         %d приглашений
         %d приглашений
     
-    Поиск в зашифрованных комнатах пока не поддерживается.
     Фильтр заблокированных пользователей
     У вас нет разрешения на запуск звонка
     У вас нет разрешения на запуск конференции
@@ -3148,7 +3147,6 @@
     Сквозное шифрование не требующее номера телефона. Нет рекламы или сбора данных.
     Выбор где хранятся ваши разговоры дает вам власть и независимость. Подключено с помощью Matrix.
     Безопасное и независимое общение, обеспечивающее вам такой же уровень конфиденциальности, как при личном общении в вашем собственном доме.
-    Удалите шлак из команд.
     Безопасный обмен сообщениями.
     Ваше управление.
     Ваши собственные разговоры.
diff --git a/vector/src/main/res/values-sk/strings.xml b/vector/src/main/res/values-sk/strings.xml
index e0d87f48e4e..817cbd3fb1f 100644
--- a/vector/src/main/res/values-sk/strings.xml
+++ b/vector/src/main/res/values-sk/strings.xml
@@ -2397,7 +2397,6 @@
     Uistite sa, že ste klikli na odkaz v e-maile, ktorý sme vám poslali.
     Do vášho účtu nebol pridaný žiadny e-mail
     Do vášho účtu nebolo pridané žiadne telefónne číslo
-    Vyhľadávanie v zašifrovaných miestnostiach zatiaľ nie je podporované.
     Poslať udalosti m.room.server_acl
     Zmeniť hlavnú adresu miestnosti
     Odstrániť správy odoslané inými osobami
diff --git a/vector/src/main/res/values-sq/strings.xml b/vector/src/main/res/values-sq/strings.xml
index 4198be41135..6200c8f900e 100644
--- a/vector/src/main/res/values-sq/strings.xml
+++ b/vector/src/main/res/values-sq/strings.xml
@@ -2387,7 +2387,6 @@
     S’u arrit të merrej push. Zgjidhje mund të jetë riinstalimi i aplikacionit.
     Aplikacioni po merr PUSH
     Aplikacioni po pret për PUSH-in
-    Kërkimi në dhoma të fshehtëzuara nuk mbulohet ende.
     Filtro përdorues të dëbuar
     S’keni leje të nisni një thirrje
     S’keni leje të nisni një thirrje konferencë
diff --git a/vector/src/main/res/values-sv/strings.xml b/vector/src/main/res/values-sv/strings.xml
index e0b41886b9a..4aa1d41f053 100644
--- a/vector/src/main/res/values-sv/strings.xml
+++ b/vector/src/main/res/values-sv/strings.xml
@@ -2399,7 +2399,6 @@
     Appen tar emot PUSH:en
     Appen väntar på PUSH:en
     Testa push
-    Sökning i krypterade rum stöds inte än.
     Filtrera bannade användare
     Du är inte behörig att starta ett samtal
     Du är inte behörig att starta ett gruppsamtal
diff --git a/vector/src/main/res/values-th/strings.xml b/vector/src/main/res/values-th/strings.xml
index c0a5da16048..0304a52a211 100644
--- a/vector/src/main/res/values-th/strings.xml
+++ b/vector/src/main/res/values-th/strings.xml
@@ -173,7 +173,6 @@
     
         %d ห้อง
     
-    ยังไม่รองรับการค้นหาในห้องที่เข้ารหัส
     ไฟล์
     ผู้คน
     ข้อความ
diff --git a/vector/src/main/res/values-tr/strings.xml b/vector/src/main/res/values-tr/strings.xml
index d42e2532a1d..e75b3857b91 100644
--- a/vector/src/main/res/values-tr/strings.xml
+++ b/vector/src/main/res/values-tr/strings.xml
@@ -1325,7 +1325,6 @@
     Hesabınıza e-posta adresi eklenmemiş
     E-posta hesapları
     Hesabınıza hiçbir telefon numarası eklenmedi
-    Şifreli odalarda arama yapmak henüz desteklenmiyor.
     Banlanan kullanıcıları filtrele
     Konuyu değiştir
     Odayı geliştir
@@ -2223,7 +2222,6 @@
     Uçtan uca şifrelenir ve telefon numarası gerekmez. Reklam veya veri madenciliği yok.
     Size kontrol ve bağımsızlık vererek konuşmalarınızın nerede tutulacağını seçin. Matrix ile bağlandı.
     Size kendi evinizde yüz yüze görüşmeyle aynı düzeyde mahremiyet sağlayan güvenli ve bağımsız iletişim.
-    Takımların gevşekliğini kesin.
     Güvenli mesajlaşma.
     Kontrol sende.
     Konuşmalarınıza sahip çıkın.
diff --git a/vector/src/main/res/values-uk/strings.xml b/vector/src/main/res/values-uk/strings.xml
index d9215cc654c..1bbbb0bc73d 100644
--- a/vector/src/main/res/values-uk/strings.xml
+++ b/vector/src/main/res/values-uk/strings.xml
@@ -1104,7 +1104,6 @@
     Аудіо
     Створити нову кімнату
     Кімнати
-    Пошук у зашифрованих кімнатах не наразі не підтримується.
     Запитувати підтвердження перед початком виклику
     Запобігати випадковим викликам
     Мені не потрібні мої зашифровані повідомлення
@@ -3156,7 +3155,6 @@
     Автозвіт про помилки шифрування.
     Замінити колір псевдоніма
     Я вже маю обліковий запис
-    Удоскональте спілкування в команді.
     Захищене спілкування.
     Ви контролюєте все.
     Володійте своїми розмовами.
diff --git a/vector/src/main/res/values-vi/strings.xml b/vector/src/main/res/values-vi/strings.xml
index ad7c87b64f6..6e632a87d8b 100644
--- a/vector/src/main/res/values-vi/strings.xml
+++ b/vector/src/main/res/values-vi/strings.xml
@@ -1430,7 +1430,6 @@
         %d phòng
     
     Duyệt danh mục phòng
-    Tìm kiếm trong phòng mã hóa chưa được hỗ trợ.
     FILES
     NGƯỜI
     TIN NHẮN
diff --git a/vector/src/main/res/values-zh-rCN/strings.xml b/vector/src/main/res/values-zh-rCN/strings.xml
index 93132824919..fa5e819ec14 100644
--- a/vector/src/main/res/values-zh-rCN/strings.xml
+++ b/vector/src/main/res/values-zh-rCN/strings.xml
@@ -2307,7 +2307,6 @@
     应用正在接受推送
     应用正在等待推送
     测试推送
-    尚不支持在加密聊天室中搜索。
     过滤被封禁的用户
     你没有权限发起通话
     你没有权限发起会议通话
diff --git a/vector/src/main/res/values-zh-rTW/strings.xml b/vector/src/main/res/values-zh-rTW/strings.xml
index a5da6e876cd..b4a8808e491 100644
--- a/vector/src/main/res/values-zh-rTW/strings.xml
+++ b/vector/src/main/res/values-zh-rTW/strings.xml
@@ -2352,7 +2352,6 @@
     應用程式正在接收 PUSH
     應用程式正在等待 PUSH
     測試推播
-    目前尚不支援在已加密的聊天室中搜尋。
     過濾被封鎖的使用者
     您沒有開始通話的權限
     您沒有開始會議通話的權限
@@ -2995,7 +2994,6 @@
     自動回報解密錯誤。
     覆寫暱稱色彩
     我已有一個帳號
-    減少團隊的懈怠。
     安全傳送訊息。
     您已掌控了您的資料。
     擁有您的對話。
diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml
index b8ebd90fc99..2da817767e0 100644
--- a/vector/src/main/res/values/strings.xml
+++ b/vector/src/main/res/values/strings.xml
@@ -1068,8 +1068,6 @@
     MESSAGES
     PEOPLE
     FILES
-    
-    Searching in encrypted rooms is not supported yet.
 
     
     Browse directory
@@ -2548,8 +2546,6 @@
     Own your conversations.
     You\'re in control.
     Secure messaging.
-    
-    Cut the slack from teams.
 
     
     Secure and independent communication that gives you the same level of privacy as a face-to-face conversation in your own home.

From 98b9f1b1ca4c646da78b10ecd274cccd84ce0e6d Mon Sep 17 00:00:00 2001
From: Benoit Marty 
Date: Tue, 22 Feb 2022 15:38:43 +0100
Subject: [PATCH 568/581] Rename some string resources

---
 .../ftueauth/SplashCarouselStateFactory.kt    | 16 +++++++-------
 vector/src/main/res/values-cs/strings.xml     | 14 ++++++-------
 vector/src/main/res/values-de/strings.xml     | 14 ++++++-------
 vector/src/main/res/values-et/strings.xml     | 14 ++++++-------
 vector/src/main/res/values-fa/strings.xml     | 14 ++++++-------
 vector/src/main/res/values-fr/strings.xml     | 14 ++++++-------
 vector/src/main/res/values-hu/strings.xml     | 14 ++++++-------
 vector/src/main/res/values-in/strings.xml     | 14 ++++++-------
 vector/src/main/res/values-it/strings.xml     | 14 ++++++-------
 vector/src/main/res/values-ja/strings.xml     | 14 ++++++-------
 vector/src/main/res/values-nl/strings.xml     | 14 ++++++-------
 vector/src/main/res/values-pl/strings.xml     |  6 +++---
 vector/src/main/res/values-pt-rBR/strings.xml | 14 ++++++-------
 vector/src/main/res/values-ru/strings.xml     | 14 ++++++-------
 vector/src/main/res/values-sk/strings.xml     | 14 ++++++-------
 vector/src/main/res/values-sq/strings.xml     | 14 ++++++-------
 vector/src/main/res/values-sv/strings.xml     | 14 ++++++-------
 vector/src/main/res/values-tr/strings.xml     | 14 ++++++-------
 vector/src/main/res/values-uk/strings.xml     | 14 ++++++-------
 vector/src/main/res/values-zh-rTW/strings.xml | 14 ++++++-------
 vector/src/main/res/values/strings.xml        | 21 ++++++++-----------
 21 files changed, 146 insertions(+), 149 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/SplashCarouselStateFactory.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/SplashCarouselStateFactory.kt
index da5f8b6379d..006492f6dc2 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/SplashCarouselStateFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/SplashCarouselStateFactory.kt
@@ -43,26 +43,26 @@ class SplashCarouselStateFactory @Inject constructor(
         fun hero(@DrawableRes lightDrawable: Int, @DrawableRes darkDrawable: Int) = if (lightTheme) lightDrawable else darkDrawable
         return SplashCarouselState(listOf(
                 SplashCarouselState.Item(
-                        R.string.ftue_auth_carousel_1_title.colorTerminatingFullStop(R.attr.colorAccent),
-                        R.string.ftue_auth_carousel_body_secure,
+                        R.string.ftue_auth_carousel_secure_title.colorTerminatingFullStop(R.attr.colorAccent),
+                        R.string.ftue_auth_carousel_secure_body,
                         hero(R.drawable.ic_splash_conversations, R.drawable.ic_splash_conversations_dark),
                         background(R.drawable.bg_carousel_page_1)
                 ),
                 SplashCarouselState.Item(
-                        R.string.ftue_auth_carousel_2_title.colorTerminatingFullStop(R.attr.colorAccent),
-                        R.string.ftue_auth_carousel_body_control,
+                        R.string.ftue_auth_carousel_control_title.colorTerminatingFullStop(R.attr.colorAccent),
+                        R.string.ftue_auth_carousel_control_body,
                         hero(R.drawable.ic_splash_control, R.drawable.ic_splash_control_dark),
                         background(R.drawable.bg_carousel_page_2)
                 ),
                 SplashCarouselState.Item(
-                        R.string.ftue_auth_carousel_3_title.colorTerminatingFullStop(R.attr.colorAccent),
-                        R.string.ftue_auth_carousel_body_encrypted,
+                        R.string.ftue_auth_carousel_encrypted_title.colorTerminatingFullStop(R.attr.colorAccent),
+                        R.string.ftue_auth_carousel_encrypted_body,
                         hero(R.drawable.ic_splash_secure, R.drawable.ic_splash_secure_dark),
                         background(R.drawable.bg_carousel_page_3)
                 ),
                 SplashCarouselState.Item(
                         collaborationTitle().colorTerminatingFullStop(R.attr.colorAccent),
-                        R.string.ftue_auth_carousel_body_workplace,
+                        R.string.ftue_auth_carousel_workplace_body,
                         hero(R.drawable.ic_splash_collaboration, R.drawable.ic_splash_collaboration_dark),
                         background(R.drawable.bg_carousel_page_4)
                 )
@@ -72,7 +72,7 @@ class SplashCarouselStateFactory @Inject constructor(
     private fun collaborationTitle(): Int {
         return when {
             localeProvider.isEnglishSpeaking() -> R.string.cut_the_slack_from_teams
-            else                               -> R.string.ftue_auth_carousel_title_messaging
+            else                               -> R.string.ftue_auth_carousel_workplace_title
         }
     }
 
diff --git a/vector/src/main/res/values-cs/strings.xml b/vector/src/main/res/values-cs/strings.xml
index c7ae3aa034f..1808e05cca7 100644
--- a/vector/src/main/res/values-cs/strings.xml
+++ b/vector/src/main/res/values-cs/strings.xml
@@ -3112,9 +3112,9 @@
     Automaticky nahlašovat chyby dešifrování.
     Přepsat barvu přezdívky
     Již mám účet
-    Bezpečené zasílání zpráv.
-    Máte vše pod kontrolou.
-    Vlastněte své konverzace.
+    Bezpečené zasílání zpráv.
+    Máte vše pod kontrolou.
+    Vlastněte své konverzace.
     Sdílet polohu
     Zobrazit polohy uživatele na časové ose
     Po zapnutí budete moci odeslat svou polohu do libovolné místnosti
@@ -3142,10 +3142,10 @@
     Šifrování bylo špatně nakonfigurováno.
     Sdíleli svou polohu
     Vytvořit účet
-    Zasílání zpráv pro váš tým.
-    Koncové šifrování bez potřeby telefonního čísla. Žádné reklamy ani vytěžování dat.
-    Můžete si vybrat, kde budou vaše konverzace uloženy, a získat tak kontrolu a nezávislost. Připojeno přes Matrix.
-    Bezpečná a nezávislá komunikace, která vám poskytne stejnou úroveň soukromí jako osobní rozhovor u vás doma.
+    Zasílání zpráv pro váš tým.
+    Koncové šifrování bez potřeby telefonního čísla. Žádné reklamy ani vytěžování dat.
+    Můžete si vybrat, kde budou vaše konverzace uloženy, a získat tak kontrolu a nezávislost. Připojeno přes Matrix.
+    Bezpečná a nezávislá komunikace, která vám poskytne stejnou úroveň soukromí jako osobní rozhovor u vás doma.
     Poloha
     Šifrování bylo špatně nakonfigurováno, takže nelze odesílat zprávy. Kliknutím otevřete nastavení.
     Zobrazit bubliny zpráv
diff --git a/vector/src/main/res/values-de/strings.xml b/vector/src/main/res/values-de/strings.xml
index e617540a951..f9214383fb9 100644
--- a/vector/src/main/res/values-de/strings.xml
+++ b/vector/src/main/res/values-de/strings.xml
@@ -2994,7 +2994,7 @@
     Hilf mit, Element zu verbessern
     Aktivieren
     Farbe des Nicknamens ändern
-    Du hast die volle Kontrolle.
+    Du hast die volle Kontrolle.
     Du hast nicht die Berechtigung um diesen Raum zu betreten
     
         Keine Stimme abgegeben
@@ -3019,8 +3019,8 @@
     Schließe die Konfiguration des Auffindbarkeitsdienstes ab.
     Du verwendest derzeit keinen Identitätsserver. Um Teammitglieder einzuladen und für sie auffindbar zu sein, müssen du einen solchen Server konfigurieren.
     Ich habe schon ein Konto
-    Sichere Nachrichtenübertragung.
-    Besitze deine Konversationen.
+    Sichere Nachrichtenübertragung.
+    Besitze deine Konversationen.
     Richtlinie
     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.
     Um bestehende Kontakte zu ermitteln, müssen Sie Kontaktinformationen an deinen Identitätsserver senden.
@@ -3069,10 +3069,10 @@
     Umfrage bearbeiten
     Keine Stimmen abgegeben
     Konto erstellen
-    Nachrichtenaustausch für dein Team.
-    Ende-zu-Ende-verschlüsselt und ohne Telefonnummer nutzbar. Keine Werbung oder Datenerfassung.
-    Wähle wo deine Gespräche liegen, für Kontrolle und Unabhängigkeit. Verbunden mit Matrix.
-    Sichere und unabhängige Kommunikation, die für die gleiche Vertraulichkeit sorgt, wie ein Gespräch von Angesicht zu Angesicht in deinem eigenen Zuhause.
+    Nachrichtenaustausch für dein Team.
+    Ende-zu-Ende-verschlüsselt und ohne Telefonnummer nutzbar. Keine Werbung oder Datenerfassung.
+    Wähle wo deine Gespräche liegen, für Kontrolle und Unabhängigkeit. Verbunden mit Matrix.
+    Sichere und unabhängige Kommunikation, die für die gleiche Vertraulichkeit sorgt, wie ein Gespräch von Angesicht zu Angesicht in deinem eigenen Zuhause.
     Standort
     Fehlerhaft konfiguriertes Vertrauenslevel
     Die Verschlüsselung ist fehlerhaft konfiguriert
diff --git a/vector/src/main/res/values-et/strings.xml b/vector/src/main/res/values-et/strings.xml
index 75e9eaabd71..e6ad731065b 100644
--- a/vector/src/main/res/values-et/strings.xml
+++ b/vector/src/main/res/values-et/strings.xml
@@ -3053,9 +3053,9 @@
     Automaatselt teata dekrüptimise vigadest.
     Asenda hüüdnime värvid
     Mul on kasutajakonto juba olemas
-    Turvaline sõnumivahetus.
-    Sul on kontroll oma andmete üle.
-    Vestlused, mida sa tegelikult ka omad.
+    Turvaline sõnumivahetus.
+    Sul on kontroll oma andmete üle.
+    Vestlused, mida sa tegelikult ka omad.
     Jaga asukohta
     Kuva ajajoonel kasutaja asukohti
     Kui see seadistus on kasutusel, siis sa saad oma asukohta jagada igas jututoas
@@ -3082,10 +3082,10 @@
     Krüptimise seadistustes on viga.
     Jagas oma asukohta
     Loo kasutajakonto
-    Sõnumisuhtlus sinu tiimi või kogukonna jaoks.
-    Tagatud on andmete läbiv krüptimine ning oma telefoninumbrit ei pea sa jagama. Pole reklaame ega sinu andmete kogumist.
-    Sa ise valid serveri, kus sinu vestlusi hoitakse ning sellega tagadki kontrolli oma andmete üle. Lahendus põhineb Matrix\'i võrgul.
-    Turvaline ja sõltumatu suhtluslahendus, mis tagab sama privaatsuse, kui omavaheline vestlus sinu kodus.
+    Sõnumisuhtlus sinu tiimi või kogukonna jaoks.
+    Tagatud on andmete läbiv krüptimine ning oma telefoninumbrit ei pea sa jagama. Pole reklaame ega sinu andmete kogumist.
+    Sa ise valid serveri, kus sinu vestlusi hoitakse ning sellega tagadki kontrolli oma andmete üle. Lahendus põhineb Matrix\'i võrgul.
+    Turvaline ja sõltumatu suhtluslahendus, mis tagab sama privaatsuse, kui omavaheline vestlus sinu kodus.
     Asukoht
     Krüptimise seadistustes on viga ja sa ei saa sõnumeid saata. Seadistuste avamiseks klõpsi siin.
     Krüptimise seadistustes on viga ja sa ei saa sõnumeid saata. Kui soovid krüptimist töökorda saada, siis võta ühendust serveri haldajaga.
diff --git a/vector/src/main/res/values-fa/strings.xml b/vector/src/main/res/values-fa/strings.xml
index 1d514025b0b..340a39e5856 100644
--- a/vector/src/main/res/values-fa/strings.xml
+++ b/vector/src/main/res/values-fa/strings.xml
@@ -3052,9 +3052,9 @@
     سامانه‌تان هنگام‌ مواجهه با خطای ناتوانی در رمزگشایی، گزارش‌ها را به صورت خودکار خواهد فرستاد
     گزارش خودکار خطاهای رمزگشایی.
     از پیش حساب دارم
-    پیام‌رسانی امن.
-    شما کنترل می‌کنید.
-    صاحب گفت‌وگوهایتان باشید.
+    پیام‌رسانی امن.
+    شما کنترل می‌کنید.
+    صاحب گفت‌وگوهایتان باشید.
     هم‌رسانی مکان
     به کار انداختن هم‌رسانی مکان
     گشودن با
@@ -3071,7 +3071,7 @@
     بازیابی رمزنگاری
     مکانش را هم‌رسانی کرد
     ایجاد حساب
-    پیام‌رسانی برای گروهتان.
+    پیام‌رسانی برای گروهتان.
     مکان
     پرداخت مکان‌های کاربری در خط زمانی
     پس از به کار افتادن، قادر خواهید بد مکانتان را به هر اتاقی بفرستید
@@ -3084,9 +3084,9 @@
     پایمالی رنگ نام مستعار
     لطفاً برای بازگردانی رمزنگاری به یه وضعیتی معتبر، با مدیری تماس بگیرید.
     رمزنگاری بد پیکربندی شده.
-    رمزنگاشتهٔ سرتاسری و بدون نیاز به شماره تلفن. بدون تبلیغات یا داده‌کاوی.
-    مکان نگه‌داری گفت‌وگوهایتان را برگزینید که به شما کنترل و استقلال می‌هد. وصل شده با ماتریکس.
-    ارتباطات امن و مستقل که به شما همان سطح محرمانگی گفت‌وگوی رودررو در خانهٔ خودتان را می‌دهد.
+    رمزنگاشتهٔ سرتاسری و بدون نیاز به شماره تلفن. بدون تبلیغات یا داده‌کاوی.
+    مکان نگه‌داری گفت‌وگوهایتان را برگزینید که به شما کنترل و استقلال می‌هد. وصل شده با ماتریکس.
+    ارتباطات امن و مستقل که به شما همان سطح محرمانگی گفت‌وگوی رودررو در خانهٔ خودتان را می‌دهد.
     از آن‌جا که رمزنگاری بد پیکربندی شده، نمی‌توانید پیام بفرستید. برای گشودن تنظیمات، بزنید.
     از آن‌جا که رمزنگاری بد پیکربندی شده، نمی‌توانید پیام بفرستید. لطفاً برای بازگردانی رمزنگاری به یه وضعیت معتبر، با مدیری تماس بگیرید.
     نمایش حباب‌های پیام
diff --git a/vector/src/main/res/values-fr/strings.xml b/vector/src/main/res/values-fr/strings.xml
index d51d644f0db..26f3c4f36b6 100644
--- a/vector/src/main/res/values-fr/strings.xml
+++ b/vector/src/main/res/values-fr/strings.xml
@@ -3053,11 +3053,11 @@
     Signalement automatique des erreurs de déchiffrement.
     Outrepasser la couleur du pseudo
     J’ai déjà un compte
-    Messagerie sécurisée.
-    Vous êtes aux commandes.
-    Contrôlez vos conversations.
+    Messagerie sécurisée.
+    Vous êtes aux commandes.
+    Contrôlez vos conversations.
     Vous n’êtes pas autorisé à rejoindre ce salon
-    Choisissez où vos conversations sont stockées, vous avez le contrôle et êtes indépendant. Connecté via Matrix.
+    Choisissez où vos conversations sont stockées, vous avez le contrôle et êtes indépendant. Connecté via Matrix.
     Le chiffrement a été mal configuré ce qui vous empêche d’envoyer des messages. Veuillez contacter un administrateur pour remettre le chiffrement en état de marche.
     Partager la localisation
     Afficher les localisations de l\'utilisateur dans le temps
@@ -3085,9 +3085,9 @@
     Le chiffrement a été mal configuré.
     On partagé leur localisation
     Créer un compte
-    Messagerie pour votre équipe.
-    Chiffré de bout en bout et aucun numéro de téléphone n\'est nécessaire. Pas de pubs ni de collecte de données.
-    Communication indépendante et sécurisée qui vous donne le même niveau d\'intimité qu\'une discussion face-à-face dans votre maison.
+    Messagerie pour votre équipe.
+    Chiffré de bout en bout et aucun numéro de téléphone n\'est nécessaire. Pas de pubs ni de collecte de données.
+    Communication indépendante et sécurisée qui vous donne le même niveau d\'intimité qu\'une discussion face-à-face dans votre maison.
     Localisation
     Le chiffrement a été mal configuré ce qui vous empêche d\'envoyer des messages. Cliquez pour ouvrir les paramètres.
 
\ No newline at end of file
diff --git a/vector/src/main/res/values-hu/strings.xml b/vector/src/main/res/values-hu/strings.xml
index aad01c11f9f..77e18e05670 100644
--- a/vector/src/main/res/values-hu/strings.xml
+++ b/vector/src/main/res/values-hu/strings.xml
@@ -3040,7 +3040,7 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze
     A változások életbelépéséhez indítsd újra az alkalmazást.
     LaTeX matematikai szintaxis engedélyezése
     Nem léphetsz be ebbe a szobába
-    Az ön beszélgetései csak az öné.
+    Az ön beszélgetései csak az öné.
     Titkosítás visszafejtési hiba esemény alkalmával a rendszer automatikusan elküldi a logokat
     Titkosítás visszafejtési hibák automatikus jelentése.
     Szavazás létrehozása
@@ -3051,8 +3051,8 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze
     Kamera megnyitása
     Becenév színének megváltoztatása
     Már van fiókom
-    Biztonságos üzenetküldés.
-    Te irányítasz.
+    Biztonságos üzenetküldés.
+    Te irányítasz.
     Tartózkodási hely megosztása
     A felhasználó földrajzi helyzetének megjelenítése az idővonalon
     A beállítás után bármelyik szobában megoszthatod a földrajzi helyzetedet
@@ -3079,10 +3079,10 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze
     A titkosítás beállítása hibás.
     A földrajzi helyzetüket megosztották
     Fiók létrehozása
-    Üzenetküldés a csoportodnak.
-    Telefonszám nélkül végpontok között titkosított. Reklámok és adatbányászat nélkül.
-    Válaszd meg hol legyenek a beszélgetéseid tárolva, visszaadja az irányítást és függetlenné tesz. Csatlakozva a Matrixhoz.
-    Biztonságos és független kommunikáció ami olyan biztonságos mintha valakivel négyszemközt beszélgetnél a házadban.
+    Üzenetküldés a csoportodnak.
+    Telefonszám nélkül végpontok között titkosított. Reklámok és adatbányászat nélkül.
+    Válaszd meg hol legyenek a beszélgetéseid tárolva, visszaadja az irányítást és függetlenné tesz. Csatlakozva a Matrixhoz.
+    Biztonságos és független kommunikáció ami olyan biztonságos mintha valakivel négyszemközt beszélgetnél a házadban.
     Földrajzi helyzet
     A titkosítás beállítása hibás így nem lehet üzenetet küldeni. Kattints a beállításokért.
     A titkosítás beállítása hibás így nem lehet üzenetet küldeni. Kérjük vedd fel a kapcsolatot az adminisztrátorral a titkosítás helyreállításához.
diff --git a/vector/src/main/res/values-in/strings.xml b/vector/src/main/res/values-in/strings.xml
index 5b86d5aa679..21af56e5809 100644
--- a/vector/src/main/res/values-in/strings.xml
+++ b/vector/src/main/res/values-in/strings.xml
@@ -2998,9 +2998,9 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
     Laporkan Kesalahan Dekripsi Secara Otomatis.
     Ubah warna nama tampilan
     Saya sudah punya akun
-    Perpesanan yang aman.
-    Anda dalam kendali.
-    Miliki percakapan Anda.
+    Perpesanan yang aman.
+    Anda dalam kendali.
+    Miliki percakapan Anda.
     Bagikan lokasi
     Tampilkan lokasi pengguna di linimasa
     Setelah diaktifkan Anda akan dapat mengirim lokasi Anda ke ruangan apa saja
@@ -3027,10 +3027,10 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
     Enkripsi telah dikonfigurasi dengan salah.
     Membagikan lokasinya
     Buat akun
-    Perpesanan untuk tim Anda.
-    Terenkripsi secara ujung-ke-ujung dan tidak memerlukan nomor telepon. Tidak ada iklan atau penambangan data.
-    Anda pilih di mana percakapan Anda disimpan, memberikan Anda kendali dan kebebasan. Terhubung via Matrix.
-    Komunikasi aman dan independen yang memberikan tingkat privasi yang sama seperti percakapan wajah-ke-wajah di dalam rumah Anda sendiri.
+    Perpesanan untuk tim Anda.
+    Terenkripsi secara ujung-ke-ujung dan tidak memerlukan nomor telepon. Tidak ada iklan atau penambangan data.
+    Anda pilih di mana percakapan Anda disimpan, memberikan Anda kendali dan kebebasan. Terhubung via Matrix.
+    Komunikasi aman dan independen yang memberikan tingkat privasi yang sama seperti percakapan wajah-ke-wajah di dalam rumah Anda sendiri.
     Lokasi
     Enkripsi telah dikonfigurasi dengan salah sehingga Anda tidak dapat mengirim pesan. Klik untuk membuka pengaturan.
     Enkripsi telah dikonfigurasi dengan salah sehingga Anda tidak dapat mengirim pesan. Mohon hubungi sebuah admin untuk memulihkan enkripsi ke status yang valid.
diff --git a/vector/src/main/res/values-it/strings.xml b/vector/src/main/res/values-it/strings.xml
index a07a02a66a6..2eff4ea6cf6 100644
--- a/vector/src/main/res/values-it/strings.xml
+++ b/vector/src/main/res/values-it/strings.xml
@@ -3043,9 +3043,9 @@
     Auto-segnala errori di decifrazione.
     Sovrascrivi colore nick
     Ho già un account
-    Messaggistica sicura.
-    Tu hai il controllo.
-    Prendi il controllo delle tue conversazioni.
+    Messaggistica sicura.
+    Tu hai il controllo.
+    Prendi il controllo delle tue conversazioni.
     Condividi posizione
     Mostra le posizioni dell\'utente nella linea temporale
     Una volta attivata, potrai inviare la tua posizione in qualsiasi stanza
@@ -3072,10 +3072,10 @@
     La crittografia è stata configurata male.
     Ha condiviso la sua posizione
     Crea account
-    Messaggistica per la tua squadra.
-    Crittografia end-to-end e nessun numero di telefono richiesto. Niente pubblicità o raccolta di dati.
-    Scegli dove vengono tenute le tue conversazioni, dandoti controllo e indipendenza. Connesso via Matrix.
-    Comunicazioni sicure e indipendenti che ti danno lo stesso livello di riservatezza di una conversazione faccia a faccia in casa tua.
+    Messaggistica per la tua squadra.
+    Crittografia end-to-end e nessun numero di telefono richiesto. Niente pubblicità o raccolta di dati.
+    Scegli dove vengono tenute le tue conversazioni, dandoti controllo e indipendenza. Connesso via Matrix.
+    Comunicazioni sicure e indipendenti che ti danno lo stesso livello di riservatezza di una conversazione faccia a faccia in casa tua.
     Posizione
     La crittografia è stata configurata male, perciò non puoi inviare messaggi. Clicca per aprire le impostazioni.
     La crittografia è stata configurata male, perciò non puoi inviare messaggi. Contatta l\'amministratore per reimpostare la crittografia ad uno stato valido.
diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 9dbad1bc224..d5304474788 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2484,7 +2484,7 @@
     未確認
     制限は不明です。
     サーバーのファイルアップロードの制限
-    自分の会話は、自分のもの。
+    自分の会話は、自分のもの。
     %1$sがこのルームを「招待者のみ参加可能」に設定しました。
     選択したメッセージをネタバレとして送信
     ${app_name} がエンドツーエンド暗号鍵をディスクに保存する許可を要求しています。
@@ -2502,12 +2502,12 @@
     メッセージを降雪と共に送る
     紙吹雪🎉を送る
     降雪❄️を送る
-    あなたのチームのメッセージングに。
-    エンドツーエンドで暗号化され、電話番号不要。広告やデータマイニング無し。
-    会話の保存先を自分で決められ、自分で管理できる独立したコミュニケーション。Matrixをもとに。
-    お宅での対面会話と同じぐらいのプライバシーを提供する、セキュアで独立したコミュニケーション。
-    セキュアメッセージング
-    管理権を握るのは、あなたです。
+    あなたのチームのメッセージングに。
+    エンドツーエンドで暗号化され、電話番号不要。広告やデータマイニング無し。
+    会話の保存先を自分で決められ、自分で管理できる独立したコミュニケーション。Matrixをもとに。
+    お宅での対面会話と同じぐらいのプライバシーを提供する、セキュアで独立したコミュニケーション。
+    セキュアメッセージング
+    管理権を握るのは、あなたです。
     Elementの使用に関するヘルプ
     詳細なログは、イライラシェイクでログを送信する際に、より多くのログを提供することで、開発者にとっての助けになります。有効にした場合でも、メッセージの内容やその他のプライベートな情報は記録されません。
     ルームのアップグレードは高度な作業であり、不具合や欠けている機能、セキュリティー上の脆弱性がある場合に推奨されます。
diff --git a/vector/src/main/res/values-nl/strings.xml b/vector/src/main/res/values-nl/strings.xml
index 84bd540e9bd..f013aa8a7e0 100644
--- a/vector/src/main/res/values-nl/strings.xml
+++ b/vector/src/main/res/values-nl/strings.xml
@@ -2868,7 +2868,7 @@
     Je wachtwoord is gereset.
     Ik heb mijn e-mailadres geverifieerd
     Tik op de link om uw nieuwe wachtwoord te bevestigen. Klik hieronder als u de link hebt gevolgd die erin staat.
-    Word eigenaar van uw gesprekken.
+    Word eigenaar van uw gesprekken.
     Space aanmaken
     Space aanmaken…
     Aanmaken een space
@@ -3054,8 +3054,8 @@
     Maak een Space
     Bijnaam kleur overschrijven
     Ik heb al een account
-    Veilig berichtenverkeer.
-    U heeft de controle.
+    Veilig berichtenverkeer.
+    U heeft de controle.
     Deel locatie
     Gebruikerslocaties in de tijdlijn weergeven
     Eenmaal ingeschakeld, kun je je locatie naar elke kamer sturen
@@ -3082,10 +3082,10 @@
     Versleuteling is verkeerd geconfigureerd.
     Hun locatie gedeeld
     Account aanmaken
-    Berichten voor uw team.
-    End-to-end versleuteld en geen telefoonnummer vereist. Geen advertenties of dataverzameling.
-    Kies waar je gesprekken worden bewaard, zodat u controle en onafhankelijkheid heeft. Verbonden via Matrix.
-    Veilige en onafhankelijke communicatie die u dezelfde mate van privacy geeft als een persoonlijk gesprek in uw eigen huis.
+    Berichten voor uw team.
+    End-to-end versleuteld en geen telefoonnummer vereist. Geen advertenties of dataverzameling.
+    Kies waar je gesprekken worden bewaard, zodat u controle en onafhankelijkheid heeft. Verbonden via Matrix.
+    Veilige en onafhankelijke communicatie die u dezelfde mate van privacy geeft als een persoonlijk gesprek in uw eigen huis.
     Locatie
     De versleuteling is verkeerd geconfigureerd, zodat u geen berichten kunt verzenden. Klik om instellingen te openen.
     De versleuteling is verkeerd geconfigureerd, zodat u geen berichten kunt verzenden. Neem contact op met een beheerder om de versleuteling in een geldige staat te herstellen.
diff --git a/vector/src/main/res/values-pl/strings.xml b/vector/src/main/res/values-pl/strings.xml
index fc852245ebe..c9478d09978 100644
--- a/vector/src/main/res/values-pl/strings.xml
+++ b/vector/src/main/res/values-pl/strings.xml
@@ -3057,9 +3057,9 @@
     
     Nadpisz kolor nicku
     Posiadam już konto
-    Połącz się z każdym.
-    Ty jesteś w kontroli.
-    Przejmij swoje konwersacje.
+    Połącz się z każdym.
+    Ty jesteś w kontroli.
+    Przejmij swoje konwersacje.
     By odkryć istniejące kontakty, musisz najpierw przesłać swoje dane kontaktowe (adresy e-mail i numer telefonu) do serwera tożsamości. Przed wysłaniem Twoje dane zostaną zaszyfrowane w celu zachowania prywatności.
     Uzyskaj pomoc w korzystaniu z Element
     Nie masz uprawnień by dołączyć do tego pokoju
diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/vector/src/main/res/values-pt-rBR/strings.xml
index cff40afaa85..c6cda609723 100644
--- a/vector/src/main/res/values-pt-rBR/strings.xml
+++ b/vector/src/main/res/values-pt-rBR/strings.xml
@@ -3053,9 +3053,9 @@
     Auro Reportar Erros de Decriptação.
     Sobrepor cor de nick
     Eu já tenho uma conta
-    Mensageria segura.
-    Você está em controle.
-    Tenha posse de suas conversas.
+    Mensageria segura.
+    Você está em controle.
+    Tenha posse de suas conversas.
     Compartilhar localização
     Render localizações de usuária(o) na timeline
     Uma vez habilitada você vai ser capaz de enviar sua localização a qualquer sala
@@ -3083,10 +3083,10 @@
     Localização
     Encriptação tem sido malconfigurada.
     Criar conta
-    Mensageria para seu time.
-    Encriptado ponta-a-ponta e nenhum número de telefone requerido. Sem publicidade ou datamining.
-    Escolha onde suas conversas são mantidas, dando-lhe controle e independência. Conectado via Matrix.
-    Comunicação segura e independente que lhe dá o mesmo nível de privacidade que conversa face-a-face em sua própria casa.
+    Mensageria para seu time.
+    Encriptado ponta-a-ponta e nenhum número de telefone requerido. Sem publicidade ou datamining.
+    Escolha onde suas conversas são mantidas, dando-lhe controle e independência. Conectado via Matrix.
+    Comunicação segura e independente que lhe dá o mesmo nível de privacidade que conversa face-a-face em sua própria casa.
     Encriptação tem sido malconfigurada então você não pode enviar mensagens. Clique para abrir configurações.
     Encriptação tem sido malconfigurada então você não pode enviar mensagens. Por favor contacte um(a) admin para restaurar encriptação a um estado válido.
     Mostrar Bolhas de mensagem
diff --git a/vector/src/main/res/values-ru/strings.xml b/vector/src/main/res/values-ru/strings.xml
index d5e878f9542..9ecb710da46 100644
--- a/vector/src/main/res/values-ru/strings.xml
+++ b/vector/src/main/res/values-ru/strings.xml
@@ -3143,13 +3143,13 @@
     Поделились своим местоположением
     У меня уже есть учетная запись
     Создать учетную запись
-    Обмен сообщениями для вашей команды.
-    Сквозное шифрование не требующее номера телефона. Нет рекламы или сбора данных.
-    Выбор где хранятся ваши разговоры дает вам власть и независимость. Подключено с помощью Matrix.
-    Безопасное и независимое общение, обеспечивающее вам такой же уровень конфиденциальности, как при личном общении в вашем собственном доме.
-    Безопасный обмен сообщениями.
-    Ваше управление.
-    Ваши собственные разговоры.
+    Обмен сообщениями для вашей команды.
+    Сквозное шифрование не требующее номера телефона. Нет рекламы или сбора данных.
+    Выбор где хранятся ваши разговоры дает вам власть и независимость. Подключено с помощью Matrix.
+    Безопасное и независимое общение, обеспечивающее вам такой же уровень конфиденциальности, как при личном общении в вашем собственном доме.
+    Безопасный обмен сообщениями.
+    Ваше управление.
+    Ваши собственные разговоры.
     Местоположение
     Вы согласны отправить эту информацию\?
     Чтобы обнаружить существующие контакты, необходимо отправить контактную информацию (электронную почту и номера телефонов) на сервер обнаружения. Мы хешируем ваши данные перед отправкой для обеспечения конфиденциальности.
diff --git a/vector/src/main/res/values-sk/strings.xml b/vector/src/main/res/values-sk/strings.xml
index 817cbd3fb1f..daed54c19d8 100644
--- a/vector/src/main/res/values-sk/strings.xml
+++ b/vector/src/main/res/values-sk/strings.xml
@@ -2083,7 +2083,7 @@
     Skontrolujte prosím svoj e-mail
     Anketa skončila
     Pozvať podľa používateľského mena
-    Vlastnite svoje konverzácie.
+    Vlastnite svoje konverzácie.
     Už mám účet
     Nahrať súbor
     Videohovor s %s
@@ -2189,8 +2189,8 @@
     Pozvať pomocou používateľského mena alebo e-mailu
     Povoliť matematiku LaTeX
     Reštartujte aplikáciu, aby sa zmena prejavila.
-    Vy máte všetko pod kontrolou.
-    Bezpečné zasielanie správ.
+    Vy máte všetko pod kontrolou.
+    Bezpečné zasielanie správ.
     Automatické hlásenie chýb dešifrovania.
     Váš systém bude automaticky odosielať záznamy, keď sa vyskytne chyba dešifrovania
     Otvoriť kameru
@@ -2198,10 +2198,10 @@
     Odoslať nálepku
     Otvoriť kontakty
     Vytvoriť anketu
-    Bezpečná a nezávislá komunikácia, ktorá vám poskytuje rovnakú úroveň súkromia ako rozhovor zoči-voči vo vašom vlastnom dome.
-    Vyberte si, kde budú vaše rozhovory uložené, a získajte tak kontrolu a nezávislosť. Pripojené cez Matrix.
-    End-to-end šifrovanie a bez potreby telefónneho čísla. Žiadne reklamy ani zneužívanie údajov.
-    Zasielanie správ pre váš tím.
+    Bezpečná a nezávislá komunikácia, ktorá vám poskytuje rovnakú úroveň súkromia ako rozhovor zoči-voči vo vašom vlastnom dome.
+    Vyberte si, kde budú vaše rozhovory uložené, a získajte tak kontrolu a nezávislosť. Pripojené cez Matrix.
+    End-to-end šifrovanie a bez potreby telefónneho čísla. Žiadne reklamy ani zneužívanie údajov.
+    Zasielanie správ pre váš tím.
     Vytvoriť účet
     Šifrovanie je nesprávne nastavené.
     Obráťte sa na správcu, aby obnovil platný stav šifrovania.
diff --git a/vector/src/main/res/values-sq/strings.xml b/vector/src/main/res/values-sq/strings.xml
index 6200c8f900e..03d8a94cdd5 100644
--- a/vector/src/main/res/values-sq/strings.xml
+++ b/vector/src/main/res/values-sq/strings.xml
@@ -3041,9 +3041,9 @@
     Raporto Vetvetiu Gabime Shfshehtëzimi.
     Anashkalo ngjyrë nofke
     Kam tashmë një llogari
-    Shkëmbim i siguruar mesazhesh.
-    Kontrollin e keni ju.
-    Jini zot i bisedave tuaja.
+    Shkëmbim i siguruar mesazhesh.
+    Kontrollin e keni ju.
+    Jini zot i bisedave tuaja.
     Jepe vendndodhjen
     Aktivizoni dhënie vendndodhjeje
     Jepe vendndodhjen
@@ -3056,10 +3056,10 @@
     Anketim i hapur
     Lloj anketimi
     Krijoni llogari
-    Shkëmbim mesazhesh për ekipin tuaj.
-    I fshehtëzuar skaj më skaj dhe pa u dashur numër telefoni. Pa reklama, apo shfrytëzim të dhënash.
-    Zgjidhni ku mbahen bisedat tuaja, duke ju dhënë kontroll dhe pavarësi. Të lidhur përmes Matrix-i.
-    Komunikim i sigurt dhe i pavarur, që ju jep të njëjtën shkallë privatësie si biseda kokë më kokë, në shtëpinë tuaj.
+    Shkëmbim mesazhesh për ekipin tuaj.
+    I fshehtëzuar skaj më skaj dhe pa u dashur numër telefoni. Pa reklama, apo shfrytëzim të dhënash.
+    Zgjidhni ku mbahen bisedat tuaja, duke ju dhënë kontroll dhe pavarësi. Të lidhur përmes Matrix-i.
+    Komunikim i sigurt dhe i pavarur, që ju jep të njëjtën shkallë privatësie si biseda kokë më kokë, në shtëpinë tuaj.
     Trego vendndodhje përdoruesi në rrjedhën kohore
     Pasi të aktivizohet, do të jeni në gjendje të dërgoni vendndodhjen tuaj në çfarëdo dhome
     Hape me
diff --git a/vector/src/main/res/values-sv/strings.xml b/vector/src/main/res/values-sv/strings.xml
index 4aa1d41f053..5d6e3572c86 100644
--- a/vector/src/main/res/values-sv/strings.xml
+++ b/vector/src/main/res/values-sv/strings.xml
@@ -3052,9 +3052,9 @@
     Rapportera avkrypteringsfel automatiskt.
     Åsidosätt visningsnamnsfärg
     Jag har redan ett konto
-    Säkra meddelanden.
-    Du har kontroll.
-    Äg dina konversationer.
+    Säkra meddelanden.
+    Du har kontroll.
+    Äg dina konversationer.
     Du får inte gå med i det här rummet
     Dela plats
     Visa användarplatser i tidslinjen
@@ -3082,10 +3082,10 @@
     Kryptering har felkonfigurerats.
     Delade sin plats
     Skapa konto
-    Meddelanden för ditt team.
-    Totalsträckskrypterad och inget telefonnummer krävs. Ingen reklam eller datainsamling.
-    Välj var dina konversationer lagras, vilket ger dig kontroll och självständighet. Ansluts via Matrix.
-    Säker och oberoende kommunikation som ger dig samma sekretessnivå som en personlig konversation i ditt eget hem.
+    Meddelanden för ditt team.
+    Totalsträckskrypterad och inget telefonnummer krävs. Ingen reklam eller datainsamling.
+    Välj var dina konversationer lagras, vilket ger dig kontroll och självständighet. Ansluts via Matrix.
+    Säker och oberoende kommunikation som ger dig samma sekretessnivå som en personlig konversation i ditt eget hem.
     Plats
     Kryptering har felkonfigurerats så du kan inte skicka meddelanden. Klicka för att öppna inställningar.
     Kryptering har felkonfigurerats så du kan inte skicka meddelanden. Vänligen kontakta en admin för att återställa kryptering till ett giltigt tillstånd.
diff --git a/vector/src/main/res/values-tr/strings.xml b/vector/src/main/res/values-tr/strings.xml
index e75b3857b91..b2ca20d8714 100644
--- a/vector/src/main/res/values-tr/strings.xml
+++ b/vector/src/main/res/values-tr/strings.xml
@@ -2218,13 +2218,13 @@
     %s ile devam et
     zaten bir hesabım var
     Hesap oluştur
-    Ekibiniz için mesajlaşma.
-    Uçtan uca şifrelenir ve telefon numarası gerekmez. Reklam veya veri madenciliği yok.
-    Size kontrol ve bağımsızlık vererek konuşmalarınızın nerede tutulacağını seçin. Matrix ile bağlandı.
-    Size kendi evinizde yüz yüze görüşmeyle aynı düzeyde mahremiyet sağlayan güvenli ve bağımsız iletişim.
-    Güvenli mesajlaşma.
-    Kontrol sende.
-    Konuşmalarınıza sahip çıkın.
+    Ekibiniz için mesajlaşma.
+    Uçtan uca şifrelenir ve telefon numarası gerekmez. Reklam veya veri madenciliği yok.
+    Size kontrol ve bağımsızlık vererek konuşmalarınızın nerede tutulacağını seçin. Matrix ile bağlandı.
+    Size kendi evinizde yüz yüze görüşmeyle aynı düzeyde mahremiyet sağlayan güvenli ve bağımsız iletişim.
+    Güvenli mesajlaşma.
+    Kontrol sende.
+    Konuşmalarınıza sahip çıkın.
     Bu daveti yalnızca siz yaptınız.
     %1$s bu daveti yalnızca yaptı.
     %1$s odayı yalnızca davet etti.
diff --git a/vector/src/main/res/values-uk/strings.xml b/vector/src/main/res/values-uk/strings.xml
index 1bbbb0bc73d..dd07ae649e2 100644
--- a/vector/src/main/res/values-uk/strings.xml
+++ b/vector/src/main/res/values-uk/strings.xml
@@ -3155,9 +3155,9 @@
     Автозвіт про помилки шифрування.
     Замінити колір псевдоніма
     Я вже маю обліковий запис
-    Захищене спілкування.
-    Ви контролюєте все.
-    Володійте своїми розмовами.
+    Захищене спілкування.
+    Ви контролюєте все.
+    Володійте своїми розмовами.
     Поділитися місцеперебуванням
     Зображувати місцеперебування користувача у стрічці
     Після увімкнення ви зможете надіслати своє місцеперебування до будь-якої кімнати
@@ -3184,10 +3184,10 @@
     Шифрування неправильно налаштовано.
     Поділилися своїм місцеперебуванням
     Створити обліковий запис
-    Спілкування вашої команди.
-    З наскрізним шифруванням і без вимоги номера телефону. Без реклами чи аналізу даних.
-    Оберіть де спілкуватись, що дасть вам контроль і незалежність. Під\'єднано через Matrix.
-    Захищене та незалежне спілкування, яке дає вам такий самий рівень приватності, як розмова віч-на-віч у вашому власному домі.
+    Спілкування вашої команди.
+    З наскрізним шифруванням і без вимоги номера телефону. Без реклами чи аналізу даних.
+    Оберіть де спілкуватись, що дасть вам контроль і незалежність. Під\'єднано через Matrix.
+    Захищене та незалежне спілкування, яке дає вам такий самий рівень приватності, як розмова віч-на-віч у вашому власному домі.
     Місцеперебування
     Шифрування було налаштовано неправильно, тому ви не можете надсилати повідомлення. Торкніться, щоб відкрити налаштування.
     Шифрування було налаштовано неправильно, тому ви не можете надсилати повідомлення. Зверніться до адміністратора, щоб відновити роботу шифрування.
diff --git a/vector/src/main/res/values-zh-rTW/strings.xml b/vector/src/main/res/values-zh-rTW/strings.xml
index b4a8808e491..6fe33a224dd 100644
--- a/vector/src/main/res/values-zh-rTW/strings.xml
+++ b/vector/src/main/res/values-zh-rTW/strings.xml
@@ -2994,9 +2994,9 @@
     自動回報解密錯誤。
     覆寫暱稱色彩
     我已有一個帳號
-    安全傳送訊息。
-    您已掌控了您的資料。
-    擁有您的對話。
+    安全傳送訊息。
+    您已掌控了您的資料。
+    擁有您的對話。
     分享位置
     在時間軸中繪製使用者位置
     啟用後,您就能將您的位置傳送至任何聊天室
@@ -3023,10 +3023,10 @@
     加密設定錯誤。
     分享了他們的位置
     建立帳號
-    為您的團隊傳送訊息。
-    端到端加密,不需要電話號碼。沒有廣告或資料挖礦。
-    選擇保留對話的位置,讓您擁有控制權與獨立性。透過 Matrix 連結。
-    安全且獨立的通訊,為您提供與在家中進行面對面對話相同的隱私等級。
+    為您的團隊傳送訊息。
+    端到端加密,不需要電話號碼。沒有廣告或資料挖礦。
+    選擇保留對話的位置,讓您擁有控制權與獨立性。透過 Matrix 連結。
+    安全且獨立的通訊,為您提供與在家中進行面對面對話相同的隱私等級。
     位置
     加密設定錯誤,因此您無法傳送訊息。點擊以開啟設定。
     加密設定錯誤,因此您無法傳送訊息。請聯絡管理員將加密還原至有效的狀態。
diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml
index 2da817767e0..ed54500a9d6 100644
--- a/vector/src/main/res/values/strings.xml
+++ b/vector/src/main/res/values/strings.xml
@@ -2542,19 +2542,16 @@
     Unread messages
 
     
-    
-    Own your conversations.
-    You\'re in control.
-    Secure messaging.
-
-    
-    Secure and independent communication that gives you the same level of privacy as a face-to-face conversation in your own home.
-    Choose where your conversations are kept, giving you control and independence. Connected via Matrix.
-    End-to-end encrypted and no phone number required. No ads or datamining.
-    
-    Messaging for your team.
+    Own your conversations.
+    You\'re in control.
+    Secure messaging.
+    Messaging for your team.
+
+    Secure and independent communication that gives you the same level of privacy as a face-to-face conversation in your own home.
+    Choose where your conversations are kept, giving you control and independence. Connected via Matrix.
+    End-to-end encrypted and no phone number required. No ads or datamining.
     
-    ${app_name} is also great for the workplace. It’s trusted by the world’s most secure organisations.
+    ${app_name} is also great for the workplace. It’s trusted by the world’s most secure organisations.
 
     Who will you chat to the most?
     We\'ll help you get connected.

From 407394b7fac267df76297f22d23ee31569c03bba Mon Sep 17 00:00:00 2001
From: Benoit Marty 
Date: Tue, 22 Feb 2022 15:42:29 +0100
Subject: [PATCH 569/581] Create template with the app name

---
 vector/src/main/res/values-cs/strings.xml     | 2 +-
 vector/src/main/res/values-de/strings.xml     | 2 +-
 vector/src/main/res/values-es/strings.xml     | 2 +-
 vector/src/main/res/values-et/strings.xml     | 2 +-
 vector/src/main/res/values-fa/strings.xml     | 2 +-
 vector/src/main/res/values-fr/strings.xml     | 2 +-
 vector/src/main/res/values-hu/strings.xml     | 2 +-
 vector/src/main/res/values-in/strings.xml     | 2 +-
 vector/src/main/res/values-it/strings.xml     | 2 +-
 vector/src/main/res/values-nl/strings.xml     | 2 +-
 vector/src/main/res/values-pl/strings.xml     | 2 +-
 vector/src/main/res/values-pt-rBR/strings.xml | 2 +-
 vector/src/main/res/values-ru/strings.xml     | 2 +-
 vector/src/main/res/values-sk/strings.xml     | 2 +-
 vector/src/main/res/values-sq/strings.xml     | 2 +-
 vector/src/main/res/values-sv/strings.xml     | 2 +-
 vector/src/main/res/values-uk/strings.xml     | 2 +-
 vector/src/main/res/values-vi/strings.xml     | 2 +-
 vector/src/main/res/values-zh-rCN/strings.xml | 2 +-
 vector/src/main/res/values-zh-rTW/strings.xml | 2 +-
 vector/src/main/res/values/strings.xml        | 3 +--
 21 files changed, 21 insertions(+), 22 deletions(-)

diff --git a/vector/src/main/res/values-cs/strings.xml b/vector/src/main/res/values-cs/strings.xml
index 1808e05cca7..ddb342d60de 100644
--- a/vector/src/main/res/values-cs/strings.xml
+++ b/vector/src/main/res/values-cs/strings.xml
@@ -2912,7 +2912,7 @@
     Další
     Zmínky a klíčová slova
     Výchozí oznámení
-    %s v Nastavení pro příjem pozvánek přímo v Elementu.
+    %s v Nastavení pro příjem pozvánek přímo v ${app_name}u.
     Propojte tento e-mail se svým účtem
     Pozvánka do této místnosti byla odeslána na adresu %s, která není spojena s vaším účtem
     Pozvánka do tohoto prostoru byla odeslána na adresu %s, která není spojena s vaším účtem
diff --git a/vector/src/main/res/values-de/strings.xml b/vector/src/main/res/values-de/strings.xml
index f9214383fb9..493a58ca0eb 100644
--- a/vector/src/main/res/values-de/strings.xml
+++ b/vector/src/main/res/values-de/strings.xml
@@ -3010,7 +3010,7 @@
     
     Starte die Anwendung neu, damit die Änderung wirksam wird.
     LaTeX-Mathematik aktivieren
-    %s in den Einstellungen, um Einladungen direkt in Element zu erhalten.
+    %s in den Einstellungen, um Einladungen direkt in ${app_name} zu erhalten.
     Diese Einladung zu diesem Raum wurde an %s gesendet, der nicht mit deinem Konto verbunden ist
     Das System sendet automatisch Protokolle, wenn ein Fehler bei der Entschlüsselung auftritt
     Entschlüsselungsfehler automatisch melden.
diff --git a/vector/src/main/res/values-es/strings.xml b/vector/src/main/res/values-es/strings.xml
index 5de1aa8bc36..c0a5308ca7a 100644
--- a/vector/src/main/res/values-es/strings.xml
+++ b/vector/src/main/res/values-es/strings.xml
@@ -2933,7 +2933,7 @@ Por favor permite el acceso en la próxima ventana emergente para descubrir usua
     Llamada sonando…
     Espacios
     Aprende más
-    %s en Configuración para recibir invitaciones directamente en Element.
+    %s en Configuración para recibir invitaciones directamente en ${app_name}.
     Vincula este correo electrónico con tu cuenta
     Esta invitación a este espacio se envió a %s que no está asociado con su cuenta
     Esta invitación a esta sala se envió a %s que no está asociado con su cuenta
diff --git a/vector/src/main/res/values-et/strings.xml b/vector/src/main/res/values-et/strings.xml
index e6ad731065b..f61afa6dce2 100644
--- a/vector/src/main/res/values-et/strings.xml
+++ b/vector/src/main/res/values-et/strings.xml
@@ -2860,7 +2860,7 @@
     Kõik hõlmava kogukonna liikmed saavad antud jututuba leida ja sellega liituda - sa ei pea kedagi ükshaaval kutsuma. Neid jututoa seadistusi saad igal hetkel muuta.
     Kõik %s jututoa liikmed saavad antud jututuba leida ja sellega liituda - sa ei pea kedagi ükshaaval kutsuma. Neid jututoa seadistusi saad igal hetkel muuta.
     %1$s - tagasipöördumiseks klõpsi
-    %s ja saa kutsed otse Element\'i.
+    %s ja saa kutsed otse ${app_name}\'i.
     Seo see e-posti aadress oma kasutajakontoga
     See kutse siia kogukonnakeskusesse saadeti aadressile %s, mis ei ole seotud sinu kontoga
     See kutse siia jututuppa saadeti aadressile %s, mis ei ole seotud sinu kontoga
diff --git a/vector/src/main/res/values-fa/strings.xml b/vector/src/main/res/values-fa/strings.xml
index 340a39e5856..fb07fc7d813 100644
--- a/vector/src/main/res/values-fa/strings.xml
+++ b/vector/src/main/res/values-fa/strings.xml
@@ -2858,7 +2858,7 @@
     دیگر
     اشاره‌ها و کلیدواژگان
     آگاهی‌های پیش‌گزیده
-    %s در تنظیمات برای دریافت مستقیم دعوت‌ها در المنت.
+    %s در تنظیمات برای دریافت مستقیم دعوت‌ها در المنت.
     این رایانامه را به حسابتان پیوند دهید
     این دعوت به این فضا به %s فرستاده شده که با حسابتان در ارتباط نیست
     این دعوت به این اتاق به %s فرستاده شده که با حسابتان در ارتباط نیست
diff --git a/vector/src/main/res/values-fr/strings.xml b/vector/src/main/res/values-fr/strings.xml
index 26f3c4f36b6..d573dd37183 100644
--- a/vector/src/main/res/values-fr/strings.xml
+++ b/vector/src/main/res/values-fr/strings.xml
@@ -2860,7 +2860,7 @@
     Autre
     Mentions et mots-clés
     Notifications par défaut
-    %s dans les paramètres pour recevoir les invitations directement dans Element.
+    %s dans les paramètres pour recevoir les invitations directement dans ${app_name}.
     Lier cet e-mail à votre compte
     Cette invitation à cette espace a été envoyée à %s qui n’est pas associé à votre compte
     Cette invitation à ce salon a été envoyée à %s qui n’est pas associé à votre compte
diff --git a/vector/src/main/res/values-hu/strings.xml b/vector/src/main/res/values-hu/strings.xml
index 77e18e05670..51e9ac831ed 100644
--- a/vector/src/main/res/values-hu/strings.xml
+++ b/vector/src/main/res/values-hu/strings.xml
@@ -2856,7 +2856,7 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze
     Elhúzás a megszakításhoz
     Hang üzenet felvétele
     %s tér tagság megtalálhatja és hozzáférhet. Más tereket is beállíthatsz.
-    %s a Beállításokba a közvetlen meghívások fogadásához az Elemenetben.
+    %s a Beállításokba a közvetlen meghívások fogadásához az ${app_name}.
     Ehhez a térhez a meghívó ide lett elküldve: %s, ami nincs összefüggésben a fiókoddal
     Ehhez a szobához a meghívó ide lett elküldve: %s, ami nincs összefüggésben a fiókoddal
     Ennek az e-mailnek a fiókhoz való kötése
diff --git a/vector/src/main/res/values-in/strings.xml b/vector/src/main/res/values-in/strings.xml
index 21af56e5809..98cd4d27bf8 100644
--- a/vector/src/main/res/values-in/strings.xml
+++ b/vector/src/main/res/values-in/strings.xml
@@ -2619,7 +2619,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
     Tambahkan sebuah space ke space apa saja yang Anda dapat kelola.
     Beri nama untuk melanjutkan.
     Gagal untuk memvalidasi PIN, mohon ketuk yang baru.
-    %s di Pengaturan untuk menerima undangan secara langsung di Element.
+    %s di Pengaturan untuk menerima undangan secara langsung di ${app_name}.
     Tautkan email ini ke akun Anda
     Undangan space ini telah dikirim ke %s yang tidak diasosiasikan dengan akun Anda
     Undangan ruangan ini telah dikirim ke %s yang tidak diasosiasikan dengan akun Anda
diff --git a/vector/src/main/res/values-it/strings.xml b/vector/src/main/res/values-it/strings.xml
index 2eff4ea6cf6..afd2dffd4e6 100644
--- a/vector/src/main/res/values-it/strings.xml
+++ b/vector/src/main/res/values-it/strings.xml
@@ -2849,7 +2849,7 @@
     Altro
     Menzioni e parole chiave
     Notifiche predefinite
-    %s nella impostazioni per ricevere inviti direttamente in Element.
+    %s nella impostazioni per ricevere inviti direttamente in ${app_name}.
     Collega questa email con il tuo account
     Questo invito per questo spazio è stato inviato a %s, la quale non è associata al tuo account
     Questo invito per questa stanza è stato inviato a %s, la quale non è associata al tuo account
diff --git a/vector/src/main/res/values-nl/strings.xml b/vector/src/main/res/values-nl/strings.xml
index f013aa8a7e0..1c3e540cbd3 100644
--- a/vector/src/main/res/values-nl/strings.xml
+++ b/vector/src/main/res/values-nl/strings.xml
@@ -2934,7 +2934,7 @@
     Poll maken
     Start de toepassing opnieuw om de wijziging door te voeren.
     LaTeX wiskunde inschakelen
-    %s in Instellingen om uitnodigingen rechtstreeks in Element te ontvangen.
+    %s in Instellingen om uitnodigingen rechtstreeks in ${app_name} te ontvangen.
     Koppel deze e-mail aan uw account
     Deze uitnodiging voor deze space is verzonden naar %s die niet is gekoppeld aan uw account
     Deze uitnodiging voor deze kamer is verzonden naar %s die niet is gekoppeld aan uw account
diff --git a/vector/src/main/res/values-pl/strings.xml b/vector/src/main/res/values-pl/strings.xml
index c9478d09978..04ef105f600 100644
--- a/vector/src/main/res/values-pl/strings.xml
+++ b/vector/src/main/res/values-pl/strings.xml
@@ -2477,7 +2477,7 @@
     • Serwery pasujące do %s zostały usunięte z listy zablokowanych.
     • Serwery pasujące do %s są teraz zablokowane.
     %1$s, %2$s i %3$s
-    %s w Ustawieniach, aby otrzymywać zaproszenia bezpośrednio w Elememencie.
+    %s w Ustawieniach, aby otrzymywać zaproszenia bezpośrednio w ${app_name}.
     Zmiana tematu
     Aktualizacja Przestrzeni
     Aktualizacja pokoju
diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/vector/src/main/res/values-pt-rBR/strings.xml
index c6cda609723..4f529b08e75 100644
--- a/vector/src/main/res/values-pt-rBR/strings.xml
+++ b/vector/src/main/res/values-pt-rBR/strings.xml
@@ -2859,7 +2859,7 @@
     Outras
     Menções e Palavrachaves
     Notificações Default
-    %s em Configurações para receber convites diretamente em Element.
+    %s em Configurações para receber convites diretamente em ${app_name}.
     Linkar este email com sua conta
     Este convite para este espaço foi enviado para %s que não está associado com sua conta
     Este convite para esta sala foi enviado para %s que não está associado com sua conta
diff --git a/vector/src/main/res/values-ru/strings.xml b/vector/src/main/res/values-ru/strings.xml
index 9ecb710da46..6a9498a564d 100644
--- a/vector/src/main/res/values-ru/strings.xml
+++ b/vector/src/main/res/values-ru/strings.xml
@@ -2945,7 +2945,7 @@
     Упоминания и ключевые слова
     Уведомления по умолчанию
     Чтобы отправлять голосовые сообщения, предоставьте разрешение на Микрофон.
-    %s в настройках, чтобы получать приглашения непосредственно в Element.
+    %s в настройках, чтобы получать приглашения непосредственно в ${app_name}.
     Свяжите этот адрес электронной почты с вашей учетной записью
     Приглашение в эту комнату было отправлено на %s, который не связан с вашей учетной записью
     Приглашение в это пространство было отправлено на %s, который не связан с вашей учетной записью
diff --git a/vector/src/main/res/values-sk/strings.xml b/vector/src/main/res/values-sk/strings.xml
index daed54c19d8..14ee5d9356a 100644
--- a/vector/src/main/res/values-sk/strings.xml
+++ b/vector/src/main/res/values-sk/strings.xml
@@ -2463,7 +2463,7 @@
     Obrázok QR kódu
     Vytvorte novú priamu konverzáciu naskenovaním QR kódu
     Pridať pomocou QR kódu
-    %s v Nastaveniach na prijímanie pozvánok priamo v Elemente.
+    %s v Nastaveniach na prijímanie pozvánok priamo v ${app_name}e.
     Táto pozvánka do tohto priestoru bola odoslaná na adresu %s, ktorá nie je spojená s vaším účtom
     Táto pozvánka do tejto miestnosti bola odoslaná na adresu %s, ktorá nie je spojená s vaším účtom
     Každý, kto sa nachádza v nadradenom priestore, bude môcť túto miestnosť nájsť a pripojiť sa k nej - nie je potrebné každého ručne pozývať. V nastaveniach miestnosti to budete môcť kedykoľvek zmeniť.
diff --git a/vector/src/main/res/values-sq/strings.xml b/vector/src/main/res/values-sq/strings.xml
index 03d8a94cdd5..6dfa7b928b7 100644
--- a/vector/src/main/res/values-sq/strings.xml
+++ b/vector/src/main/res/values-sq/strings.xml
@@ -2849,7 +2849,7 @@
     Tjetër
     Përmendje dhe Fjalëkyçe
     Njoftime Parazgjedhje
-    %s te Rregullimet, që të merrni ftesa drejt e në Element.
+    %s te Rregullimet, që të merrni ftesa drejt e në ${app_name}.
     Lidheni këtë email me llogarinë tuaj
     Kjo ftesë për te kjo hapësirë u dërgua te %s që s’është i përshoqëruar me llogarinë tuaj
     Kjo ftesë për te kjo dhomë qe dërguar për %s që s’është i përshoqëruar me llogarinë tuaj
diff --git a/vector/src/main/res/values-sv/strings.xml b/vector/src/main/res/values-sv/strings.xml
index 5d6e3572c86..5fc5ab93122 100644
--- a/vector/src/main/res/values-sv/strings.xml
+++ b/vector/src/main/res/values-sv/strings.xml
@@ -2859,7 +2859,7 @@
     Hemserver-API-URL
     Saknar behörighet
     För att skicka röstmeddelanden, vänligen ge mikrofonåtkomst.
-    %s i inställningar för att ta emot inbjudningar direkt i Element.
+    %s i inställningar för att ta emot inbjudningar direkt i ${app_name}.
     Länka den här e-postadressen med ditt konto
     Denna inbjudan till det här utrymmet skickades till %s, vilket inte är associerat med ditt konto
     Denna inbjudan till det här rummet skickades till %s, vilket inte är associerat med ditt konto
diff --git a/vector/src/main/res/values-uk/strings.xml b/vector/src/main/res/values-uk/strings.xml
index dd07ae649e2..56d95e711a0 100644
--- a/vector/src/main/res/values-uk/strings.xml
+++ b/vector/src/main/res/values-uk/strings.xml
@@ -2062,7 +2062,7 @@
     Створення кімнати…
     Створення кімнати…
     Адміністратор вашого сервера вимкнув автоматичне наскрізне шифрування для приватних кімнат та особистих повідомлень.
-    %s у налаштуваннях, щоб отримувати запрошення безпосередньо в Element.
+    %s у налаштуваннях, щоб отримувати запрошення безпосередньо в ${app_name}.
     Зашифровані особисті повідомлення
     Особисті повідомлення
     Введіть ключ відновлення
diff --git a/vector/src/main/res/values-vi/strings.xml b/vector/src/main/res/values-vi/strings.xml
index 6e632a87d8b..94147d6e45a 100644
--- a/vector/src/main/res/values-vi/strings.xml
+++ b/vector/src/main/res/values-vi/strings.xml
@@ -1704,7 +1704,7 @@
     Câu hỏi hoặc chủ đề
     Câu hỏi hoặc chủ đề thăm dò ý kiến
     Tạo Cuộc thăm dò ý kiến
-    %s trong Cài đặt để nhận lời mời trực tiếp trong Element.
+    %s trong Cài đặt để nhận lời mời trực tiếp trong ${app_name}.
     Liên kết email này với tài khoản của bạn
     Lời mời này đến Space này đã được gửi đến %s không được liên kết với tài khoản của bạn
     Lời mời này đến phòng này đã được gửi đến %s không được liên kết với tài khoản của bạn
diff --git a/vector/src/main/res/values-zh-rCN/strings.xml b/vector/src/main/res/values-zh-rCN/strings.xml
index fa5e819ec14..afafabf60dc 100644
--- a/vector/src/main/res/values-zh-rCN/strings.xml
+++ b/vector/src/main/res/values-zh-rCN/strings.xml
@@ -2808,7 +2808,7 @@
     默认通知
     活跃视频通话
     活跃语音通话
-    在 Element 中直接接收邀请的设置 %s。
+    在 ${app_name} 中直接接收邀请的设置 %s。
     将此邮箱与您的账户相链接
     加入这个空间的邀请被发送至 %s,此邮箱未与您的账户相关联
     加入这个聊天室的邀请被发送至 %s,此邮箱未与您的账户相关联
diff --git a/vector/src/main/res/values-zh-rTW/strings.xml b/vector/src/main/res/values-zh-rTW/strings.xml
index 6fe33a224dd..79329d7534e 100644
--- a/vector/src/main/res/values-zh-rTW/strings.xml
+++ b/vector/src/main/res/values-zh-rTW/strings.xml
@@ -2806,7 +2806,7 @@
     其他
     提及與關鍵字
     預設通知
-    設定中的 %s 可直接在 Element 中接收邀請。
+    設定中的 %s 可直接在 ${app_name} 中接收邀請。
     將此電子郵件與您的帳號連結
     此空間的邀請已傳送給與您的帳號無關的 %s
     此聊天室的邀請已傳送給與您的帳號無關的 %s
diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml
index ed54500a9d6..a1152dbb8fc 100644
--- a/vector/src/main/res/values/strings.xml
+++ b/vector/src/main/res/values/strings.xml
@@ -3686,9 +3686,8 @@
     This invite to this space was sent to %s which is not associated with your account
 
     Link this email with your account
-    
     
-    %s in Settings to receive invites directly in Element.
+    %s in Settings to receive invites directly in ${app_name}.
 
     Enable LaTeX mathematics
     Restart the application for the change to take effect.

From e35d6a676a6790654a5b20f2f314f2a63633b2da Mon Sep 17 00:00:00 2001
From: Benoit Marty 
Date: Tue, 22 Feb 2022 15:44:14 +0100
Subject: [PATCH 570/581] Create template with the app name

---
 vector/src/main/res/values-cs/strings.xml     | 2 +-
 vector/src/main/res/values-de/strings.xml     | 2 +-
 vector/src/main/res/values-et/strings.xml     | 2 +-
 vector/src/main/res/values-fa/strings.xml     | 2 +-
 vector/src/main/res/values-fr/strings.xml     | 2 +-
 vector/src/main/res/values-hu/strings.xml     | 2 +-
 vector/src/main/res/values-in/strings.xml     | 2 +-
 vector/src/main/res/values-it/strings.xml     | 2 +-
 vector/src/main/res/values-ja/strings.xml     | 2 +-
 vector/src/main/res/values-nl/strings.xml     | 2 +-
 vector/src/main/res/values-pl/strings.xml     | 2 +-
 vector/src/main/res/values-pt-rBR/strings.xml | 2 +-
 vector/src/main/res/values-ru/strings.xml     | 2 +-
 vector/src/main/res/values-sk/strings.xml     | 2 +-
 vector/src/main/res/values-sq/strings.xml     | 2 +-
 vector/src/main/res/values-sv/strings.xml     | 2 +-
 vector/src/main/res/values-tr/strings.xml     | 2 +-
 vector/src/main/res/values-uk/strings.xml     | 2 +-
 vector/src/main/res/values-vi/strings.xml     | 2 +-
 vector/src/main/res/values-zh-rCN/strings.xml | 2 +-
 vector/src/main/res/values-zh-rTW/strings.xml | 2 +-
 vector/src/main/res/values/strings.xml        | 3 +--
 22 files changed, 22 insertions(+), 23 deletions(-)

diff --git a/vector/src/main/res/values-cs/strings.xml b/vector/src/main/res/values-cs/strings.xml
index ddb342d60de..871b6df78b1 100644
--- a/vector/src/main/res/values-cs/strings.xml
+++ b/vector/src/main/res/values-cs/strings.xml
@@ -3097,7 +3097,7 @@
     Pomozte nám identifikovat problémy a vylepšit Element sdílením anonymních údajů o používání. Abychom pochopili, jak lidé používají více zařízení, vygenerujeme náhodný identifikátor sdílený vašimi zařízeními.
 \n
 \nMůžete si přečíst všechny naše podmínky %s.
-    Pomozte vylepšit Element
+    Pomozte vylepšit ${app_name}
     Povolit
     Restartujte aplikaci, aby se změna projevila.
     Povolit matematické výrazy LaTeXu
diff --git a/vector/src/main/res/values-de/strings.xml b/vector/src/main/res/values-de/strings.xml
index 493a58ca0eb..e9f621f2c6d 100644
--- a/vector/src/main/res/values-de/strings.xml
+++ b/vector/src/main/res/values-de/strings.xml
@@ -2991,7 +2991,7 @@
     Rechtliches
     Entscheide, welche Spaces Zugriff auf den Raum haben sollen. Die Mitglieder der Spaces können diesen Räumen beitreten.
     hier
-    Hilf mit, Element zu verbessern
+    Hilf mit, ${app_name} zu verbessern
     Aktivieren
     Farbe des Nicknamens ändern
     Du hast die volle Kontrolle.
diff --git a/vector/src/main/res/values-et/strings.xml b/vector/src/main/res/values-et/strings.xml
index f61afa6dce2..bf57e8e16be 100644
--- a/vector/src/main/res/values-et/strings.xml
+++ b/vector/src/main/res/values-et/strings.xml
@@ -3037,7 +3037,7 @@
     Võimalike vigade leidmiseks ja Element\'i arendamiseks jaga meiega anonüümseid andmeid. Selleks, et mõistaksime, kuidas kasutajad erinevaid seadmeid pruugivad me loome sinu seadmetele ühise juhusliku tunnuse.
 \n
 \nMeie kasutustingimused leiad siit - %s.
-    Aita Element\'i arendamisel
+    Aita ${app_name}\'i arendamisel
     Võta kasutusele
     Muudatuste jõustamiseks käivita rakendus uuesti.
     Kasuta LaTeX-vorminduses matemaatika märgistust
diff --git a/vector/src/main/res/values-fa/strings.xml b/vector/src/main/res/values-fa/strings.xml
index fb07fc7d813..59440e3343b 100644
--- a/vector/src/main/res/values-fa/strings.xml
+++ b/vector/src/main/res/values-fa/strings.xml
@@ -3038,7 +3038,7 @@
     ما اطّلاعات را با سوم‌شخص‌ها هم‌رسانی نمی‌کنیم
     ما هیچ دادهٔ حسابی را ذخیره یا نمایه نمی‌کنیم
     این‌جا
-    کمک به بهبود المنت
+    کمک به بهبود المنت
     به کار انداختن
     برای اثربخشی تغییر، برنامه را دوباره اجرا کنید.
     به کار انداختن ریاضیات لاتک
diff --git a/vector/src/main/res/values-fr/strings.xml b/vector/src/main/res/values-fr/strings.xml
index d573dd37183..e664ff63d11 100644
--- a/vector/src/main/res/values-fr/strings.xml
+++ b/vector/src/main/res/values-fr/strings.xml
@@ -3039,7 +3039,7 @@
     Aidez nous à identifier les problèmes et améliorer Element en envoyant des rapports d’usage anonymes. Pour comprendre de quelle manière les gens utilisent Element sur plusieurs appareils, nous créeront un identifiant aléatoire commun à tous vos appareils.
 \n
 \nVous pouvez lire toutes les conditions %s.
-    Aider à améliorer Element
+    Aider à améliorer ${app_name}
     Activer
     Créer un sondage
     Ouvrir les contacts
diff --git a/vector/src/main/res/values-hu/strings.xml b/vector/src/main/res/values-hu/strings.xml
index 51e9ac831ed..6d852041725 100644
--- a/vector/src/main/res/values-hu/strings.xml
+++ b/vector/src/main/res/values-hu/strings.xml
@@ -3034,7 +3034,7 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze
     Segíts észrevennünk a hibákat, és jobbá tenni az Element-et a névtelen használati adatok küldése által. Ahhoz, hogy megértsük, hogyan használnak a felhasználók egyszerre több eszközt, egy véletlenszerű azonosítót generálunk, ami az eszközeid között meg lesz osztva.
 \n
 \nElolvashatod a feltételeinket %s.
-    Segíts az Element-et jobbá tenni
+    Segíts az ${app_name}-et jobbá tenni
     nyerő válasz
     Jogi dolgok
     A változások életbelépéséhez indítsd újra az alkalmazást.
diff --git a/vector/src/main/res/values-in/strings.xml b/vector/src/main/res/values-in/strings.xml
index 98cd4d27bf8..c45f2f54aea 100644
--- a/vector/src/main/res/values-in/strings.xml
+++ b/vector/src/main/res/values-in/strings.xml
@@ -2983,7 +2983,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
     Bantu kami mengidentifikasi masalah-masalah dan membuat Element lebih baik dengan membagikan data penggunaan anonim. Untuk memahami bagaimana orang-orang menggunakan beberapa perangkat-perangkat, kami akan membuat pengenal acak, yang dibagikan oleh perangkat Anda.
 \n
 \nAnda dapat membaca semua kebijakan kami %s.
-    Bantu buat Element lebih baik
+    Bantu buat ${app_name} lebih baik
     Aktifkan
     Mulai ulang aplikasi ini untuk menerapkan perubahan.
     Aktifkan matematika LaTeX
diff --git a/vector/src/main/res/values-it/strings.xml b/vector/src/main/res/values-it/strings.xml
index afd2dffd4e6..d6768d2a049 100644
--- a/vector/src/main/res/values-it/strings.xml
+++ b/vector/src/main/res/values-it/strings.xml
@@ -3028,7 +3028,7 @@
     Aiutaci a identificare problemi e a migliorare Element condividendo dati di utilizzo anonimi. Per capire come le persone usano diversi dispositivi, genereremo un identificativo casuale, condiviso dai tuoi dispositivi.
 \n
 \nPuoi leggere i nostri termini di servizio %s.
-    Aiuta a migliorare Element
+    Aiuta a migliorare ${app_name}
     Attiva
     Riavvia l\'applicazione per applicare le modifiche.
     Attiva la matematica LaTeX
diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index d5304474788..84b2c3263b2 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2314,7 +2314,7 @@
     これはいつでも設定から無効にできます
     私たちは、情報を第三者と共有することはありません
     私たちは、アカウントのデータを記録したり分析したりすることはありません
-    Elementの改善を手伝う
+    ${app_name}の改善を手伝う
     このルームを「リンクを知っている人が参加可能」に設定しました。
     どのユーザーも無視していません
     キーワードを入力するとリアクションを検索できます。
diff --git a/vector/src/main/res/values-nl/strings.xml b/vector/src/main/res/values-nl/strings.xml
index 1c3e540cbd3..8a4c2670110 100644
--- a/vector/src/main/res/values-nl/strings.xml
+++ b/vector/src/main/res/values-nl/strings.xml
@@ -2239,7 +2239,7 @@
     Help ons problemen te identificeren en Element te verbeteren door anonieme gebruiksgegevens te delen. Om inzicht te krijgen in hoe mensen meerdere apparaten gebruiken, genereren we een willekeurige identificatie die door uw apparaten wordt gedeeld.
 \n
 \nU kunt al onze voorwaarden %s lezen.
-    Help Element verbeteren
+    Help ${app_name} verbeteren
     Dit zal uw huidige sleutel of zin vervangen.
     Genereer een nieuwe beveiligingssleutel of stel een nieuwe beveiligingszin in voor uw bestaande back-up.
     Bescherm uzelf tegen verlies van toegang tot versleutelde berichten en gegevens door een back-up te maken van versleutelingssleutels op uw server.
diff --git a/vector/src/main/res/values-pl/strings.xml b/vector/src/main/res/values-pl/strings.xml
index 04ef105f600..7caf382e554 100644
--- a/vector/src/main/res/values-pl/strings.xml
+++ b/vector/src/main/res/values-pl/strings.xml
@@ -3046,7 +3046,7 @@
     Pomóż nam znaleźć błędy i ulepszyć Element poprzez udostępnianie anonimowych danych użytkowania. Aby lepiej zrozumieć jak użytkownicy wykorzystują wiele urządzeń wygenerujemy losowy identyfikator dzielony pomiędzy Twoimi urządzeniami.
 \n
 \nWięcej informacji %s.
-    Pomóż usprawnić Element
+    Pomóż usprawnić ${app_name}
     Nie teraz
     Włącz
     
diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/vector/src/main/res/values-pt-rBR/strings.xml
index 4f529b08e75..ba12e21a282 100644
--- a/vector/src/main/res/values-pt-rBR/strings.xml
+++ b/vector/src/main/res/values-pt-rBR/strings.xml
@@ -3032,7 +3032,7 @@
     Ajude-nos a identificar problemas e melhorar Element ao compartilhar dados de uso anônimos. Para entender como pessoas usam seus múltiplos dispositivos, nós vamos gerar um identificador aleatório, compartilhado por seus dispositivos.
 \n
 \nVocê pode ler todos os nossos termos %s.
-    Ajude a melhorar Element
+    Ajude a melhorar ${app_name}
     Configurações de sistema
     Ajuda e suporte
     Ajuda
diff --git a/vector/src/main/res/values-ru/strings.xml b/vector/src/main/res/values-ru/strings.xml
index 6a9498a564d..e57cfa8e1f2 100644
--- a/vector/src/main/res/values-ru/strings.xml
+++ b/vector/src/main/res/values-ru/strings.xml
@@ -3176,7 +3176,7 @@
     Помогите нам выявить проблемы и улучшить Element, поделившись анонимными данными об использовании. Чтобы понять, как люди используют несколько устройств, мы сгенерируем случайный идентификатор, общий для всех ваших устройств.
 \n
 \nВы можете ознакомиться со всеми нашими условиями %s.
-    Помогите улучшить Element
+    Помогите улучшить ${app_name}
     Сессия завершена!
     Комната покинута!
     Шифрование неправильно настроено, поэтому вы не можете отправлять сообщения. Нажмите, чтобы открыть настройки.
diff --git a/vector/src/main/res/values-sk/strings.xml b/vector/src/main/res/values-sk/strings.xml
index 14ee5d9356a..3d8b2b830c6 100644
--- a/vector/src/main/res/values-sk/strings.xml
+++ b/vector/src/main/res/values-sk/strings.xml
@@ -2628,7 +2628,7 @@
     Pomôžte nám identifikovať problémy a zlepšiť Element zdieľaním anonymných údajov o používaní. Aby sme pochopili, ako ľudia používajú viacero zariadení, vygenerujeme náhodný identifikátor, ktorý zdieľajú vaše zariadenia.
 \n
 \nMôžete si prečítať všetky naše podmienky %s.
-    Pomôžte zlepšiť Element
+    Pomôžte zlepšiť ${app_name}
     %1$s Ťuknite pre návrat
     Aktívny hovor (%1$s) ·
     
diff --git a/vector/src/main/res/values-sq/strings.xml b/vector/src/main/res/values-sq/strings.xml
index 6dfa7b928b7..03911e18f04 100644
--- a/vector/src/main/res/values-sq/strings.xml
+++ b/vector/src/main/res/values-sq/strings.xml
@@ -3022,7 +3022,7 @@
     Ndihmonani të identifikojmë probleme dhe të përmirësojmë Element-in, duke ndarë me ne të dhëna anonime përdorimi. Për të kuptuar se si i përdorin njerëzit disa pajisje njëherësh, do të prodhojmë një identifikues kuturu, të përbashkët për pajisjet tuaja.
 \n
 \nMund të lexoni krejt kushtet tona %s.
-    Ndihmoni të përmirësohet Element-in
+    Ndihmoni të përmirësohet ${app_name}-in
     Aktivizoje
     
         %1$d votë e hedhur. Votoni, që të shihni përfundimet
diff --git a/vector/src/main/res/values-sv/strings.xml b/vector/src/main/res/values-sv/strings.xml
index 5fc5ab93122..84d58a080f8 100644
--- a/vector/src/main/res/values-sv/strings.xml
+++ b/vector/src/main/res/values-sv/strings.xml
@@ -3002,7 +3002,7 @@
     Hjälp oss att identifiera problem och förbättra Element genom att dela anonym användningsdata. För att förstå hur personer använder multipla enheter så generar vi en slumpmässig identifierare som delas mellan dina enheter.
 \n
 \nDu kan läsa alla våra villkor %s.
-    Hjälp att förbättra Element
+    Hjälp att förbättra ${app_name}
     Aktivera
     Är du säker på att du vill ta bort den här omröstningen\? Du kommer inte kunna få tillbaka den när den har tagits bort.
     Ta bort omröstning
diff --git a/vector/src/main/res/values-tr/strings.xml b/vector/src/main/res/values-tr/strings.xml
index b2ca20d8714..1ca6bfedb06 100644
--- a/vector/src/main/res/values-tr/strings.xml
+++ b/vector/src/main/res/values-tr/strings.xml
@@ -2077,7 +2077,7 @@
     Anonim kullanım verilerini paylaşarak sorunları belirlememize ve Element\'i iyileştirmemize yardımcı olun. İnsanların birden fazla cihazı nasıl kullandığını anlamak için, cihazlarınız tarafından paylaşılan rastgele bir tanımlayıcı oluşturacağız.
 \n
 \n Tüm şartlarımızı %s okuyabilirsiniz.
-    Öğeyi iyileştirmeye yardımcı olun
+    Öğeyi iyileştirmeye yardımcı olun
     Mobil cihazlarda şifreli odalarda bahsedilenler ve anahtar kelimeler için bildirim almayacaksınız.
     Oda yükseltmeleri
     Bot tarafından gönderilen mesajlar
diff --git a/vector/src/main/res/values-uk/strings.xml b/vector/src/main/res/values-uk/strings.xml
index 56d95e711a0..e38e676db9e 100644
--- a/vector/src/main/res/values-uk/strings.xml
+++ b/vector/src/main/res/values-uk/strings.xml
@@ -3140,7 +3140,7 @@
     Допомагайте нам визначати проблеми й удосконалювати Element, надсилаючи анонімні дані про використання. Щоб розуміти, як люди використовують кілька пристроїв, ми створимо спільний для ваших пристроїв випадковий ідентифікатор.
 \n
 \nМожете прочитати всі наші умови %s.
-    Допоможіть покращити Element
+    Допоможіть покращити ${app_name}
     Увімкнути
     Перезапустіть застосунок, щоб зміни набули чинності.
     Увімкнути підтримку LaTeX
diff --git a/vector/src/main/res/values-vi/strings.xml b/vector/src/main/res/values-vi/strings.xml
index 94147d6e45a..79ae77cc5a7 100644
--- a/vector/src/main/res/values-vi/strings.xml
+++ b/vector/src/main/res/values-vi/strings.xml
@@ -2914,7 +2914,7 @@
     Giúp chúng tôi xác định các vấn đề và cải thiện Element bằng cách chia sẻ dữ liệu sử dụng ẩn danh. Để hiểu cách mọi người sử dụng nhiều thiết bị, chúng tôi sẽ tạo ra một mã định danh ngẫu nhiên, được chia sẻ bởi các thiết bị của bạn.
 \n
 \nBạn có thể đọc tất cả các thuật ngữ của chúng tôi %s.
-    Giúp cải thiện Element
+    Giúp cải thiện ${app_name}
     Thiết bị đã bị đăng xuất!
     Căn phòng đã bị bỏ lại!
     Chọn homeerver
diff --git a/vector/src/main/res/values-zh-rCN/strings.xml b/vector/src/main/res/values-zh-rCN/strings.xml
index afafabf60dc..0cc008faac0 100644
--- a/vector/src/main/res/values-zh-rCN/strings.xml
+++ b/vector/src/main/res/values-zh-rCN/strings.xml
@@ -2979,6 +2979,6 @@
     通过共享匿名使用数据,帮助我们识别问题并改进 Element。为了理解人们如何使用多台设备,我们将生成一个随机标识符,由您的设备共享。
 \n
 \n你可以阅读我们所有的条款 %s。
-    帮助改进 Element
+    帮助改进 ${app_name}
     启用
 
\ No newline at end of file
diff --git a/vector/src/main/res/values-zh-rTW/strings.xml b/vector/src/main/res/values-zh-rTW/strings.xml
index 79329d7534e..12002a52f71 100644
--- a/vector/src/main/res/values-zh-rTW/strings.xml
+++ b/vector/src/main/res/values-zh-rTW/strings.xml
@@ -2979,7 +2979,7 @@
     透過分享匿名使用資料協助我們找出問題並改善 Element。為了了解人們如何使用多裝置,我們將會產生隨機識別字串,在您的裝置間共享。
 \n
 \n您可以閱讀我們的條款 %s。
-    協助改善 Element
+    協助改善 ${app_name}
     啟用
     重新啟動應用程式以讓變更生效。
     啟用 LaTeX 數學
diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml
index a1152dbb8fc..5f2f2dabb2f 100644
--- a/vector/src/main/res/values/strings.xml
+++ b/vector/src/main/res/values/strings.xml
@@ -1386,8 +1386,7 @@
     Please enable analytics to help us improve ${app_name}.
     Yes, I want to help!
 
-    
-    Help improve Element
+    Help improve ${app_name}
     
     
     Help us identify issues and improve Element by sharing anonymous usage data. To understand how people use multiple devices, we’ll generate a random identifier, shared by your devices.\n\nYou can read all our terms %s.

From 9cd4b5d3b862ca392a6c1ae963e528dc4ffbe123 Mon Sep 17 00:00:00 2001
From: Benoit Marty 
Date: Tue, 22 Feb 2022 15:45:47 +0100
Subject: [PATCH 571/581] Create template with the app name

---
 vector/src/main/res/values-cs/strings.xml     | 2 +-
 vector/src/main/res/values-de/strings.xml     | 2 +-
 vector/src/main/res/values-et/strings.xml     | 2 +-
 vector/src/main/res/values-fa/strings.xml     | 2 +-
 vector/src/main/res/values-fr/strings.xml     | 2 +-
 vector/src/main/res/values-hu/strings.xml     | 2 +-
 vector/src/main/res/values-in/strings.xml     | 2 +-
 vector/src/main/res/values-it/strings.xml     | 2 +-
 vector/src/main/res/values-ja/strings.xml     | 2 +-
 vector/src/main/res/values-nl/strings.xml     | 2 +-
 vector/src/main/res/values-pl/strings.xml     | 2 +-
 vector/src/main/res/values-pt-rBR/strings.xml | 2 +-
 vector/src/main/res/values-ru/strings.xml     | 2 +-
 vector/src/main/res/values-sk/strings.xml     | 2 +-
 vector/src/main/res/values-sq/strings.xml     | 2 +-
 vector/src/main/res/values-sv/strings.xml     | 2 +-
 vector/src/main/res/values-tr/strings.xml     | 2 +-
 vector/src/main/res/values-uk/strings.xml     | 2 +-
 vector/src/main/res/values-vi/strings.xml     | 2 +-
 vector/src/main/res/values-zh-rCN/strings.xml | 2 +-
 vector/src/main/res/values-zh-rTW/strings.xml | 2 +-
 vector/src/main/res/values/strings.xml        | 3 +--
 22 files changed, 22 insertions(+), 23 deletions(-)

diff --git a/vector/src/main/res/values-cs/strings.xml b/vector/src/main/res/values-cs/strings.xml
index 871b6df78b1..8284a5de6a9 100644
--- a/vector/src/main/res/values-cs/strings.xml
+++ b/vector/src/main/res/values-cs/strings.xml
@@ -3094,7 +3094,7 @@
     Nesdílíme informace s třetími stranami
     Nezaznamenáváme ani neprofilujeme žádné údaje o účtu
     zde
-    Pomozte nám identifikovat problémy a vylepšit Element sdílením anonymních údajů o používání. Abychom pochopili, jak lidé používají více zařízení, vygenerujeme náhodný identifikátor sdílený vašimi zařízeními.
+    Pomozte nám identifikovat problémy a vylepšit ${app_name} sdílením anonymních údajů o používání. Abychom pochopili, jak lidé používají více zařízení, vygenerujeme náhodný identifikátor sdílený vašimi zařízeními.
 \n
 \nMůžete si přečíst všechny naše podmínky %s.
     Pomozte vylepšit ${app_name}
diff --git a/vector/src/main/res/values-de/strings.xml b/vector/src/main/res/values-de/strings.xml
index e9f621f2c6d..3236332085a 100644
--- a/vector/src/main/res/values-de/strings.xml
+++ b/vector/src/main/res/values-de/strings.xml
@@ -2975,7 +2975,7 @@
     Du kannst dies jederzeit in den Einstellungen deaktivieren
     Wir teilen keine Informationen mit Drittpersonen
     Wir erfassen und analysieren keine Accountdaten
-    Hilf uns dabei Probleme zu identifizieren und Element zu verbessern, indem du anonyme Nutzungsdaten teilst. Um zu verstehen, wie Personen mehrere Geräte benutzen, werden wir eine zufällige Kennung generieren, die zwischen deinen Geräten geteilt wird.
+    Hilf uns dabei Probleme zu identifizieren und ${app_name} zu verbessern, indem du anonyme Nutzungsdaten teilst. Um zu verstehen, wie Personen mehrere Geräte benutzen, werden wir eine zufällige Kennung generieren, die zwischen deinen Geräten geteilt wird.
 \n
 \n%s kannst du alle unsere Bedingungen lesen.
     Stelle sicher, dass die richtigen Personen Zugriff auf %s haben. Du kannst jederzeit weitere Personen einladen.
diff --git a/vector/src/main/res/values-et/strings.xml b/vector/src/main/res/values-et/strings.xml
index bf57e8e16be..bf486045be2 100644
--- a/vector/src/main/res/values-et/strings.xml
+++ b/vector/src/main/res/values-et/strings.xml
@@ -3034,7 +3034,7 @@
     Meie ei jaga teavet kolmandate osapooltega
     Meie ei salvesta ega profileeri sinu kasutajakonto andmeid
     siit
-    Võimalike vigade leidmiseks ja Element\'i arendamiseks jaga meiega anonüümseid andmeid. Selleks, et mõistaksime, kuidas kasutajad erinevaid seadmeid pruugivad me loome sinu seadmetele ühise juhusliku tunnuse.
+    Võimalike vigade leidmiseks ja ${app_name}\'i arendamiseks jaga meiega anonüümseid andmeid. Selleks, et mõistaksime, kuidas kasutajad erinevaid seadmeid pruugivad me loome sinu seadmetele ühise juhusliku tunnuse.
 \n
 \nMeie kasutustingimused leiad siit - %s.
     Aita ${app_name}\'i arendamisel
diff --git a/vector/src/main/res/values-fa/strings.xml b/vector/src/main/res/values-fa/strings.xml
index 59440e3343b..c5e92e18f31 100644
--- a/vector/src/main/res/values-fa/strings.xml
+++ b/vector/src/main/res/values-fa/strings.xml
@@ -2995,7 +2995,7 @@
     با فرستادن این اطّلاعات موافقید؟
     اکنون نه
     برای کشف آشنایان موجود، لازم است اطلاعات آشنایان (رایانامه‌ها و شماره تلفن‌ها) را به کارساز هویتتان بفرستید. برای محرمانگیتان، داده‌هایتان را پیش از فرستادن، در هم می‌ریزیم.
-    با هم‌رسانی داده‌ّای استفادهٔ ناشناس، در تشخیص مشکل‌ها و بهبود المنت یاریمان کنید. برای درک چگونگی استفادهٔ مردم از چندین افزاره، شناسه‌ای کاتوره‌ای بین افزاره‌هایتان هم‌رسانی خواهیم کرد.
+    با هم‌رسانی داده‌ّای استفادهٔ ناشناس، در تشخیص مشکل‌ها و بهبود المنت یاریمان کنید. برای درک چگونگی استفادهٔ مردم از چندین افزاره، شناسه‌ای کاتوره‌ای بین افزاره‌هایتان هم‌رسانی خواهیم کرد.
 \n
 \nمی‌توانید از %s قوانینمان را بخوانید.
     مطمئنید که می خواهید این نظرسنجی را بردارید؟ پس از این کار، قادر به بازگردانیش نیستید.
diff --git a/vector/src/main/res/values-fr/strings.xml b/vector/src/main/res/values-fr/strings.xml
index e664ff63d11..ef55b09cc50 100644
--- a/vector/src/main/res/values-fr/strings.xml
+++ b/vector/src/main/res/values-fr/strings.xml
@@ -3036,7 +3036,7 @@
     Nous ne partageons aucune information avec des tiers
     Nous n’enregistrons ou ne profilons aucune donnée du compte
     ici
-    Aidez nous à identifier les problèmes et améliorer Element en envoyant des rapports d’usage anonymes. Pour comprendre de quelle manière les gens utilisent Element sur plusieurs appareils, nous créeront un identifiant aléatoire commun à tous vos appareils.
+    Aidez nous à identifier les problèmes et améliorer ${app_name} en envoyant des rapports d’usage anonymes. Pour comprendre de quelle manière les gens utilisent Element sur plusieurs appareils, nous créeront un identifiant aléatoire commun à tous vos appareils.
 \n
 \nVous pouvez lire toutes les conditions %s.
     Aider à améliorer ${app_name}
diff --git a/vector/src/main/res/values-hu/strings.xml b/vector/src/main/res/values-hu/strings.xml
index 6d852041725..574386c0b44 100644
--- a/vector/src/main/res/values-hu/strings.xml
+++ b/vector/src/main/res/values-hu/strings.xml
@@ -3031,7 +3031,7 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze
     Nem osztjuk meg az információt harmadik féllel
     Nem küldünk és nem profilozunk semmilyen fiók adatot
     itt
-    Segíts észrevennünk a hibákat, és jobbá tenni az Element-et a névtelen használati adatok küldése által. Ahhoz, hogy megértsük, hogyan használnak a felhasználók egyszerre több eszközt, egy véletlenszerű azonosítót generálunk, ami az eszközeid között meg lesz osztva.
+    Segíts észrevennünk a hibákat, és jobbá tenni az ${app_name}-et a névtelen használati adatok küldése által. Ahhoz, hogy megértsük, hogyan használnak a felhasználók egyszerre több eszközt, egy véletlenszerű azonosítót generálunk, ami az eszközeid között meg lesz osztva.
 \n
 \nElolvashatod a feltételeinket %s.
     Segíts az ${app_name}-et jobbá tenni
diff --git a/vector/src/main/res/values-in/strings.xml b/vector/src/main/res/values-in/strings.xml
index c45f2f54aea..c4c20b3a241 100644
--- a/vector/src/main/res/values-in/strings.xml
+++ b/vector/src/main/res/values-in/strings.xml
@@ -2980,7 +2980,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
     Kami tidak membagikan informasi ini dengan pihak ketiga
     Kami tidak merekam atau memprofil data akun apapun
     di sini
-    Bantu kami mengidentifikasi masalah-masalah dan membuat Element lebih baik dengan membagikan data penggunaan anonim. Untuk memahami bagaimana orang-orang menggunakan beberapa perangkat-perangkat, kami akan membuat pengenal acak, yang dibagikan oleh perangkat Anda.
+    Bantu kami mengidentifikasi masalah-masalah dan membuat ${app_name} lebih baik dengan membagikan data penggunaan anonim. Untuk memahami bagaimana orang-orang menggunakan beberapa perangkat-perangkat, kami akan membuat pengenal acak, yang dibagikan oleh perangkat Anda.
 \n
 \nAnda dapat membaca semua kebijakan kami %s.
     Bantu buat ${app_name} lebih baik
diff --git a/vector/src/main/res/values-it/strings.xml b/vector/src/main/res/values-it/strings.xml
index d6768d2a049..b5b0c09b624 100644
--- a/vector/src/main/res/values-it/strings.xml
+++ b/vector/src/main/res/values-it/strings.xml
@@ -3025,7 +3025,7 @@
     Non condividiamo informazioni con terze parti
     Non registriamo o profiliamo alcun dato dell\'account
     qui
-    Aiutaci a identificare problemi e a migliorare Element condividendo dati di utilizzo anonimi. Per capire come le persone usano diversi dispositivi, genereremo un identificativo casuale, condiviso dai tuoi dispositivi.
+    Aiutaci a identificare problemi e a migliorare ${app_name} condividendo dati di utilizzo anonimi. Per capire come le persone usano diversi dispositivi, genereremo un identificativo casuale, condiviso dai tuoi dispositivi.
 \n
 \nPuoi leggere i nostri termini di servizio %s.
     Aiuta a migliorare ${app_name}
diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 84b2c3263b2..64bf6859e78 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2704,7 +2704,7 @@
     次の絵文字が相手の画面にも同じ順番で現れるのを確認し、このユーザーを検証してください。
     信頼できないサインイン
     使用できない文字が含まれています
-    Elementの改善と課題抽出のために、匿名の使用状況データの送信をお願いします。複数の端末での使用を分析するために、あなたの全端末共通のランダムな識別子を生成します。
+    ${app_name}の改善と課題抽出のために、匿名の使用状況データの送信をお願いします。複数の端末での使用を分析するために、あなたの全端末共通のランダムな識別子を生成します。
 \n
 \n%sで利用規約を閲覧できます。
     最初の検索結果のみ表示しています。文字をもっと入力してください…
diff --git a/vector/src/main/res/values-nl/strings.xml b/vector/src/main/res/values-nl/strings.xml
index 8a4c2670110..d3cd933e161 100644
--- a/vector/src/main/res/values-nl/strings.xml
+++ b/vector/src/main/res/values-nl/strings.xml
@@ -2236,7 +2236,7 @@
     We delen geen informatie met derden
     We registreren of profileren geen accountgegevens
     hier
-    Help ons problemen te identificeren en Element te verbeteren door anonieme gebruiksgegevens te delen. Om inzicht te krijgen in hoe mensen meerdere apparaten gebruiken, genereren we een willekeurige identificatie die door uw apparaten wordt gedeeld.
+    Help ons problemen te identificeren en ${app_name} te verbeteren door anonieme gebruiksgegevens te delen. Om inzicht te krijgen in hoe mensen meerdere apparaten gebruiken, genereren we een willekeurige identificatie die door uw apparaten wordt gedeeld.
 \n
 \nU kunt al onze voorwaarden %s lezen.
     Help ${app_name} verbeteren
diff --git a/vector/src/main/res/values-pl/strings.xml b/vector/src/main/res/values-pl/strings.xml
index 7caf382e554..26d01b81caf 100644
--- a/vector/src/main/res/values-pl/strings.xml
+++ b/vector/src/main/res/values-pl/strings.xml
@@ -3043,7 +3043,7 @@
     Nie udostępniamy informacji podmiotom trzecim
      Nie zbieramy i nie profilujemy  danych użytkownika
     tutaj
-    Pomóż nam znaleźć błędy i ulepszyć Element poprzez udostępnianie anonimowych danych użytkowania. Aby lepiej zrozumieć jak użytkownicy wykorzystują wiele urządzeń wygenerujemy losowy identyfikator dzielony pomiędzy Twoimi urządzeniami.
+    Pomóż nam znaleźć błędy i ulepszyć ${app_name} poprzez udostępnianie anonimowych danych użytkowania. Aby lepiej zrozumieć jak użytkownicy wykorzystują wiele urządzeń wygenerujemy losowy identyfikator dzielony pomiędzy Twoimi urządzeniami.
 \n
 \nWięcej informacji %s.
     Pomóż usprawnić ${app_name}
diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/vector/src/main/res/values-pt-rBR/strings.xml
index ba12e21a282..d3fdd8ee051 100644
--- a/vector/src/main/res/values-pt-rBR/strings.xml
+++ b/vector/src/main/res/values-pt-rBR/strings.xml
@@ -3029,7 +3029,7 @@
     A política de seu servidorcasa
     Política de ${app_name}
     Nós não gravamos ou perfilamos quaisquer dados de conta
-    Ajude-nos a identificar problemas e melhorar Element ao compartilhar dados de uso anônimos. Para entender como pessoas usam seus múltiplos dispositivos, nós vamos gerar um identificador aleatório, compartilhado por seus dispositivos.
+    Ajude-nos a identificar problemas e melhorar ${app_name} ao compartilhar dados de uso anônimos. Para entender como pessoas usam seus múltiplos dispositivos, nós vamos gerar um identificador aleatório, compartilhado por seus dispositivos.
 \n
 \nVocê pode ler todos os nossos termos %s.
     Ajude a melhorar ${app_name}
diff --git a/vector/src/main/res/values-ru/strings.xml b/vector/src/main/res/values-ru/strings.xml
index e57cfa8e1f2..4ab6e3a75f2 100644
--- a/vector/src/main/res/values-ru/strings.xml
+++ b/vector/src/main/res/values-ru/strings.xml
@@ -3173,7 +3173,7 @@
     Мы не передаем информацию третьим лицам
     Мы не записываем и не профилируем любые данные учетной записи
     тут
-    Помогите нам выявить проблемы и улучшить Element, поделившись анонимными данными об использовании. Чтобы понять, как люди используют несколько устройств, мы сгенерируем случайный идентификатор, общий для всех ваших устройств.
+    Помогите нам выявить проблемы и улучшить ${app_name}, поделившись анонимными данными об использовании. Чтобы понять, как люди используют несколько устройств, мы сгенерируем случайный идентификатор, общий для всех ваших устройств.
 \n
 \nВы можете ознакомиться со всеми нашими условиями %s.
     Помогите улучшить ${app_name}
diff --git a/vector/src/main/res/values-sk/strings.xml b/vector/src/main/res/values-sk/strings.xml
index 3d8b2b830c6..89f05c0360e 100644
--- a/vector/src/main/res/values-sk/strings.xml
+++ b/vector/src/main/res/values-sk/strings.xml
@@ -2625,7 +2625,7 @@
     Nezaznamenávame ani neprofilujeme žiadne údaje o účte
     Potvrďte svoju totožnosť overením tohto prihlasovacieho mena, čím mu udelíte prístup k zašifrovaným správam.
     Potvrďte svoju totožnosť overením tohto prihlásenia z jednej z vašich ďalších relácií, čím jej udelíte prístup k zašifrovaným správam.
-    Pomôžte nám identifikovať problémy a zlepšiť Element zdieľaním anonymných údajov o používaní. Aby sme pochopili, ako ľudia používajú viacero zariadení, vygenerujeme náhodný identifikátor, ktorý zdieľajú vaše zariadenia.
+    Pomôžte nám identifikovať problémy a zlepšiť ${app_name} zdieľaním anonymných údajov o používaní. Aby sme pochopili, ako ľudia používajú viacero zariadení, vygenerujeme náhodný identifikátor, ktorý zdieľajú vaše zariadenia.
 \n
 \nMôžete si prečítať všetky naše podmienky %s.
     Pomôžte zlepšiť ${app_name}
diff --git a/vector/src/main/res/values-sq/strings.xml b/vector/src/main/res/values-sq/strings.xml
index 03911e18f04..d6c796cfa5c 100644
--- a/vector/src/main/res/values-sq/strings.xml
+++ b/vector/src/main/res/values-sq/strings.xml
@@ -3019,7 +3019,7 @@
     Nuk u japin hollësi palëve të treta
     Nuk regjistrojmë ose profilizojmë ndonjë të dhënë llogarie
     këtu
-    Ndihmonani të identifikojmë probleme dhe të përmirësojmë Element-in, duke ndarë me ne të dhëna anonime përdorimi. Për të kuptuar se si i përdorin njerëzit disa pajisje njëherësh, do të prodhojmë një identifikues kuturu, të përbashkët për pajisjet tuaja.
+    Ndihmonani të identifikojmë probleme dhe të përmirësojmë ${app_name}-in, duke ndarë me ne të dhëna anonime përdorimi. Për të kuptuar se si i përdorin njerëzit disa pajisje njëherësh, do të prodhojmë një identifikues kuturu, të përbashkët për pajisjet tuaja.
 \n
 \nMund të lexoni krejt kushtet tona %s.
     Ndihmoni të përmirësohet ${app_name}-in
diff --git a/vector/src/main/res/values-sv/strings.xml b/vector/src/main/res/values-sv/strings.xml
index 84d58a080f8..fcdbbc481a4 100644
--- a/vector/src/main/res/values-sv/strings.xml
+++ b/vector/src/main/res/values-sv/strings.xml
@@ -2999,7 +2999,7 @@
     Vi delar inte information med tredje parter
     Vi spelar inte in eller profilerar någon kontodata
     här
-    Hjälp oss att identifiera problem och förbättra Element genom att dela anonym användningsdata. För att förstå hur personer använder multipla enheter så generar vi en slumpmässig identifierare som delas mellan dina enheter.
+    Hjälp oss att identifiera problem och förbättra ${app_name} genom att dela anonym användningsdata. För att förstå hur personer använder multipla enheter så generar vi en slumpmässig identifierare som delas mellan dina enheter.
 \n
 \nDu kan läsa alla våra villkor %s.
     Hjälp att förbättra ${app_name}
diff --git a/vector/src/main/res/values-tr/strings.xml b/vector/src/main/res/values-tr/strings.xml
index 1ca6bfedb06..88928e86c34 100644
--- a/vector/src/main/res/values-tr/strings.xml
+++ b/vector/src/main/res/values-tr/strings.xml
@@ -2074,7 +2074,7 @@
     Bilgileri üçüncü taraflarla paylaşmayız
     Herhangi bir hesap verisini kaydetmiyoruz
     burada
-    Anonim kullanım verilerini paylaşarak sorunları belirlememize ve Element\'i iyileştirmemize yardımcı olun. İnsanların birden fazla cihazı nasıl kullandığını anlamak için, cihazlarınız tarafından paylaşılan rastgele bir tanımlayıcı oluşturacağız.
+    Anonim kullanım verilerini paylaşarak sorunları belirlememize ve ${app_name}\'i iyileştirmemize yardımcı olun. İnsanların birden fazla cihazı nasıl kullandığını anlamak için, cihazlarınız tarafından paylaşılan rastgele bir tanımlayıcı oluşturacağız.
 \n
 \n Tüm şartlarımızı %s okuyabilirsiniz.
     Öğeyi iyileştirmeye yardımcı olun
diff --git a/vector/src/main/res/values-uk/strings.xml b/vector/src/main/res/values-uk/strings.xml
index e38e676db9e..4d92411188e 100644
--- a/vector/src/main/res/values-uk/strings.xml
+++ b/vector/src/main/res/values-uk/strings.xml
@@ -3137,7 +3137,7 @@
     Ми не передаємо даних стороннім особам
     Ми не записуємо й не аналізуємо жодних даних облікового запису
     тут
-    Допомагайте нам визначати проблеми й удосконалювати Element, надсилаючи анонімні дані про використання. Щоб розуміти, як люди використовують кілька пристроїв, ми створимо спільний для ваших пристроїв випадковий ідентифікатор.
+    Допомагайте нам визначати проблеми й удосконалювати ${app_name}, надсилаючи анонімні дані про використання. Щоб розуміти, як люди використовують кілька пристроїв, ми створимо спільний для ваших пристроїв випадковий ідентифікатор.
 \n
 \nМожете прочитати всі наші умови %s.
     Допоможіть покращити ${app_name}
diff --git a/vector/src/main/res/values-vi/strings.xml b/vector/src/main/res/values-vi/strings.xml
index 79ae77cc5a7..99aa2361788 100644
--- a/vector/src/main/res/values-vi/strings.xml
+++ b/vector/src/main/res/values-vi/strings.xml
@@ -2911,7 +2911,7 @@
     Chúng tôi không  chia sẻ thông tin với bên thứ ba
     Chúng tôi không ghi âm hoặc tạo hồ sơ bất kỳ dữ liệu tài khoản nào
     ở đây
-    Giúp chúng tôi xác định các vấn đề và cải thiện Element bằng cách chia sẻ dữ liệu sử dụng ẩn danh. Để hiểu cách mọi người sử dụng nhiều thiết bị, chúng tôi sẽ tạo ra một mã định danh ngẫu nhiên, được chia sẻ bởi các thiết bị của bạn.
+    Giúp chúng tôi xác định các vấn đề và cải thiện ${app_name} bằng cách chia sẻ dữ liệu sử dụng ẩn danh. Để hiểu cách mọi người sử dụng nhiều thiết bị, chúng tôi sẽ tạo ra một mã định danh ngẫu nhiên, được chia sẻ bởi các thiết bị của bạn.
 \n
 \nBạn có thể đọc tất cả các thuật ngữ của chúng tôi %s.
     Giúp cải thiện ${app_name}
diff --git a/vector/src/main/res/values-zh-rCN/strings.xml b/vector/src/main/res/values-zh-rCN/strings.xml
index 0cc008faac0..e3339807101 100644
--- a/vector/src/main/res/values-zh-rCN/strings.xml
+++ b/vector/src/main/res/values-zh-rCN/strings.xml
@@ -2976,7 +2976,7 @@
     你可以随时在设置中关闭它
     我们与第三方共享信息
     此处
-    通过共享匿名使用数据,帮助我们识别问题并改进 Element。为了理解人们如何使用多台设备,我们将生成一个随机标识符,由您的设备共享。
+    通过共享匿名使用数据,帮助我们识别问题并改进 ${app_name}。为了理解人们如何使用多台设备,我们将生成一个随机标识符,由您的设备共享。
 \n
 \n你可以阅读我们所有的条款 %s。
     帮助改进 ${app_name}
diff --git a/vector/src/main/res/values-zh-rTW/strings.xml b/vector/src/main/res/values-zh-rTW/strings.xml
index 12002a52f71..0a0f38ec665 100644
--- a/vector/src/main/res/values-zh-rTW/strings.xml
+++ b/vector/src/main/res/values-zh-rTW/strings.xml
@@ -2976,7 +2976,7 @@
     我們不會與第三方分享資訊
     我們不會記錄或分析任何帳號資料
     這裡
-    透過分享匿名使用資料協助我們找出問題並改善 Element。為了了解人們如何使用多裝置,我們將會產生隨機識別字串,在您的裝置間共享。
+    透過分享匿名使用資料協助我們找出問題並改善 ${app_name}。為了了解人們如何使用多裝置,我們將會產生隨機識別字串,在您的裝置間共享。
 \n
 \n您可以閱讀我們的條款 %s。
     協助改善 ${app_name}
diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml
index 5f2f2dabb2f..a397b1d03f1 100644
--- a/vector/src/main/res/values/strings.xml
+++ b/vector/src/main/res/values/strings.xml
@@ -1387,9 +1387,8 @@
     Yes, I want to help!
 
     Help improve ${app_name}
-    
     
-    Help us identify issues and improve Element by sharing anonymous usage data. To understand how people use multiple devices, we’ll generate a random identifier, shared by your devices.\n\nYou can read all our terms %s.
+    Help us identify issues and improve ${app_name} by sharing anonymous usage data. To understand how people use multiple devices, we’ll generate a random identifier, shared by your devices.\n\nYou can read all our terms %s.
     here
     We don\'t record or profile any account data
     We don\'t share information with third parties

From fa66f1007581117cb33e6987b1a2cf6ff22c636a Mon Sep 17 00:00:00 2001
From: Benoit Marty 
Date: Tue, 22 Feb 2022 15:47:15 +0100
Subject: [PATCH 572/581] Create template with the app name

---
 vector/src/main/res/values-cs/strings.xml     | 2 +-
 vector/src/main/res/values-de/strings.xml     | 2 +-
 vector/src/main/res/values-et/strings.xml     | 2 +-
 vector/src/main/res/values-fa/strings.xml     | 2 +-
 vector/src/main/res/values-fr/strings.xml     | 2 +-
 vector/src/main/res/values-hu/strings.xml     | 2 +-
 vector/src/main/res/values-in/strings.xml     | 2 +-
 vector/src/main/res/values-it/strings.xml     | 2 +-
 vector/src/main/res/values-ja/strings.xml     | 2 +-
 vector/src/main/res/values-nl/strings.xml     | 2 +-
 vector/src/main/res/values-pl/strings.xml     | 2 +-
 vector/src/main/res/values-pt-rBR/strings.xml | 2 +-
 vector/src/main/res/values-ru/strings.xml     | 2 +-
 vector/src/main/res/values-sk/strings.xml     | 2 +-
 vector/src/main/res/values-sq/strings.xml     | 2 +-
 vector/src/main/res/values-sv/strings.xml     | 2 +-
 vector/src/main/res/values-tr/strings.xml     | 2 +-
 vector/src/main/res/values-uk/strings.xml     | 2 +-
 vector/src/main/res/values-vi/strings.xml     | 2 +-
 vector/src/main/res/values-zh-rCN/strings.xml | 2 +-
 vector/src/main/res/values-zh-rTW/strings.xml | 2 +-
 vector/src/main/res/values/strings.xml        | 3 +--
 22 files changed, 22 insertions(+), 23 deletions(-)

diff --git a/vector/src/main/res/values-cs/strings.xml b/vector/src/main/res/values-cs/strings.xml
index 8284a5de6a9..6fb17ebb006 100644
--- a/vector/src/main/res/values-cs/strings.xml
+++ b/vector/src/main/res/values-cs/strings.xml
@@ -3081,7 +3081,7 @@
     
     Systémová nastavení
     Verze
-    Získejte pomoc při používání Elementu
+    Získejte pomoc při používání ${app_name}u
     Nápověda a podpora
     Nápověda
     Právní dokumenty
diff --git a/vector/src/main/res/values-de/strings.xml b/vector/src/main/res/values-de/strings.xml
index 3236332085a..195b8311419 100644
--- a/vector/src/main/res/values-de/strings.xml
+++ b/vector/src/main/res/values-de/strings.xml
@@ -2985,7 +2985,7 @@
     Bedingungen des Identitätsservers anzeigen
     Systemeinstellungen
     Versionen
-    Erhalte Hilfe bei der Bedienung von Element
+    Erhalte Hilfe bei der Bedienung von ${app_name}
     Hilfe und Unterstützung
     Hilfe
     Rechtliches
diff --git a/vector/src/main/res/values-et/strings.xml b/vector/src/main/res/values-et/strings.xml
index bf486045be2..a9ac3c3bf55 100644
--- a/vector/src/main/res/values-et/strings.xml
+++ b/vector/src/main/res/values-et/strings.xml
@@ -3021,7 +3021,7 @@
     
     Süsteemiseadistused
     Versioonid
-    Element\'i kasutamiseks vajalik abiteave
+    ${app_name}\'i kasutamiseks vajalik abiteave
     Abiteave ja kasutajatugi
     Abiteave
     Juriidiline teave
diff --git a/vector/src/main/res/values-fa/strings.xml b/vector/src/main/res/values-fa/strings.xml
index c5e92e18f31..47af4f0a1cf 100644
--- a/vector/src/main/res/values-fa/strings.xml
+++ b/vector/src/main/res/values-fa/strings.xml
@@ -3025,7 +3025,7 @@
     
     تنظیمات سامانه
     نگارش‌ها
-    کمک در استفاده از المنت
+    کمک در استفاده از المنت
     کمک و پشتیانی
     کمک
     موارد حقوقی
diff --git a/vector/src/main/res/values-fr/strings.xml b/vector/src/main/res/values-fr/strings.xml
index ef55b09cc50..fdc20b2bbd7 100644
--- a/vector/src/main/res/values-fr/strings.xml
+++ b/vector/src/main/res/values-fr/strings.xml
@@ -3023,7 +3023,7 @@
     
     Paramètres système
     Versions
-    Obtenir de l’aide pour utiliser Element
+    Obtenir de l’aide pour utiliser ${app_name}
     Aide et support
     Aide
     Mentions légales
diff --git a/vector/src/main/res/values-hu/strings.xml b/vector/src/main/res/values-hu/strings.xml
index 574386c0b44..01ddffed6c9 100644
--- a/vector/src/main/res/values-hu/strings.xml
+++ b/vector/src/main/res/values-hu/strings.xml
@@ -3019,7 +3019,7 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze
     
     Rendszerbeállítások
     Verziók
-    Segítség az Element használatában
+    Segítség az ${app_name} használatában
     Segítség és támogatás
     Segítség
     Ez a szerver nem adott meg szabályzatot.
diff --git a/vector/src/main/res/values-in/strings.xml b/vector/src/main/res/values-in/strings.xml
index c4c20b3a241..e99c91037fd 100644
--- a/vector/src/main/res/values-in/strings.xml
+++ b/vector/src/main/res/values-in/strings.xml
@@ -2967,7 +2967,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.
     
     Pengaturan sistem
     Versi
-    Dapatkan bantuan dalam menggunakan Element
+    Dapatkan bantuan dalam menggunakan ${app_name}
     Bantuan dan dukungan
     Bantuan
     Hukum
diff --git a/vector/src/main/res/values-it/strings.xml b/vector/src/main/res/values-it/strings.xml
index b5b0c09b624..d0645c1dfdf 100644
--- a/vector/src/main/res/values-it/strings.xml
+++ b/vector/src/main/res/values-it/strings.xml
@@ -3012,7 +3012,7 @@
     
     Impostazioni di sistema
     Versioni
-    Ricevi aiuto nell\'uso di Element
+    Ricevi aiuto nell\'uso di ${app_name}
     Aiuto e supporto
     Aiuto
     Informazioni legali
diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 64bf6859e78..0b6fcfded60 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -2508,7 +2508,7 @@
     お宅での対面会話と同じぐらいのプライバシーを提供する、セキュアで独立したコミュニケーション。
     セキュアメッセージング
     管理権を握るのは、あなたです。
-    Elementの使用に関するヘルプ
+    ${app_name}の使用に関するヘルプ
     詳細なログは、イライラシェイクでログを送信する際に、より多くのログを提供することで、開発者にとっての助けになります。有効にした場合でも、メッセージの内容やその他のプライベートな情報は記録されません。
     ルームのアップグレードは高度な作業であり、不具合や欠けている機能、セキュリティー上の脆弱性がある場合に推奨されます。
 \nアップグレードは通常、ルームがサーバー上で処理される仕方にだけ影響します。
diff --git a/vector/src/main/res/values-nl/strings.xml b/vector/src/main/res/values-nl/strings.xml
index d3cd933e161..7f586c1cb6c 100644
--- a/vector/src/main/res/values-nl/strings.xml
+++ b/vector/src/main/res/values-nl/strings.xml
@@ -2126,7 +2126,7 @@
     Doe een suggestie
     Systeem instellingen
     Versies
-    Hulp bij het gebruik van Element
+    Hulp bij het gebruik van ${app_name}
     Hulp en ondersteuning
     Hulp
     Juridisch
diff --git a/vector/src/main/res/values-pl/strings.xml b/vector/src/main/res/values-pl/strings.xml
index 26d01b81caf..2925d00a359 100644
--- a/vector/src/main/res/values-pl/strings.xml
+++ b/vector/src/main/res/values-pl/strings.xml
@@ -3061,7 +3061,7 @@
     Ty jesteś w kontroli.
     Przejmij swoje konwersacje.
     By odkryć istniejące kontakty, musisz najpierw przesłać swoje dane kontaktowe (adresy e-mail i numer telefonu) do serwera tożsamości. Przed wysłaniem Twoje dane zostaną zaszyfrowane w celu zachowania prywatności.
-    Uzyskaj pomoc w korzystaniu z Element
+    Uzyskaj pomoc w korzystaniu z ${app_name}
     Nie masz uprawnień by dołączyć do tego pokoju
     Typ
     Niedostępny
diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/vector/src/main/res/values-pt-rBR/strings.xml
index d3fdd8ee051..ee332d3aa8d 100644
--- a/vector/src/main/res/values-pt-rBR/strings.xml
+++ b/vector/src/main/res/values-pt-rBR/strings.xml
@@ -3021,7 +3021,7 @@
         %1$d votos
     
     Versões
-    Conseguir ajuda com uso de Element
+    Conseguir ajuda com uso de ${app_name}
     Jurídicos
     Este servidor não provê nenhuma política.
     Bibliotecas de terceiros
diff --git a/vector/src/main/res/values-ru/strings.xml b/vector/src/main/res/values-ru/strings.xml
index 4ab6e3a75f2..d71debde63c 100644
--- a/vector/src/main/res/values-ru/strings.xml
+++ b/vector/src/main/res/values-ru/strings.xml
@@ -3160,7 +3160,7 @@
     Ваши контакты приватны. Чтобы обнаружить пользователей из ваших контактов, нам необходимо ваше разрешение на отправку контактной информации на ваш сервер обнаружения.
     Системные настройки
     Версии
-    Получите помощь в использовании Element
+    Получите помощь в использовании ${app_name}
     Помощь и поддержка
     Помощь
     Правовые положения
diff --git a/vector/src/main/res/values-sk/strings.xml b/vector/src/main/res/values-sk/strings.xml
index 89f05c0360e..4d253b61e2e 100644
--- a/vector/src/main/res/values-sk/strings.xml
+++ b/vector/src/main/res/values-sk/strings.xml
@@ -2508,7 +2508,7 @@
     Nepoznáte svoju prístupovú frázu pre zálohovanie kľúčov, môžete %s.
     Overte tohto používateľa potvrdením, že sa na jeho obrazovke zobrazujú nasledujúce jedinečné emotikony v rovnakom poradí.
     Zobrazenie niektorých užitočných informácií na pomoc pri ladení aplikácie
-    Získajte pomoc s používaním aplikácie Element
+    Získajte pomoc s používaním aplikácie ${app_name}
     Všetky miestnosti, v ktorých sa nachádzate, sa zobrazia v časti Domov.
     Zobraziť všetky miestnosti v časti Domov
     Riešenie problémov
diff --git a/vector/src/main/res/values-sq/strings.xml b/vector/src/main/res/values-sq/strings.xml
index d6c796cfa5c..f86b0515c7f 100644
--- a/vector/src/main/res/values-sq/strings.xml
+++ b/vector/src/main/res/values-sq/strings.xml
@@ -3006,7 +3006,7 @@
     Pyetësor
     Rregullime sistemi
     Versione
-    Merrni ndihmë për përdorimin e Element-it
+    Merrni ndihmë për përdorimin e ${app_name}-it
     Ndihmë dhe asistencë
     Ndihmë
     Ligjore
diff --git a/vector/src/main/res/values-sv/strings.xml b/vector/src/main/res/values-sv/strings.xml
index fcdbbc481a4..b5f0313e36d 100644
--- a/vector/src/main/res/values-sv/strings.xml
+++ b/vector/src/main/res/values-sv/strings.xml
@@ -3027,7 +3027,7 @@
     
     Systeminställningar
     Versioner
-    Få hjälp med att använda Element
+    Få hjälp med att använda ${app_name}
     Hjälp och support
     Hjälp
     Legalt
diff --git a/vector/src/main/res/values-tr/strings.xml b/vector/src/main/res/values-tr/strings.xml
index 88928e86c34..e27861035a0 100644
--- a/vector/src/main/res/values-tr/strings.xml
+++ b/vector/src/main/res/values-tr/strings.xml
@@ -2306,7 +2306,7 @@
     Kayıt belirteci
     Sistem ayarları
     Sürümler
-    Element\'i kullanma konusunda yardım alın
+    ${app_name}\'i kullanma konusunda yardım alın
     Yardım ve Destek
     Yardım
     Yasal
diff --git a/vector/src/main/res/values-uk/strings.xml b/vector/src/main/res/values-uk/strings.xml
index 4d92411188e..f392c6fd9a6 100644
--- a/vector/src/main/res/values-uk/strings.xml
+++ b/vector/src/main/res/values-uk/strings.xml
@@ -3124,7 +3124,7 @@
     
     Системні налаштування
     Версії
-    Отримати допомогу в використанні Element
+    Отримати допомогу в використанні ${app_name}
     Довідка й підтримка
     Довідка
     Правові положення
diff --git a/vector/src/main/res/values-vi/strings.xml b/vector/src/main/res/values-vi/strings.xml
index 99aa2361788..24156706ef4 100644
--- a/vector/src/main/res/values-vi/strings.xml
+++ b/vector/src/main/res/values-vi/strings.xml
@@ -2962,7 +2962,7 @@
     Token đăng ký
     Cài đặt hệ thống
     Phiên bản
-    Nhận trợ giúp về việc sử dụng Element
+    Nhận trợ giúp về việc sử dụng ${app_name}
     Trợ giúp và hỗ trợ
     Trợ giúp
     Pháp lý
diff --git a/vector/src/main/res/values-zh-rCN/strings.xml b/vector/src/main/res/values-zh-rCN/strings.xml
index e3339807101..27d9e805e13 100644
--- a/vector/src/main/res/values-zh-rCN/strings.xml
+++ b/vector/src/main/res/values-zh-rCN/strings.xml
@@ -2963,7 +2963,7 @@
     
     系统设置
     版本
-    获取使用 Element 的帮助
+    获取使用 ${app_name} 的帮助
     帮助和支持
     帮助
     法律
diff --git a/vector/src/main/res/values-zh-rTW/strings.xml b/vector/src/main/res/values-zh-rTW/strings.xml
index 0a0f38ec665..b40f4fd8935 100644
--- a/vector/src/main/res/values-zh-rTW/strings.xml
+++ b/vector/src/main/res/values-zh-rTW/strings.xml
@@ -2963,7 +2963,7 @@
     
     系統設定
     版本
-    取得關於使用 Element 的協助
+    取得關於使用 ${app_name} 的協助
     說明與支援
     說明
     法律
diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml
index a397b1d03f1..ec0ba07c264 100644
--- a/vector/src/main/res/values/strings.xml
+++ b/vector/src/main/res/values/strings.xml
@@ -2296,8 +2296,7 @@
 
     Help
     Help and support
-    
-    Get help with using Element
+    Get help with using ${app_name}
     Versions
     System settings
 

From a9702c63d2594075bb4140cc407155703a3886ba Mon Sep 17 00:00:00 2001
From: Benoit Marty 
Date: Tue, 22 Feb 2022 16:00:40 +0100
Subject: [PATCH 573/581] Fix lint issue

---
 vector/src/main/res/values-ja/strings.xml | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml
index 0b6fcfded60..a110a7f379b 100644
--- a/vector/src/main/res/values-ja/strings.xml
+++ b/vector/src/main/res/values-ja/strings.xml
@@ -1409,8 +1409,6 @@
     %1$s、%2$s、%3$sと%4$s
     %1$s、%2$sと%3$s
     %1$sの権限レベルを%2$sから%3$sへ変更しました。
-    %1$sが
-    あなたは
     カスタム
     カスタム (%1$d)
     デフォルト
@@ -2842,7 +2840,7 @@
     新しいログイン。あなたですか?
     ${app_name} Android
     部屋の管理者によって削除されています、理由:%1$s
-    ユーザーによって削除されています、理由:
+    ユーザーによって削除されています、理由:%1$s
     削除した理由
     この添付ファイルを%1$sに送信しますか?
     秘密ストレージは信頼されている端末でのみアクセスしましょう

From 17fa463bc8146203b76f3092831fdf2d46f749c2 Mon Sep 17 00:00:00 2001
From: Benoit Marty 
Date: Tue, 22 Feb 2022 16:05:40 +0100
Subject: [PATCH 574/581] Delete unused resource

---
 .../main/res/layout/dialog_event_content.xml   | 18 ------------------
 1 file changed, 18 deletions(-)
 delete mode 100644 vector/src/main/res/layout/dialog_event_content.xml

diff --git a/vector/src/main/res/layout/dialog_event_content.xml b/vector/src/main/res/layout/dialog_event_content.xml
deleted file mode 100644
index d3c52a61eda..00000000000
--- a/vector/src/main/res/layout/dialog_event_content.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-    
-
-
\ No newline at end of file

From 1ce65d7f87a4dbf76b956234868fce4f8a49a30f Mon Sep 17 00:00:00 2001
From: Benoit Marty 
Date: Tue, 22 Feb 2022 16:08:08 +0100
Subject: [PATCH 575/581] Use plurals for message_reaction_show_more. Fixes
 #5227

---
 .../home/room/detail/timeline/item/AbsBaseMessageItem.kt     | 2 +-
 vector/src/main/res/values/strings.xml                       | 5 ++++-
 2 files changed, 5 insertions(+), 2 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt
index 23f2aceff54..430e0970bbe 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt
@@ -124,7 +124,7 @@ abstract class AbsBaseMessageItem : BaseEventItem
                     showReactionsTextView.onClick { reactionsSummary.onShowLessClicked() }
                 } else {
                     val moreCount = reactions.count() - MAX_REACTIONS_TO_SHOW
-                    showReactionsTextView.text = holder.view.resources.getString(R.string.message_reaction_show_more, moreCount)
+                    showReactionsTextView.text = holder.view.resources.getQuantityString(R.plurals.message_reaction_show_more, moreCount, moreCount)
                     showReactionsTextView.onClick { reactionsSummary.onShowMoreClicked() }
                 }
                 holder.reactionsContainer.addView(showReactionsTextView)
diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml
index ec0ba07c264..800d1092f82 100644
--- a/vector/src/main/res/values/strings.xml
+++ b/vector/src/main/res/values/strings.xml
@@ -3761,7 +3761,10 @@
     Share location
 
     Show less
-    "%1$d more"
+    
+        "%1$d more"
+        "%1$d more"
+    
 
     Notify the whole room
     Users

From 21fa0267f6b0e2a823e0e85bc754bdc755211cf2 Mon Sep 17 00:00:00 2001
From: Maxime Naturel 
Date: Tue, 22 Feb 2022 17:44:59 +0100
Subject: [PATCH 576/581] Removing TODOs

---
 .../java/im/vector/app/features/login/LoginWebFragment.kt     | 2 --
 .../java/im/vector/app/features/login2/LoginWebFragment2.kt   | 4 +---
 2 files changed, 1 insertion(+), 5 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/login/LoginWebFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginWebFragment.kt
index f1558518f72..bb73ed79b25 100644
--- a/vector/src/main/java/im/vector/app/features/login/LoginWebFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/login/LoginWebFragment.kt
@@ -51,8 +51,6 @@ class LoginWebFragment @Inject constructor(
         private val assetReader: AssetReader
 ) : AbstractLoginFragment() {
 
-    // TODO I noticed in the Git history this variable was a local variable inside notifyViewModel method
-    // TODO was there any reason ? To be able to create this ViewModel we must be sure we will have a valid session
     private val softLogoutViewModel: SoftLogoutViewModel by activityViewModel()
 
     override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginWebBinding {
diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginWebFragment2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginWebFragment2.kt
index 4e28d8e56c7..4427f08309e 100644
--- a/vector/src/main/java/im/vector/app/features/login2/LoginWebFragment2.kt
+++ b/vector/src/main/java/im/vector/app/features/login2/LoginWebFragment2.kt
@@ -56,8 +56,6 @@ class LoginWebFragment2 @Inject constructor(
         return FragmentLoginWebBinding.inflate(inflater, container, false)
     }
 
-    // TODO I noticed in the Git history this variable was a local variable inside notifyViewModel method
-    // TODO was there any reason ? To be able to create this ViewModel we must be sure we will have a valid session
     private val softLogoutViewModel: SoftLogoutViewModel by activityViewModel()
 
     private var isWebViewLoaded = false
@@ -84,7 +82,7 @@ class LoginWebFragment2 @Inject constructor(
     private fun setupTitle(state: LoginViewState2) {
         toolbar?.title = when (state.signMode) {
             SignMode2.SignIn -> getString(R.string.login_signin)
-            else            -> getString(R.string.login_signup)
+            else             -> getString(R.string.login_signup)
         }
     }
 

From ed80fe517d38f72989613d64b4269fd814359485 Mon Sep 17 00:00:00 2001
From: Benoit Marty 
Date: Tue, 22 Feb 2022 21:06:41 +0100
Subject: [PATCH 577/581] Revert recent change to fix a crash
 `readReceiptsSummaryEntity.realm` is null

---
 .../mapper/ReadReceiptsSummaryMapper.kt       | 19 ++++++++++++-------
 1 file changed, 12 insertions(+), 7 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/ReadReceiptsSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/ReadReceiptsSummaryMapper.kt
index 5aaa49b9e82..f3770e4afee 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/ReadReceiptsSummaryMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/ReadReceiptsSummaryMapper.kt
@@ -23,18 +23,23 @@ import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity
 import org.matrix.android.sdk.internal.database.query.where
 import javax.inject.Inject
 
-internal class ReadReceiptsSummaryMapper @Inject constructor(private val realmSessionProvider: RealmSessionProvider) {
+internal class ReadReceiptsSummaryMapper @Inject constructor(
+        private val realmSessionProvider: RealmSessionProvider
+) {
 
     fun map(readReceiptsSummaryEntity: ReadReceiptsSummaryEntity?): List {
         if (readReceiptsSummaryEntity == null) {
             return emptyList()
         }
         val readReceipts = readReceiptsSummaryEntity.readReceipts
-        return readReceipts
-                .mapNotNull {
-                    val roomMember = RoomMemberSummaryEntity.where(readReceiptsSummaryEntity.realm, roomId = it.roomId, userId = it.userId).findFirst()
-                            ?: return@mapNotNull null
-                    ReadReceipt(roomMember.asDomain(), it.originServerTs.toLong())
-                }
+
+        return realmSessionProvider.withRealm { realm ->
+            readReceipts
+                    .mapNotNull {
+                        val roomMember = RoomMemberSummaryEntity.where(realm, roomId = it.roomId, userId = it.userId).findFirst()
+                                ?: return@mapNotNull null
+                        ReadReceipt(roomMember.asDomain(), it.originServerTs.toLong())
+                    }
+        }
     }
 }

From 6fd14c9ebc85d07fd8d1b9dd558027dce4fefbbf Mon Sep 17 00:00:00 2001
From: Benoit Marty 
Date: Tue, 22 Feb 2022 21:21:24 +0100
Subject: [PATCH 578/581] Typo in the filename

---
 changelog.d/{5198.buxfix => 5198.bugfix} | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename changelog.d/{5198.buxfix => 5198.bugfix} (100%)

diff --git a/changelog.d/5198.buxfix b/changelog.d/5198.bugfix
similarity index 100%
rename from changelog.d/5198.buxfix
rename to changelog.d/5198.bugfix

From b746321bad42eab53eb2126cddddc3d0f3938912 Mon Sep 17 00:00:00 2001
From: Benoit Marty 
Date: Tue, 22 Feb 2022 21:21:40 +0100
Subject: [PATCH 579/581] Towncrier

---
 CHANGES.md               | 44 ++++++++++++++++++++++++++++++++++++++++
 changelog.d/2782.misc    |  1 -
 changelog.d/3771.feature |  1 -
 changelog.d/4640.bugfix  |  1 -
 changelog.d/4643.misc    |  1 -
 changelog.d/5104.misc    |  1 -
 changelog.d/5123.feature |  1 -
 changelog.d/5136.misc    |  1 -
 changelog.d/5178.bugfix  |  1 -
 changelog.d/5183.sdk     |  1 -
 changelog.d/5185.sdk     |  1 -
 changelog.d/5195.bugfix  |  1 -
 changelog.d/5198.bugfix  |  1 -
 changelog.d/5201.bugfix  |  1 -
 changelog.d/5204.feature |  1 -
 changelog.d/5207.sdk     |  1 -
 changelog.d/5209.misc    |  1 -
 changelog.d/5210.misc    |  1 -
 changelog.d/5218.bugfix  |  1 -
 changelog.d/5225.misc    |  1 -
 changelog.d/5234.bugfix  |  1 -
 changelog.d/5243.bugfix  |  1 -
 changelog.d/5254.misc    |  1 -
 changelog.d/5256.misc    |  1 -
 changelog.d/5276.misc    |  1 -
 changelog.d/5290.feature |  1 -
 changelog.d/5294.misc    |  1 -
 changelog.d/5295.bugfix  |  1 -
 changelog.d/5297.misc    |  1 -
 29 files changed, 44 insertions(+), 28 deletions(-)
 delete mode 100644 changelog.d/2782.misc
 delete mode 100644 changelog.d/3771.feature
 delete mode 100644 changelog.d/4640.bugfix
 delete mode 100644 changelog.d/4643.misc
 delete mode 100644 changelog.d/5104.misc
 delete mode 100644 changelog.d/5123.feature
 delete mode 100644 changelog.d/5136.misc
 delete mode 100644 changelog.d/5178.bugfix
 delete mode 100644 changelog.d/5183.sdk
 delete mode 100644 changelog.d/5185.sdk
 delete mode 100644 changelog.d/5195.bugfix
 delete mode 100644 changelog.d/5198.bugfix
 delete mode 100644 changelog.d/5201.bugfix
 delete mode 100644 changelog.d/5204.feature
 delete mode 100644 changelog.d/5207.sdk
 delete mode 100644 changelog.d/5209.misc
 delete mode 100644 changelog.d/5210.misc
 delete mode 100644 changelog.d/5218.bugfix
 delete mode 100644 changelog.d/5225.misc
 delete mode 100644 changelog.d/5234.bugfix
 delete mode 100644 changelog.d/5243.bugfix
 delete mode 100644 changelog.d/5254.misc
 delete mode 100644 changelog.d/5256.misc
 delete mode 100644 changelog.d/5276.misc
 delete mode 100644 changelog.d/5290.feature
 delete mode 100644 changelog.d/5294.misc
 delete mode 100644 changelog.d/5295.bugfix
 delete mode 100644 changelog.d/5297.misc

diff --git a/CHANGES.md b/CHANGES.md
index 3e0c5ff4aef..5ea38591774 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,3 +1,47 @@
+Changes in Element v1.4.2 (2022-02-22)
+======================================
+
+Features ✨
+----------
+ - Open the room when user accepts an invite from the room list ([#3771](https://github.com/vector-im/element-android/issues/3771))
+ - Add completion for @room to notify everyone in a room ([#5123](https://github.com/vector-im/element-android/issues/5123))
+ - Improve UI of reactions in timeline, including quick add reaction. ([#5204](https://github.com/vector-im/element-android/issues/5204))
+ - Support creating disclosed polls ([#5290](https://github.com/vector-im/element-android/issues/5290))
+
+Bugfixes 🐛
+----------
+ - Right align the notifications badge in the rooms list (and DMs) so that it's always in a consistent place on the screen. ([#4640](https://github.com/vector-im/element-android/issues/4640))
+ - Remove redundant highlight on add poll option button ([#5178](https://github.com/vector-im/element-android/issues/5178))
+ - Reliably display crash report prompt ([#5195](https://github.com/vector-im/element-android/issues/5195))
+ - Fix for rooms with virtual rooms not showing call status events in the timeline. ([#5198](https://github.com/vector-im/element-android/issues/5198))
+ - Fix for call transfer with consult failing to make outgoing consultation call. ([#5201](https://github.com/vector-im/element-android/issues/5201))
+ - Fix crash during account registration when redirecting to Web View ([#5218](https://github.com/vector-im/element-android/issues/5218))
+ - Analytics: Fixes missing use case identity values from within the onboarding flow ([#5234](https://github.com/vector-im/element-android/issues/5234))
+ - Increments database schema to take advantage of homeserver capabilities entity migration (fixes crash in pre-release builds) ([#5243](https://github.com/vector-im/element-android/issues/5243))
+ - Fixing crash when adding room by QR code after accepting the camera permission for the first time ([#5295](https://github.com/vector-im/element-android/issues/5295))
+
+SDK API changes ⚠️
+------------------
+ - `join` and `leave` methods moved from MembershipService to RoomService and SpaceService to split logic for rooms and spaces ([#5183](https://github.com/vector-im/element-android/issues/5183))
+ - Deprecates Matrix.initialize and Matrix.getInstance in favour of the client providing its own singleton instance via Matrix.createInstance ([#5185](https://github.com/vector-im/element-android/issues/5185))
+ - Adds support for MSC3283, additional homeserver capabilities ([#5207](https://github.com/vector-im/element-android/issues/5207))
+
+Other changes
+-------------
+ - Collapse successive ACLs events in room timeline ([#2782](https://github.com/vector-im/element-android/issues/2782))
+ - Home screen: Replacing search icon by filter icon in the top right menu ([#4643](https://github.com/vector-im/element-android/issues/4643))
+ - Make Space creation screens more consistent ([#5104](https://github.com/vector-im/element-android/issues/5104))
+ - Defensive coding to ensure encryption when room was once e2e ([#5136](https://github.com/vector-im/element-android/issues/5136))
+ - Reduce verbosity of debug logging, ([#5209](https://github.com/vector-im/element-android/issues/5209))
+ - Standardise emulator versions of GHA integration tests. ([#5210](https://github.com/vector-im/element-android/issues/5210))
+ - Replacing color "vctr_unread_room_badge" by "vctr_content_secondary" ([#5225](https://github.com/vector-im/element-android/issues/5225))
+ - Change preferred jitsi domain from `jitsi.riot.im` to `meet.element.io` ([#5254](https://github.com/vector-im/element-android/issues/5254))
+ - Analytics screen events are now tracked on screen enter instead of screen leave ([#5256](https://github.com/vector-im/element-android/issues/5256))
+ - Improves bitmap memory usage by caching the shortcut images ([#5276](https://github.com/vector-im/element-android/issues/5276))
+ - Changes unread marker in room list from green to grey ([#5294](https://github.com/vector-im/element-android/issues/5294))
+ - Improve some internal realm usages. ([#5297](https://github.com/vector-im/element-android/issues/5297))
+
+
 Changes in Element v1.4.0 (2022-02-09)
 ======================================
 
diff --git a/changelog.d/2782.misc b/changelog.d/2782.misc
deleted file mode 100644
index dc200503692..00000000000
--- a/changelog.d/2782.misc
+++ /dev/null
@@ -1 +0,0 @@
-Collapse successive ACLs events in room timeline
diff --git a/changelog.d/3771.feature b/changelog.d/3771.feature
deleted file mode 100644
index c480bb649d7..00000000000
--- a/changelog.d/3771.feature
+++ /dev/null
@@ -1 +0,0 @@
-Open the room when user accepts an invite from the room list
\ No newline at end of file
diff --git a/changelog.d/4640.bugfix b/changelog.d/4640.bugfix
deleted file mode 100644
index f5fa5a5bde8..00000000000
--- a/changelog.d/4640.bugfix
+++ /dev/null
@@ -1 +0,0 @@
-Right align the notifications badge in the rooms list (and DMs) so that it's always in a consistent place on the screen.
\ No newline at end of file
diff --git a/changelog.d/4643.misc b/changelog.d/4643.misc
deleted file mode 100644
index 3d86baa1a2d..00000000000
--- a/changelog.d/4643.misc
+++ /dev/null
@@ -1 +0,0 @@
-Home screen: Replacing search icon by filter icon in the top right menu
diff --git a/changelog.d/5104.misc b/changelog.d/5104.misc
deleted file mode 100644
index 673614955be..00000000000
--- a/changelog.d/5104.misc
+++ /dev/null
@@ -1 +0,0 @@
-Make Space creation screens more consistent
diff --git a/changelog.d/5123.feature b/changelog.d/5123.feature
deleted file mode 100644
index cb1a7adf083..00000000000
--- a/changelog.d/5123.feature
+++ /dev/null
@@ -1 +0,0 @@
-Add completion for @room to notify everyone in a room
diff --git a/changelog.d/5136.misc b/changelog.d/5136.misc
deleted file mode 100644
index 43404acc31d..00000000000
--- a/changelog.d/5136.misc
+++ /dev/null
@@ -1 +0,0 @@
-Defensive coding to ensure encryption when room was once e2e
\ No newline at end of file
diff --git a/changelog.d/5178.bugfix b/changelog.d/5178.bugfix
deleted file mode 100644
index 73021a04856..00000000000
--- a/changelog.d/5178.bugfix
+++ /dev/null
@@ -1 +0,0 @@
-Remove redundant highlight on add poll option button
\ No newline at end of file
diff --git a/changelog.d/5183.sdk b/changelog.d/5183.sdk
deleted file mode 100644
index 66d2c3793d7..00000000000
--- a/changelog.d/5183.sdk
+++ /dev/null
@@ -1 +0,0 @@
-`join` and `leave` methods moved from MembershipService to RoomService and SpaceService to split logic for rooms and spaces
\ No newline at end of file
diff --git a/changelog.d/5185.sdk b/changelog.d/5185.sdk
deleted file mode 100644
index 9eda2e7c9b8..00000000000
--- a/changelog.d/5185.sdk
+++ /dev/null
@@ -1 +0,0 @@
-Deprecates Matrix.initialize and Matrix.getInstance in favour of the client providing its own singleton instance via Matrix.createInstance
\ No newline at end of file
diff --git a/changelog.d/5195.bugfix b/changelog.d/5195.bugfix
deleted file mode 100644
index 50d47e089eb..00000000000
--- a/changelog.d/5195.bugfix
+++ /dev/null
@@ -1 +0,0 @@
-Reliably display crash report prompt
diff --git a/changelog.d/5198.bugfix b/changelog.d/5198.bugfix
deleted file mode 100644
index 3fce6906d50..00000000000
--- a/changelog.d/5198.bugfix
+++ /dev/null
@@ -1 +0,0 @@
-Fix for rooms with virtual rooms not showing call status events in the timeline.
\ No newline at end of file
diff --git a/changelog.d/5201.bugfix b/changelog.d/5201.bugfix
deleted file mode 100644
index f77ddcce847..00000000000
--- a/changelog.d/5201.bugfix
+++ /dev/null
@@ -1 +0,0 @@
-Fix for call transfer with consult failing to make outgoing consultation call.
\ No newline at end of file
diff --git a/changelog.d/5204.feature b/changelog.d/5204.feature
deleted file mode 100644
index a107342de7d..00000000000
--- a/changelog.d/5204.feature
+++ /dev/null
@@ -1 +0,0 @@
-Improve UI of reactions in timeline, including quick add reaction.
\ No newline at end of file
diff --git a/changelog.d/5207.sdk b/changelog.d/5207.sdk
deleted file mode 100644
index 3ba3e06fb76..00000000000
--- a/changelog.d/5207.sdk
+++ /dev/null
@@ -1 +0,0 @@
-Adds support for MSC3283, additional homeserver capabilities
\ No newline at end of file
diff --git a/changelog.d/5209.misc b/changelog.d/5209.misc
deleted file mode 100644
index a238da9d223..00000000000
--- a/changelog.d/5209.misc
+++ /dev/null
@@ -1 +0,0 @@
-Reduce verbosity of debug logging,
diff --git a/changelog.d/5210.misc b/changelog.d/5210.misc
deleted file mode 100644
index 0b68e8b23a9..00000000000
--- a/changelog.d/5210.misc
+++ /dev/null
@@ -1 +0,0 @@
-Standardise emulator versions of GHA integration tests.
diff --git a/changelog.d/5218.bugfix b/changelog.d/5218.bugfix
deleted file mode 100644
index 4f92338a4f4..00000000000
--- a/changelog.d/5218.bugfix
+++ /dev/null
@@ -1 +0,0 @@
-Fix crash during account registration when redirecting to Web View
diff --git a/changelog.d/5225.misc b/changelog.d/5225.misc
deleted file mode 100644
index 799a3a4d814..00000000000
--- a/changelog.d/5225.misc
+++ /dev/null
@@ -1 +0,0 @@
-Replacing color "vctr_unread_room_badge" by "vctr_content_secondary"
diff --git a/changelog.d/5234.bugfix b/changelog.d/5234.bugfix
deleted file mode 100644
index 2b5d4dee371..00000000000
--- a/changelog.d/5234.bugfix
+++ /dev/null
@@ -1 +0,0 @@
-Analytics: Fixes missing use case identity values from within the onboarding flow
\ No newline at end of file
diff --git a/changelog.d/5243.bugfix b/changelog.d/5243.bugfix
deleted file mode 100644
index eb323c1ca40..00000000000
--- a/changelog.d/5243.bugfix
+++ /dev/null
@@ -1 +0,0 @@
-Increments database schema to take advantage of homeserver capabilities entity migration (fixes crash in pre-release builds)
\ No newline at end of file
diff --git a/changelog.d/5254.misc b/changelog.d/5254.misc
deleted file mode 100644
index 2ae642e9b74..00000000000
--- a/changelog.d/5254.misc
+++ /dev/null
@@ -1 +0,0 @@
-Change preferred jitsi domain from `jitsi.riot.im` to `meet.element.io`
\ No newline at end of file
diff --git a/changelog.d/5256.misc b/changelog.d/5256.misc
deleted file mode 100644
index e20f52c7aa5..00000000000
--- a/changelog.d/5256.misc
+++ /dev/null
@@ -1 +0,0 @@
-Analytics screen events are now tracked on screen enter instead of screen leave
\ No newline at end of file
diff --git a/changelog.d/5276.misc b/changelog.d/5276.misc
deleted file mode 100644
index 437bd28eb6b..00000000000
--- a/changelog.d/5276.misc
+++ /dev/null
@@ -1 +0,0 @@
-Improves bitmap memory usage by caching the shortcut images
\ No newline at end of file
diff --git a/changelog.d/5290.feature b/changelog.d/5290.feature
deleted file mode 100644
index 6f7e9aea7fd..00000000000
--- a/changelog.d/5290.feature
+++ /dev/null
@@ -1 +0,0 @@
-Support creating disclosed polls
\ No newline at end of file
diff --git a/changelog.d/5294.misc b/changelog.d/5294.misc
deleted file mode 100644
index 857110efab8..00000000000
--- a/changelog.d/5294.misc
+++ /dev/null
@@ -1 +0,0 @@
-Changes unread marker in room list from green to grey
\ No newline at end of file
diff --git a/changelog.d/5295.bugfix b/changelog.d/5295.bugfix
deleted file mode 100644
index c5e55cde5d0..00000000000
--- a/changelog.d/5295.bugfix
+++ /dev/null
@@ -1 +0,0 @@
-Fixing crash when adding room by QR code after accepting the camera permission for the first time
\ No newline at end of file
diff --git a/changelog.d/5297.misc b/changelog.d/5297.misc
deleted file mode 100644
index f45490ce3dd..00000000000
--- a/changelog.d/5297.misc
+++ /dev/null
@@ -1 +0,0 @@
-Improve some internal realm usages.
\ No newline at end of file

From 70bb1004c146cceadb384c63747cb7fe75991c8b Mon Sep 17 00:00:00 2001
From: Benoit Marty 
Date: Tue, 22 Feb 2022 21:27:06 +0100
Subject: [PATCH 580/581] Tidy up the changelog

---
 CHANGES.md | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index 5ea38591774..255792f800c 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -10,14 +10,12 @@ Features ✨
 
 Bugfixes 🐛
 ----------
- - Right align the notifications badge in the rooms list (and DMs) so that it's always in a consistent place on the screen. ([#4640](https://github.com/vector-im/element-android/issues/4640))
  - Remove redundant highlight on add poll option button ([#5178](https://github.com/vector-im/element-android/issues/5178))
  - Reliably display crash report prompt ([#5195](https://github.com/vector-im/element-android/issues/5195))
  - Fix for rooms with virtual rooms not showing call status events in the timeline. ([#5198](https://github.com/vector-im/element-android/issues/5198))
  - Fix for call transfer with consult failing to make outgoing consultation call. ([#5201](https://github.com/vector-im/element-android/issues/5201))
  - Fix crash during account registration when redirecting to Web View ([#5218](https://github.com/vector-im/element-android/issues/5218))
  - Analytics: Fixes missing use case identity values from within the onboarding flow ([#5234](https://github.com/vector-im/element-android/issues/5234))
- - Increments database schema to take advantage of homeserver capabilities entity migration (fixes crash in pre-release builds) ([#5243](https://github.com/vector-im/element-android/issues/5243))
  - Fixing crash when adding room by QR code after accepting the camera permission for the first time ([#5295](https://github.com/vector-im/element-android/issues/5295))
 
 SDK API changes ⚠️
@@ -28,6 +26,7 @@ SDK API changes ⚠️
 
 Other changes
 -------------
+ - Right align the notifications badge in the rooms list (and DMs) so that it's always in a consistent place on the screen. ([#4640](https://github.com/vector-im/element-android/issues/4640))
  - Collapse successive ACLs events in room timeline ([#2782](https://github.com/vector-im/element-android/issues/2782))
  - Home screen: Replacing search icon by filter icon in the top right menu ([#4643](https://github.com/vector-im/element-android/issues/4643))
  - Make Space creation screens more consistent ([#5104](https://github.com/vector-im/element-android/issues/5104))
@@ -41,6 +40,10 @@ Other changes
  - Changes unread marker in room list from green to grey ([#5294](https://github.com/vector-im/element-android/issues/5294))
  - Improve some internal realm usages. ([#5297](https://github.com/vector-im/element-android/issues/5297))
 
+Translations 🗣
+--------------
+ - Improved Japanese translations (special thanks to Suguru Hirahara!)
+
 
 Changes in Element v1.4.0 (2022-02-09)
 ======================================

From bb0955f809a3928fbda2d7a814d84472b2aaedcf Mon Sep 17 00:00:00 2001
From: Benoit Marty 
Date: Tue, 22 Feb 2022 21:29:16 +0100
Subject: [PATCH 581/581] change for fastlane

---
 fastlane/metadata/android/en-US/changelogs/40104000.txt | 2 +-
 fastlane/metadata/android/en-US/changelogs/40104020.txt | 2 ++
 2 files changed, 3 insertions(+), 1 deletion(-)
 create mode 100644 fastlane/metadata/android/en-US/changelogs/40104020.txt

diff --git a/fastlane/metadata/android/en-US/changelogs/40104000.txt b/fastlane/metadata/android/en-US/changelogs/40104000.txt
index e102edbaad5..4492b788829 100644
--- a/fastlane/metadata/android/en-US/changelogs/40104000.txt
+++ b/fastlane/metadata/android/en-US/changelogs/40104000.txt
@@ -1,2 +1,2 @@
-Main changes in this version:  Initial implementation of thread messages. Message bubbles.
+Main changes in this version: Initial implementation of thread messages. Message bubbles.
 Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.4.0
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/40104020.txt b/fastlane/metadata/android/en-US/changelogs/40104020.txt
new file mode 100644
index 00000000000..82d3197db30
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/40104020.txt
@@ -0,0 +1,2 @@
+Main changes in this version: add support to @room and undisclosed polls among many other little changes.
+Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.4.2
\ No newline at end of file