From 90fedfea93754c9186dba09478a693b1aad5617c Mon Sep 17 00:00:00 2001
From: Maxime NATUREL <maxime.naturel@niji.fr>
Date: Mon, 29 Aug 2022 17:23:07 +0200
Subject: [PATCH 001/108] Adding changelog entry

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

diff --git a/changelog.d/6961.wip b/changelog.d/6961.wip
new file mode 100644
index 0000000000..2d271da8c1
--- /dev/null
+++ b/changelog.d/6961.wip
@@ -0,0 +1 @@
+[Devices Management] Session overview screen

From ed3bd871ea9c3bb7de600444baa62bba06944ddb Mon Sep 17 00:00:00 2001
From: Maxime NATUREL <maxime.naturel@niji.fr>
Date: Mon, 29 Aug 2022 17:30:59 +0200
Subject: [PATCH 002/108] Renaming header list view to be consistent

---
 .../stylable_devices_list_header_view.xml     |  2 +-
 ...eaderView.kt => SessionsListHeaderView.kt} | 20 +++++++++----------
 .../res/layout/fragment_settings_devices.xml  |  6 +++---
 ...ader.xml => view_sessions_list_header.xml} |  8 ++++----
 4 files changed, 18 insertions(+), 18 deletions(-)
 rename vector/src/main/java/im/vector/app/features/settings/devices/v2/list/{DevicesListHeaderView.kt => SessionsListHeaderView.kt} (74%)
 rename vector/src/main/res/layout/{view_devices_list_header.xml => view_sessions_list_header.xml} (83%)

diff --git a/library/ui-styles/src/main/res/values/stylable_devices_list_header_view.xml b/library/ui-styles/src/main/res/values/stylable_devices_list_header_view.xml
index f0807f89c6..97e0290815 100644
--- a/library/ui-styles/src/main/res/values/stylable_devices_list_header_view.xml
+++ b/library/ui-styles/src/main/res/values/stylable_devices_list_header_view.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
 
-    <declare-styleable name="DevicesListHeaderView">
+    <declare-styleable name="SessionsListHeaderView">
         <attr name="devicesListHeaderTitle" format="string" />
         <attr name="devicesListHeaderDescription" format="string" />
     </declare-styleable>
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/DevicesListHeaderView.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionsListHeaderView.kt
similarity index 74%
rename from vector/src/main/java/im/vector/app/features/settings/devices/v2/list/DevicesListHeaderView.kt
rename to vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionsListHeaderView.kt
index d6c7dbe273..547ed93f24 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/DevicesListHeaderView.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionsListHeaderView.kt
@@ -25,15 +25,15 @@ import androidx.core.content.res.use
 import androidx.core.view.isVisible
 import im.vector.app.R
 import im.vector.app.core.extensions.setTextWithColoredPart
-import im.vector.app.databinding.ViewDevicesListHeaderBinding
+import im.vector.app.databinding.ViewSessionsListHeaderBinding
 
-class DevicesListHeaderView @JvmOverloads constructor(
+class SessionsListHeaderView @JvmOverloads constructor(
         context: Context,
         attrs: AttributeSet? = null,
         defStyleAttr: Int = 0
 ) : ConstraintLayout(context, attrs, defStyleAttr) {
 
-    private val binding = ViewDevicesListHeaderBinding.inflate(
+    private val binding = ViewSessionsListHeaderBinding.inflate(
             LayoutInflater.from(context),
             this
     )
@@ -43,7 +43,7 @@ class DevicesListHeaderView @JvmOverloads constructor(
     init {
         context.obtainStyledAttributes(
                 attrs,
-                R.styleable.DevicesListHeaderView,
+                R.styleable.SessionsListHeaderView,
                 0,
                 0
         ).use {
@@ -53,14 +53,14 @@ class DevicesListHeaderView @JvmOverloads constructor(
     }
 
     private fun setTitle(typedArray: TypedArray) {
-        val title = typedArray.getString(R.styleable.DevicesListHeaderView_devicesListHeaderTitle)
-        binding.devicesListHeaderTitle.text = title
+        val title = typedArray.getString(R.styleable.SessionsListHeaderView_devicesListHeaderTitle)
+        binding.sessionsListHeaderTitle.text = title
     }
 
     private fun setDescription(typedArray: TypedArray) {
-        val description = typedArray.getString(R.styleable.DevicesListHeaderView_devicesListHeaderDescription)
+        val description = typedArray.getString(R.styleable.SessionsListHeaderView_devicesListHeaderDescription)
         if (description.isNullOrEmpty()) {
-            binding.devicesListHeaderDescription.isVisible = false
+            binding.sessionsListHeaderDescription.isVisible = false
             return
         }
 
@@ -70,8 +70,8 @@ class DevicesListHeaderView @JvmOverloads constructor(
         stringBuilder.append(" ")
         stringBuilder.append(learnMore)
 
-        binding.devicesListHeaderDescription.isVisible = true
-        binding.devicesListHeaderDescription.setTextWithColoredPart(
+        binding.sessionsListHeaderDescription.isVisible = true
+        binding.sessionsListHeaderDescription.setTextWithColoredPart(
                 fullText = stringBuilder.toString(),
                 coloredPart = learnMore,
                 underline = false
diff --git a/vector/src/main/res/layout/fragment_settings_devices.xml b/vector/src/main/res/layout/fragment_settings_devices.xml
index 6710f345ce..a289bda735 100644
--- a/vector/src/main/res/layout/fragment_settings_devices.xml
+++ b/vector/src/main/res/layout/fragment_settings_devices.xml
@@ -56,7 +56,7 @@
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintTop_toBottomOf="@id/deviceListInactiveSessionsRecommendation" />
 
-        <im.vector.app.features.settings.devices.v2.list.DevicesListHeaderView
+        <im.vector.app.features.settings.devices.v2.list.SessionsListHeaderView
             android:id="@+id/deviceListHeaderCurrentSession"
             android:layout_width="0dp"
             android:layout_height="wrap_content"
@@ -66,7 +66,7 @@
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintTop_toBottomOf="@id/deviceListSecurityRecommendationsDivider" />
 
-        <im.vector.app.features.settings.devices.v2.list.CurrentSessionView
+        <im.vector.app.features.settings.devices.v2.list.SessionInfoView
             android:id="@+id/deviceListCurrentSession"
             android:layout_width="0dp"
             android:layout_height="wrap_content"
@@ -86,7 +86,7 @@
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintTop_toBottomOf="@id/deviceListCurrentSession" />
 
-        <im.vector.app.features.settings.devices.v2.list.DevicesListHeaderView
+        <im.vector.app.features.settings.devices.v2.list.SessionsListHeaderView
             android:id="@+id/deviceListHeaderOtherSessions"
             android:layout_width="0dp"
             android:layout_height="wrap_content"
diff --git a/vector/src/main/res/layout/view_devices_list_header.xml b/vector/src/main/res/layout/view_sessions_list_header.xml
similarity index 83%
rename from vector/src/main/res/layout/view_devices_list_header.xml
rename to vector/src/main/res/layout/view_sessions_list_header.xml
index 492c3e7a12..d690ee4c87 100644
--- a/vector/src/main/res/layout/view_devices_list_header.xml
+++ b/vector/src/main/res/layout/view_sessions_list_header.xml
@@ -7,7 +7,7 @@
     tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
 
     <TextView
-        android:id="@+id/devices_list_header_title"
+        android:id="@+id/sessions_list_header_title"
         style="@style/TextAppearance.Vector.Subtitle.Medium.DevicesManagement"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
@@ -19,14 +19,14 @@
         tools:text="Other sessions" />
 
     <TextView
-        android:id="@+id/devices_list_header_description"
+        android:id="@+id/sessions_list_header_description"
         style="@style/TextAppearance.Vector.Body.DevicesManagement"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
         android:layout_marginTop="18.5dp"
         android:layout_marginEnd="40dp"
         app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintStart_toStartOf="@id/devices_list_header_title"
-        app:layout_constraintTop_toBottomOf="@id/devices_list_header_title"
+        app:layout_constraintStart_toStartOf="@id/sessions_list_header_title"
+        app:layout_constraintTop_toBottomOf="@id/sessions_list_header_title"
         tools:text="For best security, verify your sessions and sign out from any session that you don’t recognize or use anymore. Learn More." />
 </merge>

From ba1549048d29d9f72ccdec99a19bfec981d42a0f Mon Sep 17 00:00:00 2001
From: Maxime NATUREL <maxime.naturel@niji.fr>
Date: Mon, 29 Aug 2022 18:29:12 +0200
Subject: [PATCH 003/108] Navigation from current session

---
 .../src/main/res/values/strings.xml           |  2 +
 vector/src/main/AndroidManifest.xml           |  1 +
 .../app/core/di/MavericksViewModelModule.kt   |  6 +++
 .../v2/VectorSettingsDevicesFragment.kt       | 26 ++++++++-
 .../v2/VectorSettingsDevicesViewNavigator.kt  | 29 ++++++++++
 .../devices/v2/list/CurrentSessionView.kt     |  2 +
 .../v2/overview/SessionOverviewAction.kt      | 21 ++++++++
 .../v2/overview/SessionOverviewActivity.kt    | 52 ++++++++++++++++++
 .../v2/overview/SessionOverviewArgs.kt        | 25 +++++++++
 .../v2/overview/SessionOverviewFragment.kt    | 52 ++++++++++++++++++
 .../v2/overview/SessionOverviewState.kt       | 28 ++++++++++
 .../v2/overview/SessionOverviewViewModel.kt   | 53 +++++++++++++++++++
 .../res/layout/fragment_settings_devices.xml  |  2 +-
 .../fragment_settings_session_overview.xml    |  6 +++
 14 files changed, 302 insertions(+), 3 deletions(-)
 create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesViewNavigator.kt
 create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewAction.kt
 create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewActivity.kt
 create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewArgs.kt
 create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt
 create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewState.kt
 create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt
 create mode 100644 vector/src/main/res/layout/fragment_settings_session_overview.xml

diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml
index df0e10627a..2b8501a249 100644
--- a/library/ui-strings/src/main/res/values/strings.xml
+++ b/library/ui-strings/src/main/res/values/strings.xml
@@ -3239,5 +3239,7 @@
         <item quantity="one">Consider signing out from old sessions (%1$d day or more) that you don’t use anymore.</item>
         <item quantity="other">Consider signing out from old sessions (%1$d days or more) that you don’t use anymore.</item>
     </plurals>
+    <string name="device_manager_current_session_title">Current Session</string>
+    <string name="device_manager_session_title">Session</string>
 
 </resources>
diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml
index e87bbad77a..7ab9e85edc 100644
--- a/vector/src/main/AndroidManifest.xml
+++ b/vector/src/main/AndroidManifest.xml
@@ -338,6 +338,7 @@
         <activity android:name=".features.settings.font.FontScaleSettingActivity"/>
         <activity android:name=".features.call.dialpad.PstnDialActivity" />
         <activity android:name=".features.home.room.list.home.invites.InvitesActivity"/>
+        <activity android:name=".features.settings.devices.v2.overview.SessionOverviewActivity"/>
 
         <!-- Services -->
 
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 b21b4778e3..bd105436f3 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
@@ -87,6 +87,7 @@ import im.vector.app.features.settings.account.deactivation.DeactivateAccountVie
 import im.vector.app.features.settings.crosssigning.CrossSigningSettingsViewModel
 import im.vector.app.features.settings.devices.DeviceVerificationInfoBottomSheetViewModel
 import im.vector.app.features.settings.devices.DevicesViewModel
+import im.vector.app.features.settings.devices.v2.overview.SessionOverviewViewModel
 import im.vector.app.features.settings.devtools.AccountDataViewModel
 import im.vector.app.features.settings.devtools.GossipingEventsPaperTrailViewModel
 import im.vector.app.features.settings.devtools.KeyRequestListViewModel
@@ -624,4 +625,9 @@ interface MavericksViewModelModule {
     @IntoMap
     @MavericksViewModelKey(InvitesViewModel::class)
     fun invitesViewModel(factory: InvitesViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
+
+    @Binds
+    @IntoMap
+    @MavericksViewModelKey(SessionOverviewViewModel::class)
+    fun sessionOverviewViewModelFactory(factory: SessionOverviewViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
 }
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt
index 78b8c66f9c..2adf7969bf 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt
@@ -42,6 +42,7 @@ import im.vector.app.features.settings.devices.DevicesViewEvents
 import im.vector.app.features.settings.devices.DevicesViewModel
 import im.vector.app.features.settings.devices.v2.list.SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS
 import im.vector.app.features.settings.devices.v2.list.SecurityRecommendationViewState
+import javax.inject.Inject
 
 /**
  * Display the list of the user's devices and sessions.
@@ -50,6 +51,8 @@ import im.vector.app.features.settings.devices.v2.list.SecurityRecommendationVie
 class VectorSettingsDevicesFragment :
         VectorBaseFragment<FragmentSettingsDevicesBinding>() {
 
+    @Inject lateinit var viewNavigator: VectorSettingsDevicesViewNavigator
+
     private val viewModel: DevicesViewModel by fragmentViewModel()
 
     override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentSettingsDevicesBinding {
@@ -72,10 +75,10 @@ class VectorSettingsDevicesFragment :
 
         initLearnMoreButtons()
         initWaitingView()
-        observerViewEvents()
+        observeViewEvents()
     }
 
-    private fun observerViewEvents() {
+    private fun observeViewEvents() {
         viewModel.observeViewEvents {
             when (it) {
                 is DevicesViewEvents.Loading -> showLoading(it.message)
@@ -197,15 +200,34 @@ class VectorSettingsDevicesFragment :
             views.deviceListHeaderCurrentSession.isVisible = true
             views.deviceListCurrentSession.isVisible = true
             views.deviceListCurrentSession.render(it)
+            views.deviceListCurrentSession.debouncedClicks {
+                currentDeviceInfo.deviceInfo.deviceId?.let { deviceId -> navigateToSessionOverview(deviceId) }
+            }
+            views.deviceListCurrentSession.viewDetailsButton.debouncedClicks {
+                currentDeviceInfo.deviceInfo.deviceId?.let { deviceId -> navigateToSessionOverview(deviceId) }
+            }
         } ?: run {
             hideCurrentSessionView()
         }
     }
 
+    private fun navigateToSessionOverview(sessionId: String) {
+        viewNavigator.navigateToSessionOverview(
+                context = requireActivity(),
+                sessionId = sessionId
+        )
+    }
+
     private fun hideCurrentSessionView() {
         views.deviceListHeaderCurrentSession.isVisible = false
         views.deviceListCurrentSession.isVisible = false
         views.deviceListDividerCurrentSession.isVisible = false
+        views.deviceListCurrentSession.debouncedClicks {
+            // do nothing
+        }
+        views.deviceListCurrentSession.viewDetailsButton.debouncedClicks {
+            // do nothing
+        }
     }
 
     private fun handleRequestStatus(unIgnoreRequest: Async<Unit>) {
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesViewNavigator.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesViewNavigator.kt
new file mode 100644
index 0000000000..0e5cb87d7b
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesViewNavigator.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.settings.devices.v2
+
+import android.content.Context
+import im.vector.app.features.settings.devices.v2.overview.SessionOverviewActivity
+import javax.inject.Inject
+
+// TODO add unit tests
+class VectorSettingsDevicesViewNavigator @Inject constructor() {
+
+    fun navigateToSessionOverview(context: Context, sessionId: String) {
+        context.startActivity(SessionOverviewActivity.newIntent(context, sessionId))
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/CurrentSessionView.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/CurrentSessionView.kt
index d6f81f4f79..1ce035931f 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/CurrentSessionView.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/CurrentSessionView.kt
@@ -39,6 +39,8 @@ class CurrentSessionView @JvmOverloads constructor(
         views = ViewCurrentSessionBinding.bind(this)
     }
 
+    val viewDetailsButton = views.currentSessionViewDetailsButton
+
     fun render(currentDeviceInfo: DeviceFullInfo) {
         renderDeviceInfo(currentDeviceInfo.deviceInfo.displayName.orEmpty())
         renderVerificationStatus(currentDeviceInfo.trustLevelForShield)
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewAction.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewAction.kt
new file mode 100644
index 0000000000..c028c08ec4
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewAction.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 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.settings.devices.v2.overview
+
+import im.vector.app.core.platform.VectorViewModelAction
+
+sealed class SessionOverviewAction : VectorViewModelAction
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewActivity.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewActivity.kt
new file mode 100644
index 0000000000..a663c0ff2a
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewActivity.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.settings.devices.v2.overview
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import com.airbnb.mvrx.Mavericks
+import dagger.hilt.android.AndroidEntryPoint
+import im.vector.app.core.extensions.addFragment
+import im.vector.app.core.platform.SimpleFragmentActivity
+
+/**
+ * Display the overview info about a Session.
+ */
+@AndroidEntryPoint
+class SessionOverviewActivity : SimpleFragmentActivity() {
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        if (isFirstCreation()) {
+            addFragment(
+                    container = views.container,
+                    fragmentClass = SessionOverviewFragment::class.java,
+                    params = intent.getParcelableExtra(Mavericks.KEY_ARG)
+            )
+        }
+    }
+
+    companion object {
+        fun newIntent(context: Context, sessionId: String): Intent {
+            return Intent(context, SessionOverviewActivity::class.java).apply {
+                putExtra(Mavericks.KEY_ARG, SessionOverviewArgs(sessionId))
+            }
+        }
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewArgs.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewArgs.kt
new file mode 100644
index 0000000000..87ea883362
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewArgs.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.settings.devices.v2.overview
+
+import android.os.Parcelable
+import kotlinx.parcelize.Parcelize
+
+@Parcelize
+data class SessionOverviewArgs(
+        val sessionId: String
+) : Parcelable
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt
new file mode 100644
index 0000000000..1b8b231a5c
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.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.settings.devices.v2.overview
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.appcompat.app.AppCompatActivity
+import com.airbnb.mvrx.fragmentViewModel
+import com.airbnb.mvrx.withState
+import dagger.hilt.android.AndroidEntryPoint
+import im.vector.app.R
+import im.vector.app.core.platform.VectorBaseFragment
+import im.vector.app.databinding.FragmentSettingsSessionOverviewBinding
+
+/**
+ * Display the overview info about a Session.
+ */
+@AndroidEntryPoint
+class SessionOverviewFragment :
+        VectorBaseFragment<FragmentSettingsSessionOverviewBinding>() {
+
+    private val viewModel: SessionOverviewViewModel by fragmentViewModel()
+
+    override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentSettingsSessionOverviewBinding {
+        return FragmentSettingsSessionOverviewBinding.inflate(inflater, container, false)
+    }
+
+    override fun invalidate() = withState(viewModel) { state ->
+        updateToolbar(state.isCurrentSession)
+    }
+
+    private fun updateToolbar(isCurrentSession: Boolean) {
+        val titleResId = if (isCurrentSession) R.string.device_manager_current_session_title else R.string.device_manager_session_title
+        (activity as? AppCompatActivity)
+                ?.supportActionBar
+                ?.setTitle(titleResId)
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewState.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewState.kt
new file mode 100644
index 0000000000..d91d6a82ce
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewState.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.features.settings.devices.v2.overview
+
+import com.airbnb.mvrx.MavericksState
+
+data class SessionOverviewState(
+        val sessionId: String,
+        val isCurrentSession: Boolean = false,
+) : MavericksState {
+    constructor(args: SessionOverviewArgs) : this(
+            sessionId = args.sessionId
+    )
+}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt
new file mode 100644
index 0000000000..a95cc1a49b
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.settings.devices.v2.overview
+
+import com.airbnb.mvrx.MavericksViewModelFactory
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import im.vector.app.core.di.MavericksAssistedViewModelFactory
+import im.vector.app.core.di.hiltMavericksViewModelFactory
+import im.vector.app.core.platform.EmptyViewEvents
+import im.vector.app.core.platform.VectorViewModel
+import org.matrix.android.sdk.api.session.Session
+
+class SessionOverviewViewModel @AssistedInject constructor(
+        @Assisted val initialState: SessionOverviewState,
+        session: Session,
+) : VectorViewModel<SessionOverviewState, SessionOverviewAction, EmptyViewEvents>(initialState) {
+
+    companion object : MavericksViewModelFactory<SessionOverviewViewModel, SessionOverviewState> by hiltMavericksViewModelFactory()
+
+    @AssistedFactory
+    interface Factory : MavericksAssistedViewModelFactory<SessionOverviewViewModel, SessionOverviewState> {
+        override fun create(initialState: SessionOverviewState): SessionOverviewViewModel
+    }
+
+    init {
+        val currentSessionId = session.sessionParams.deviceId.orEmpty()
+        setState {
+            copy(
+                    isCurrentSession = sessionId.isNotEmpty() && sessionId == currentSessionId
+            )
+        }
+    }
+
+    override fun handle(action: SessionOverviewAction) {
+        TODO("Implement when adding the first action")
+    }
+}
diff --git a/vector/src/main/res/layout/fragment_settings_devices.xml b/vector/src/main/res/layout/fragment_settings_devices.xml
index a289bda735..b4f47302e1 100644
--- a/vector/src/main/res/layout/fragment_settings_devices.xml
+++ b/vector/src/main/res/layout/fragment_settings_devices.xml
@@ -61,7 +61,7 @@
             android:layout_width="0dp"
             android:layout_height="wrap_content"
             app:devicesListHeaderDescription=""
-            app:devicesListHeaderTitle="@string/device_manager_header_section_current_session"
+            app:devicesListHeaderTitle="@string/device_manager_current_session_title"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintTop_toBottomOf="@id/deviceListSecurityRecommendationsDivider" />
diff --git a/vector/src/main/res/layout/fragment_settings_session_overview.xml b/vector/src/main/res/layout/fragment_settings_session_overview.xml
new file mode 100644
index 0000000000..1354408486
--- /dev/null
+++ b/vector/src/main/res/layout/fragment_settings_session_overview.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+</androidx.constraintlayout.widget.ConstraintLayout>

From a1102738d0616cbf5a4febc90803ade7049616ee Mon Sep 17 00:00:00 2001
From: Maxime NATUREL <maxime.naturel@niji.fr>
Date: Tue, 30 Aug 2022 15:03:07 +0200
Subject: [PATCH 004/108] Unit tests for navigator

---
 .../v2/VectorSettingsDevicesViewNavigator.kt  |  1 -
 .../VectorSettingsDevicesViewNavigatorTest.kt | 65 +++++++++++++++++++
 .../im/vector/app/test/fakes/FakeContext.kt   |  7 ++
 3 files changed, 72 insertions(+), 1 deletion(-)
 create mode 100644 vector/src/test/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesViewNavigatorTest.kt

diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesViewNavigator.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesViewNavigator.kt
index 0e5cb87d7b..25c971aacb 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesViewNavigator.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesViewNavigator.kt
@@ -20,7 +20,6 @@ import android.content.Context
 import im.vector.app.features.settings.devices.v2.overview.SessionOverviewActivity
 import javax.inject.Inject
 
-// TODO add unit tests
 class VectorSettingsDevicesViewNavigator @Inject constructor() {
 
     fun navigateToSessionOverview(context: Context, sessionId: String) {
diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesViewNavigatorTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesViewNavigatorTest.kt
new file mode 100644
index 0000000000..2a4c53f34f
--- /dev/null
+++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesViewNavigatorTest.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.settings.devices.v2
+
+import android.content.Intent
+import im.vector.app.features.settings.devices.v2.overview.SessionOverviewActivity
+import im.vector.app.test.fakes.FakeContext
+import io.mockk.every
+import io.mockk.mockk
+import io.mockk.mockkObject
+import io.mockk.unmockkAll
+import io.mockk.verify
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+
+private const val A_SESSION_ID = "session_id"
+
+class VectorSettingsDevicesViewNavigatorTest {
+
+    private val context = FakeContext()
+    private val vectorSettingsDevicesViewNavigator = VectorSettingsDevicesViewNavigator()
+
+    @Before
+    fun setUp() {
+        mockkObject(SessionOverviewActivity.Companion)
+    }
+
+    @After
+    fun tearDown() {
+        unmockkAll()
+    }
+
+    @Test
+    fun `given a session id when navigating to overview then it starts the correct activity`() {
+        val intent = givenIntentForSessionOverview(A_SESSION_ID)
+        context.givenStartActivity(intent)
+
+        vectorSettingsDevicesViewNavigator.navigateToSessionOverview(context.instance, A_SESSION_ID)
+
+        verify {
+            context.instance.startActivity(intent)
+        }
+    }
+
+    private fun givenIntentForSessionOverview(sessionId: String): Intent {
+        val intent = mockk<Intent>()
+        every { SessionOverviewActivity.newIntent(context.instance, sessionId) } returns intent
+        return intent
+    }
+}
diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeContext.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeContext.kt
index 329ac1bdae..d74ebcb678 100644
--- a/vector/src/test/java/im/vector/app/test/fakes/FakeContext.kt
+++ b/vector/src/test/java/im/vector/app/test/fakes/FakeContext.kt
@@ -18,11 +18,14 @@ package im.vector.app.test.fakes
 
 import android.content.ContentResolver
 import android.content.Context
+import android.content.Intent
 import android.net.ConnectivityManager
 import android.net.Uri
 import android.os.ParcelFileDescriptor
 import io.mockk.every
+import io.mockk.just
 import io.mockk.mockk
+import io.mockk.runs
 import java.io.OutputStream
 
 class FakeContext(
@@ -67,4 +70,8 @@ class FakeContext(
         connectivityManager.givenHasActiveConnection()
         givenService(Context.CONNECTIVITY_SERVICE, ConnectivityManager::class.java, connectivityManager.instance)
     }
+
+    fun givenStartActivity(intent: Intent) {
+        every { instance.startActivity(intent) } just runs
+    }
 }

From 862edffceebc08d700a7755de928fc871d468207 Mon Sep 17 00:00:00 2001
From: Maxime NATUREL <maxime.naturel@niji.fr>
Date: Tue, 30 Aug 2022 15:39:14 +0200
Subject: [PATCH 005/108] Renaming view state

---
 .../devices/v2/overview/SessionOverviewViewModel.kt    | 10 +++++-----
 ...ionOverviewState.kt => SessionOverviewViewState.kt} |  2 +-
 2 files changed, 6 insertions(+), 6 deletions(-)
 rename vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/{SessionOverviewState.kt => SessionOverviewViewState.kt} (96%)

diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt
index a95cc1a49b..f55da2819f 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt
@@ -27,15 +27,15 @@ import im.vector.app.core.platform.VectorViewModel
 import org.matrix.android.sdk.api.session.Session
 
 class SessionOverviewViewModel @AssistedInject constructor(
-        @Assisted val initialState: SessionOverviewState,
+        @Assisted val initialState: SessionOverviewViewState,
         session: Session,
-) : VectorViewModel<SessionOverviewState, SessionOverviewAction, EmptyViewEvents>(initialState) {
+) : VectorViewModel<SessionOverviewViewState, SessionOverviewAction, EmptyViewEvents>(initialState) {
 
-    companion object : MavericksViewModelFactory<SessionOverviewViewModel, SessionOverviewState> by hiltMavericksViewModelFactory()
+    companion object : MavericksViewModelFactory<SessionOverviewViewModel, SessionOverviewViewState> by hiltMavericksViewModelFactory()
 
     @AssistedFactory
-    interface Factory : MavericksAssistedViewModelFactory<SessionOverviewViewModel, SessionOverviewState> {
-        override fun create(initialState: SessionOverviewState): SessionOverviewViewModel
+    interface Factory : MavericksAssistedViewModelFactory<SessionOverviewViewModel, SessionOverviewViewState> {
+        override fun create(initialState: SessionOverviewViewState): SessionOverviewViewModel
     }
 
     init {
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewState.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewState.kt
similarity index 96%
rename from vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewState.kt
rename to vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewState.kt
index d91d6a82ce..e839348800 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewState.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewState.kt
@@ -18,7 +18,7 @@ package im.vector.app.features.settings.devices.v2.overview
 
 import com.airbnb.mvrx.MavericksState
 
-data class SessionOverviewState(
+data class SessionOverviewViewState(
         val sessionId: String,
         val isCurrentSession: Boolean = false,
 ) : MavericksState {

From eb64b376f4d5a96ef241679319f84ba1558666a5 Mon Sep 17 00:00:00 2001
From: Maxime NATUREL <maxime.naturel@niji.fr>
Date: Tue, 30 Aug 2022 16:09:03 +0200
Subject: [PATCH 006/108] Small renamings/reorganization in CryptoService

---
 .../matrix/android/sdk/flow/FlowSession.kt    |  2 +-
 .../sdk/internal/crypto/E2eeSanityTests.kt    |  4 ++--
 .../crypto/crosssigning/XSigningTest.kt       |  2 +-
 .../internal/crypto/verification/SASTest.kt   |  4 ++--
 .../sdk/api/session/crypto/CryptoService.kt   | 24 +++++++++----------
 .../internal/crypto/DefaultCryptoService.kt   | 22 ++++++++---------
 .../helper/MessageInformationDataFactory.kt   |  2 +-
 .../VectorSettingsSecurityPrivacyFragment.kt  |  2 +-
 8 files changed, 31 insertions(+), 31 deletions(-)

diff --git a/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowSession.kt b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowSession.kt
index f22cfa369a..80ed311901 100644
--- a/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowSession.kt
+++ b/matrix-sdk-android-flow/src/main/java/org/matrix/android/sdk/flow/FlowSession.kt
@@ -72,7 +72,7 @@ class FlowSession(private val session: Session) {
     }
 
     fun liveMyDevicesInfo(): Flow<List<DeviceInfo>> {
-        return session.cryptoService().getLiveMyDevicesInfo().asFlow()
+        return session.cryptoService().getMyDevicesInfoLive().asFlow()
                 .startWith(session.coroutineDispatchers.io) {
                     session.cryptoService().getMyDevicesInfo()
                 }
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt
index 251c13ccbf..f883295495 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt
@@ -676,8 +676,8 @@ class E2eeSanityTests : InstrumentedTest {
         assertEquals("Decimal code should have matched", oldCode, newCode)
 
         // Assert that devices are verified
-        val newDeviceFromOldPov: CryptoDeviceInfo? = aliceSession.cryptoService().getDeviceInfo(aliceSession.myUserId, aliceNewSession.sessionParams.deviceId)
-        val oldDeviceFromNewPov: CryptoDeviceInfo? = aliceSession.cryptoService().getDeviceInfo(aliceSession.myUserId, aliceSession.sessionParams.deviceId)
+        val newDeviceFromOldPov: CryptoDeviceInfo? = aliceSession.cryptoService().getCryptoDeviceInfo(aliceSession.myUserId, aliceNewSession.sessionParams.deviceId)
+        val oldDeviceFromNewPov: CryptoDeviceInfo? = aliceSession.cryptoService().getCryptoDeviceInfo(aliceSession.myUserId, aliceSession.sessionParams.deviceId)
 
         Assert.assertTrue("new device should be verified from old point of view", newDeviceFromOldPov!!.isVerified)
         Assert.assertTrue("old device should be verified from new point of view", oldDeviceFromNewPov!!.isVerified)
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt
index 8cb38ddc87..ef3fdfeeda 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt
@@ -193,7 +193,7 @@ class XSigningTest : InstrumentedTest {
             fail("Bob should see the new device")
         }
 
-        val bobSecondDevicePOVFirstDevice = bobSession.cryptoService().getDeviceInfo(bobUserId, bobSecondDeviceId)
+        val bobSecondDevicePOVFirstDevice = bobSession.cryptoService().getCryptoDeviceInfo(bobUserId, bobSecondDeviceId)
         assertNotNull("Bob Second device should be known and persisted from first", bobSecondDevicePOVFirstDevice)
 
         // Manually mark it as trusted from first session
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt
index c2e74abc59..1bffbeeeaa 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt
@@ -521,9 +521,9 @@ class SASTest : InstrumentedTest {
         testHelper.await(bobSASLatch)
 
         // Assert that devices are verified
-        val bobDeviceInfoFromAlicePOV: CryptoDeviceInfo? = aliceSession.cryptoService().getDeviceInfo(bobUserId, bobDeviceId)
+        val bobDeviceInfoFromAlicePOV: CryptoDeviceInfo? = aliceSession.cryptoService().getCryptoDeviceInfo(bobUserId, bobDeviceId)
         val aliceDeviceInfoFromBobPOV: CryptoDeviceInfo? =
-                bobSession.cryptoService().getDeviceInfo(aliceSession.myUserId, aliceSession.cryptoService().getMyDevice().deviceId)
+                bobSession.cryptoService().getCryptoDeviceInfo(aliceSession.myUserId, aliceSession.cryptoService().getMyDevice().deviceId)
 
         assertTrue("alice device should be verified from bob point of view", aliceDeviceInfoFromBobPOV!!.isVerified)
         assertTrue("bob device should be verified from alice point of view", bobDeviceInfoFromAlicePOV!!.isVerified)
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 a5e05f69e0..ee5fe20d07 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,7 +113,17 @@ interface CryptoService {
 
     fun setRoomBlacklistUnverifiedDevices(roomId: String)
 
-    fun getDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo?
+    fun getCryptoDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo?
+
+    fun getCryptoDeviceInfo(deviceId: String, callback: MatrixCallback<DeviceInfo>)
+
+    fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo>
+
+    fun getLiveCryptoDeviceInfo(): LiveData<List<CryptoDeviceInfo>>
+
+    fun getLiveCryptoDeviceInfo(userId: String): LiveData<List<CryptoDeviceInfo>>
+
+    fun getLiveCryptoDeviceInfo(userIds: List<String>): LiveData<List<CryptoDeviceInfo>>
 
     fun requestRoomKeyForEvent(event: Event)
 
@@ -127,9 +137,7 @@ interface CryptoService {
 
     fun getMyDevicesInfo(): List<DeviceInfo>
 
-    fun getLiveMyDevicesInfo(): LiveData<List<DeviceInfo>>
-
-    fun getDeviceInfo(deviceId: String, callback: MatrixCallback<DeviceInfo>)
+    fun getMyDevicesInfoLive(): LiveData<List<DeviceInfo>>
 
     fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int
 
@@ -156,14 +164,6 @@ interface CryptoService {
 
     fun downloadKeys(userIds: List<String>, forceDownload: Boolean, callback: MatrixCallback<MXUsersDevicesMap<CryptoDeviceInfo>>)
 
-    fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo>
-
-    fun getLiveCryptoDeviceInfo(): LiveData<List<CryptoDeviceInfo>>
-
-    fun getLiveCryptoDeviceInfo(userId: String): LiveData<List<CryptoDeviceInfo>>
-
-    fun getLiveCryptoDeviceInfo(userIds: List<String>): LiveData<List<CryptoDeviceInfo>>
-
     fun addNewSessionListener(newSessionListener: NewSessionListener)
     fun removeSessionListener(listener: NewSessionListener)
 
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 35c066dea8..739f86e659 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
@@ -273,7 +273,7 @@ internal class DefaultCryptoService @Inject constructor(
                 .executeBy(taskExecutor)
     }
 
-    override fun getLiveMyDevicesInfo(): LiveData<List<DeviceInfo>> {
+    override fun getMyDevicesInfoLive(): LiveData<List<DeviceInfo>> {
         return cryptoStore.getLiveMyDevicesInfo()
     }
 
@@ -281,15 +281,6 @@ internal class DefaultCryptoService @Inject constructor(
         return cryptoStore.getMyDevicesInfo()
     }
 
-    override fun getDeviceInfo(deviceId: String, callback: MatrixCallback<DeviceInfo>) {
-        getDeviceInfoTask
-                .configureWith(GetDeviceInfoTask.Params(deviceId)) {
-                    this.executionThread = TaskThread.CRYPTO
-                    this.callback = callback
-                }
-                .executeBy(taskExecutor)
-    }
-
     override fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int {
         return cryptoStore.inboundGroupSessionsCount(onlyBackedUp)
     }
@@ -513,7 +504,7 @@ internal class DefaultCryptoService @Inject constructor(
      * @param userId the user id
      * @param deviceId the device id
      */
-    override fun getDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo? {
+    override fun getCryptoDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo? {
         return if (userId.isNotEmpty() && !deviceId.isNullOrEmpty()) {
             cryptoStore.getUserDevice(userId, deviceId)
         } else {
@@ -521,6 +512,15 @@ internal class DefaultCryptoService @Inject constructor(
         }
     }
 
+    override fun getCryptoDeviceInfo(deviceId: String, callback: MatrixCallback<DeviceInfo>) {
+        getDeviceInfoTask
+                .configureWith(GetDeviceInfoTask.Params(deviceId)) {
+                    this.executionThread = TaskThread.CRYPTO
+                    this.callback = callback
+                }
+                .executeBy(taskExecutor)
+    }
+
     override fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> {
         return cryptoStore.getUserDeviceList(userId).orEmpty()
     }
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 6d94837f88..b711bf37bd 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
@@ -162,7 +162,7 @@ class MessageInformationDataFactory @Inject constructor(
                             .toModel<EncryptedEventContent>()
                             ?.deviceId
                             ?.let { deviceId ->
-                                session.cryptoService().getDeviceInfo(event.root.senderId ?: "", deviceId)
+                                session.cryptoService().getCryptoDeviceInfo(event.root.senderId ?: "", deviceId)
                             }
                     when {
                         sendingDevice == null -> {
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 2b4d376f55..ecb1779a4a 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
@@ -585,7 +585,7 @@ class VectorSettingsSecurityPrivacyFragment :
         }
 
         // crypto section: device key (fingerprint)
-        val deviceInfo = session.cryptoService().getDeviceInfo(userId, deviceId)
+        val deviceInfo = session.cryptoService().getCryptoDeviceInfo(userId, deviceId)
 
         val fingerprint = deviceInfo?.fingerprint()
         if (fingerprint?.isNotEmpty() == true) {

From c690a8cd81ac8eb02f5ce961e1e9f95fcadfaf8d Mon Sep 17 00:00:00 2001
From: Maxime NATUREL <maxime.naturel@niji.fr>
Date: Tue, 30 Aug 2022 16:32:32 +0200
Subject: [PATCH 007/108] Adding a method to retrieve livedata of device info
 for a given device id

---
 .../sdk/api/session/crypto/CryptoService.kt   |  3 ++
 .../internal/crypto/DefaultCryptoService.kt   |  5 +++
 .../internal/crypto/store/IMXCryptoStore.kt   |  2 ++
 .../crypto/store/db/RealmCryptoStore.kt       | 26 +++++++++-----
 .../MyDeviceLastSeenInfoEntityMapper.kt       | 34 +++++++++++++++++++
 5 files changed, 62 insertions(+), 8 deletions(-)
 create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/MyDeviceLastSeenInfoEntityMapper.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 ee5fe20d07..4f6a1fa02f 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
@@ -40,6 +40,7 @@ import org.matrix.android.sdk.api.session.crypto.verification.VerificationServic
 import org.matrix.android.sdk.api.session.events.model.Content
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent
+import org.matrix.android.sdk.api.util.Optional
 import org.matrix.android.sdk.internal.crypto.model.SessionInfo
 
 interface CryptoService {
@@ -139,6 +140,8 @@ interface CryptoService {
 
     fun getMyDevicesInfoLive(): LiveData<List<DeviceInfo>>
 
+    fun getMyDevicesInfoLive(deviceId: String): LiveData<Optional<DeviceInfo>>
+
     fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int
 
     fun isRoomEncrypted(roomId: String): Boolean
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt
index 739f86e659..39866163ce 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
@@ -73,6 +73,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityConten
 import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
 import org.matrix.android.sdk.api.session.room.model.shouldShareHistory
 import org.matrix.android.sdk.api.session.sync.model.SyncResponse
+import org.matrix.android.sdk.api.util.Optional
 import org.matrix.android.sdk.internal.crypto.actions.MegolmSessionDataImporter
 import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction
 import org.matrix.android.sdk.internal.crypto.algorithms.IMXEncrypting
@@ -277,6 +278,10 @@ internal class DefaultCryptoService @Inject constructor(
         return cryptoStore.getLiveMyDevicesInfo()
     }
 
+    override fun getMyDevicesInfoLive(deviceId: String): LiveData<Optional<DeviceInfo>> {
+        return cryptoStore.getLiveMyDevicesInfo(deviceId)
+    }
+
     override fun getMyDevicesInfo(): List<DeviceInfo> {
         return cryptoStore.getMyDevicesInfo()
     }
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 0413fc730c..3aa4e2f764 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
@@ -242,6 +242,8 @@ internal interface IMXCryptoStore {
 
     fun getLiveMyDevicesInfo(): LiveData<List<DeviceInfo>>
 
+    fun getLiveMyDevicesInfo(deviceId: String): LiveData<Optional<DeviceInfo>>
+
     fun saveMyDevicesInfo(info: List<DeviceInfo>)
 
     /**
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 f5468634cb..736d4d495c 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
@@ -55,6 +55,7 @@ import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper
 import org.matrix.android.sdk.internal.crypto.model.OutboundGroupSessionWrapper
 import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
 import org.matrix.android.sdk.internal.crypto.store.db.mapper.CrossSigningKeysMapper
+import org.matrix.android.sdk.internal.crypto.store.db.mapper.MyDeviceLastSeenInfoEntityMapper
 import org.matrix.android.sdk.internal.crypto.store.db.model.AuditTrailEntity
 import org.matrix.android.sdk.internal.crypto.store.db.model.AuditTrailEntityFields
 import org.matrix.android.sdk.internal.crypto.store.db.model.AuditTrailMapper
@@ -68,6 +69,7 @@ import org.matrix.android.sdk.internal.crypto.store.db.model.DeviceInfoEntity
 import org.matrix.android.sdk.internal.crypto.store.db.model.DeviceInfoEntityFields
 import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntity
 import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntity
+import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntityFields
 import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntity
 import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntityFields
 import org.matrix.android.sdk.internal.crypto.store.db.model.OlmSessionEntity
@@ -112,6 +114,7 @@ internal class RealmCryptoStore @Inject constructor(
         @UserId private val userId: String,
         @DeviceId private val deviceId: String?,
         private val clock: Clock,
+        private val myDeviceLastSeenInfoEntityMapper: MyDeviceLastSeenInfoEntityMapper,
 ) : IMXCryptoStore {
 
     /* ==========================================================================================
@@ -596,17 +599,24 @@ internal class RealmCryptoStore @Inject constructor(
                 { realm: Realm ->
                     realm.where<MyDeviceLastSeenInfoEntity>()
                 },
-                { entity ->
-                    DeviceInfo(
-                            deviceId = entity.deviceId,
-                            lastSeenIp = entity.lastSeenIp,
-                            lastSeenTs = entity.lastSeenTs,
-                            displayName = entity.displayName
-                    )
-                }
+                { entity -> myDeviceLastSeenInfoEntityMapper.map(entity) }
         )
     }
 
+    override fun getLiveMyDevicesInfo(deviceId: String): LiveData<Optional<DeviceInfo>> {
+        val liveData = monarchy.findAllMappedWithChanges(
+                { realm: Realm ->
+                    realm.where<MyDeviceLastSeenInfoEntity>()
+                            .equalTo(MyDeviceLastSeenInfoEntityFields.DEVICE_ID, deviceId)
+                },
+                { entity -> myDeviceLastSeenInfoEntityMapper.map(entity) }
+        )
+
+        return Transformations.map(liveData) {
+            it.firstOrNull().toOptional()
+        }
+    }
+
     override fun saveMyDevicesInfo(info: List<DeviceInfo>) {
         val entities = info.map {
             MyDeviceLastSeenInfoEntity(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/MyDeviceLastSeenInfoEntityMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/MyDeviceLastSeenInfoEntityMapper.kt
new file mode 100644
index 0000000000..ed44b0765a
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/MyDeviceLastSeenInfoEntityMapper.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 org.matrix.android.sdk.internal.crypto.store.db.mapper
+
+import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
+import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntity
+import javax.inject.Inject
+
+// TODO add unit tests
+internal class MyDeviceLastSeenInfoEntityMapper @Inject constructor() {
+
+    fun map(entity: MyDeviceLastSeenInfoEntity): DeviceInfo {
+        return DeviceInfo(
+                deviceId = entity.deviceId,
+                lastSeenIp = entity.lastSeenIp,
+                lastSeenTs = entity.lastSeenTs,
+                displayName = entity.displayName
+        )
+    }
+}

From cc36f40a8d4cd95cac6ea1184e0021d10db3ec5b Mon Sep 17 00:00:00 2001
From: Maxime NATUREL <maxime.naturel@niji.fr>
Date: Tue, 30 Aug 2022 16:47:34 +0200
Subject: [PATCH 008/108] Adding a method to retrieve livedata of  crypto
 device info for a given device id

---
 .../matrix/android/sdk/api/session/crypto/CryptoService.kt  | 2 ++
 .../android/sdk/internal/crypto/DefaultCryptoService.kt     | 4 ++++
 .../android/sdk/internal/crypto/store/IMXCryptoStore.kt     | 2 ++
 .../sdk/internal/crypto/store/db/RealmCryptoStore.kt        | 6 ++++++
 4 files changed, 14 insertions(+)

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 4f6a1fa02f..e0e662c789 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
@@ -122,6 +122,8 @@ interface CryptoService {
 
     fun getLiveCryptoDeviceInfo(): LiveData<List<CryptoDeviceInfo>>
 
+    fun getLiveCryptoDeviceInfoWithId(deviceId: String): LiveData<Optional<CryptoDeviceInfo>>
+
     fun getLiveCryptoDeviceInfo(userId: String): LiveData<List<CryptoDeviceInfo>>
 
     fun getLiveCryptoDeviceInfo(userIds: List<String>): LiveData<List<CryptoDeviceInfo>>
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 39866163ce..8dd7c309c6 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
@@ -534,6 +534,10 @@ internal class DefaultCryptoService @Inject constructor(
         return cryptoStore.getLiveDeviceList()
     }
 
+    override fun getLiveCryptoDeviceInfoWithId(deviceId: String): LiveData<Optional<CryptoDeviceInfo>> {
+        return cryptoStore.getLiveDeviceWithId(deviceId)
+    }
+
     override fun getLiveCryptoDeviceInfo(userId: String): LiveData<List<CryptoDeviceInfo>> {
         return cryptoStore.getLiveDeviceList(userId)
     }
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 3aa4e2f764..56eba25249 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
@@ -238,6 +238,8 @@ internal interface IMXCryptoStore {
     // TODO temp
     fun getLiveDeviceList(): LiveData<List<CryptoDeviceInfo>>
 
+    fun getLiveDeviceWithId(deviceId: String): LiveData<Optional<CryptoDeviceInfo>>
+
     fun getMyDevicesInfo(): List<DeviceInfo>
 
     fun getLiveMyDevicesInfo(): LiveData<List<DeviceInfo>>
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 736d4d495c..3b8fa4cacd 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
@@ -581,6 +581,12 @@ internal class RealmCryptoStore @Inject constructor(
         }
     }
 
+    override fun getLiveDeviceWithId(deviceId: String): LiveData<Optional<CryptoDeviceInfo>> {
+        return Transformations.map(getLiveDeviceList()) { devices ->
+            devices.firstOrNull { it.deviceId == deviceId }.toOptional()
+        }
+    }
+
     override fun getMyDevicesInfo(): List<DeviceInfo> {
         return monarchy.fetchAllCopiedSync {
             it.where<MyDeviceLastSeenInfoEntity>()

From 13626a161aaf06c7348021fbeabf04cfe1f700eb Mon Sep 17 00:00:00 2001
From: Maxime NATUREL <maxime.naturel@niji.fr>
Date: Wed, 31 Aug 2022 10:09:40 +0200
Subject: [PATCH 009/108] Adding use case to get full device info for a given
 device id

---
 .../v2/overview/GetDeviceFullInfoUseCase.kt   | 54 +++++++++++++++++++
 .../v2/overview/SessionOverviewViewModel.kt   | 21 ++++++--
 2 files changed, 71 insertions(+), 4 deletions(-)
 create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt

diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt
new file mode 100644
index 0000000000..b7d8efb59a
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.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.settings.devices.v2.overview
+
+import androidx.lifecycle.asFlow
+import im.vector.app.core.di.ActiveSessionHolder
+import im.vector.app.features.settings.devices.DeviceFullInfo
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.emptyFlow
+import org.matrix.android.sdk.api.util.Optional
+import org.matrix.android.sdk.api.util.toOptional
+import javax.inject.Inject
+
+// TODO add unit tests
+class GetDeviceFullInfoUseCase @Inject constructor(
+        private val activeSessionHolder: ActiveSessionHolder,
+) {
+
+    fun execute(deviceId: String): Flow<Optional<DeviceFullInfo>> {
+        return activeSessionHolder.getSafeActiveSession()?.let { session ->
+            combine(
+                    session.cryptoService().getMyDevicesInfoLive(deviceId).asFlow(),
+                    session.cryptoService().getLiveCryptoDeviceInfoWithId(deviceId).asFlow()
+            ) { deviceInfo, cryptoDeviceInfo ->
+                val info = deviceInfo.getOrNull()
+                val cryptoInfo = cryptoDeviceInfo.getOrNull()
+                val fullInfo = if (info != null && cryptoInfo != null) {
+                    DeviceFullInfo(
+                            deviceInfo = info,
+                            cryptoDeviceInfo = cryptoInfo
+                    )
+                } else {
+                    null
+                }
+                fullInfo.toOptional()
+            }
+        } ?: emptyFlow()
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt
index f55da2819f..84c15301aa 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt
@@ -17,6 +17,7 @@
 package im.vector.app.features.settings.devices.v2.overview
 
 import com.airbnb.mvrx.MavericksViewModelFactory
+import com.airbnb.mvrx.Success
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
@@ -24,11 +25,16 @@ import im.vector.app.core.di.MavericksAssistedViewModelFactory
 import im.vector.app.core.di.hiltMavericksViewModelFactory
 import im.vector.app.core.platform.EmptyViewEvents
 import im.vector.app.core.platform.VectorViewModel
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.flow.onEach
 import org.matrix.android.sdk.api.session.Session
 
+// TODO add unit tests
 class SessionOverviewViewModel @AssistedInject constructor(
         @Assisted val initialState: SessionOverviewViewState,
         session: Session,
+        private val getDeviceFullInfoUseCase: GetDeviceFullInfoUseCase,
 ) : VectorViewModel<SessionOverviewViewState, SessionOverviewAction, EmptyViewEvents>(initialState) {
 
     companion object : MavericksViewModelFactory<SessionOverviewViewModel, SessionOverviewViewState> by hiltMavericksViewModelFactory()
@@ -39,12 +45,19 @@ class SessionOverviewViewModel @AssistedInject constructor(
     }
 
     init {
-        val currentSessionId = session.sessionParams.deviceId.orEmpty()
+        val currentDeviceId = session.sessionParams.deviceId.orEmpty()
         setState {
-            copy(
-                    isCurrentSession = sessionId.isNotEmpty() && sessionId == currentSessionId
-            )
+            copy(isCurrentSession = sessionId.isNotEmpty() && sessionId == currentDeviceId)
         }
+
+        observeSessionInfo(currentDeviceId)
+    }
+
+    private fun observeSessionInfo(deviceId: String) {
+        getDeviceFullInfoUseCase.execute(deviceId)
+                .mapNotNull { it.getOrNull() }
+                .onEach { setState { copy(deviceInfo = Success(it)) } }
+                .launchIn(viewModelScope)
     }
 
     override fun handle(action: SessionOverviewAction) {

From 40d716d0999d2ed135b9088d7196d832e8ea633c Mon Sep 17 00:00:00 2001
From: Maxime NATUREL <maxime.naturel@niji.fr>
Date: Wed, 31 Aug 2022 11:55:58 +0200
Subject: [PATCH 010/108] Adding unit tests for the new use case

---
 .../v2/overview/GetDeviceFullInfoUseCase.kt   |  1 -
 .../GetLiveLocationShareSummaryUseCaseTest.kt |  7 +-
 .../GetListOfUserLiveLocationUseCaseTest.kt   |  4 +-
 .../overview/GetDeviceFullInfoUseCaseTest.kt  | 99 +++++++++++++++++++
 .../app/test/TestCoroutineDispatchers.kt      |  2 +-
 .../app/test/fakes/FakeActiveSessionHolder.kt |  4 +
 .../app/test/fakes/FakeCryptoService.kt       |  8 ++
 .../test/fakes/FakeFlowLiveDataConversions.kt |  4 +-
 8 files changed, 119 insertions(+), 10 deletions(-)
 create mode 100644 vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCaseTest.kt

diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt
index b7d8efb59a..d20ca17471 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt
@@ -26,7 +26,6 @@ import org.matrix.android.sdk.api.util.Optional
 import org.matrix.android.sdk.api.util.toOptional
 import javax.inject.Inject
 
-// TODO add unit tests
 class GetDeviceFullInfoUseCase @Inject constructor(
         private val activeSessionHolder: ActiveSessionHolder,
 ) {
diff --git a/vector/src/test/java/im/vector/app/features/location/live/GetLiveLocationShareSummaryUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/location/live/GetLiveLocationShareSummaryUseCaseTest.kt
index ed1bcebf16..89966b5317 100644
--- a/vector/src/test/java/im/vector/app/features/location/live/GetLiveLocationShareSummaryUseCaseTest.kt
+++ b/vector/src/test/java/im/vector/app/features/location/live/GetLiveLocationShareSummaryUseCaseTest.kt
@@ -18,7 +18,7 @@ package im.vector.app.features.location.live
 
 import im.vector.app.test.fakes.FakeFlowLiveDataConversions
 import im.vector.app.test.fakes.FakeSession
-import im.vector.app.test.fakes.givenAsFlowReturns
+import im.vector.app.test.fakes.givenAsFlow
 import io.mockk.unmockkAll
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.test.runTest
@@ -28,7 +28,6 @@ import org.junit.Before
 import org.junit.Test
 import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary
 import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent
-import org.matrix.android.sdk.api.util.Optional
 
 private const val A_ROOM_ID = "room_id"
 private const val AN_EVENT_ID = "event_id"
@@ -64,7 +63,7 @@ class GetLiveLocationShareSummaryUseCaseTest {
                 .getRoom(A_ROOM_ID)
                 .locationSharingService()
                 .givenLiveLocationShareSummaryReturns(AN_EVENT_ID, summary)
-                .givenAsFlowReturns(Optional(summary))
+                .givenAsFlow()
 
         val result = getLiveLocationShareSummaryUseCase.execute(A_ROOM_ID, AN_EVENT_ID).first()
 
@@ -77,7 +76,7 @@ class GetLiveLocationShareSummaryUseCaseTest {
                 .getRoom(A_ROOM_ID)
                 .locationSharingService()
                 .givenLiveLocationShareSummaryReturns(AN_EVENT_ID, null)
-                .givenAsFlowReturns(Optional(null))
+                .givenAsFlow()
 
         val result = getLiveLocationShareSummaryUseCase.execute(A_ROOM_ID, AN_EVENT_ID).first()
 
diff --git a/vector/src/test/java/im/vector/app/features/location/live/map/GetListOfUserLiveLocationUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/location/live/map/GetListOfUserLiveLocationUseCaseTest.kt
index 420b8e6a06..6d24858915 100644
--- a/vector/src/test/java/im/vector/app/features/location/live/map/GetListOfUserLiveLocationUseCaseTest.kt
+++ b/vector/src/test/java/im/vector/app/features/location/live/map/GetListOfUserLiveLocationUseCaseTest.kt
@@ -19,7 +19,7 @@ package im.vector.app.features.location.live.map
 import im.vector.app.features.location.LocationData
 import im.vector.app.test.fakes.FakeFlowLiveDataConversions
 import im.vector.app.test.fakes.FakeSession
-import im.vector.app.test.fakes.givenAsFlowReturns
+import im.vector.app.test.fakes.givenAsFlow
 import io.mockk.coEvery
 import io.mockk.mockk
 import io.mockk.unmockkAll
@@ -81,7 +81,7 @@ class GetListOfUserLiveLocationUseCaseTest {
                 .getRoom(A_ROOM_ID)
                 .locationSharingService()
                 .givenRunningLiveLocationShareSummariesReturns(summaries)
-                .givenAsFlowReturns(summaries)
+                .givenAsFlow()
 
         val viewState1 = UserLiveLocationViewState(
                 matrixItem = MatrixItem.UserItem(id = "@userId1:matrix.org", displayName = "User 1", avatarUrl = ""),
diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCaseTest.kt
new file mode 100644
index 0000000000..32d7b6edfe
--- /dev/null
+++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCaseTest.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.settings.devices.v2.overview
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.asFlow
+import im.vector.app.features.settings.devices.DeviceFullInfo
+import im.vector.app.test.fakes.FakeActiveSessionHolder
+import im.vector.app.test.fakes.FakeFlowLiveDataConversions
+import im.vector.app.test.fakes.givenAsFlow
+import io.mockk.unmockkAll
+import io.mockk.verify
+import kotlinx.coroutines.flow.firstOrNull
+import kotlinx.coroutines.test.runTest
+import org.amshove.kluent.shouldBeEqualTo
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
+import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
+import org.matrix.android.sdk.api.util.Optional
+
+private const val A_DEVICE_ID = "device-id"
+
+class GetDeviceFullInfoUseCaseTest {
+
+    private val fakeActiveSessionHolder = FakeActiveSessionHolder()
+    private val fakeFlowLiveDataConversions = FakeFlowLiveDataConversions()
+
+    private val getDeviceFullInfoUseCase = GetDeviceFullInfoUseCase(
+            activeSessionHolder = fakeActiveSessionHolder.instance
+    )
+
+    @Before
+    fun setUp() {
+        fakeFlowLiveDataConversions.setup()
+    }
+
+    @After
+    fun tearDown() {
+        unmockkAll()
+    }
+
+    @Test
+    fun `given an active session and info for device when getting device info then the result is correct`() = runTest {
+        val deviceInfo = DeviceInfo()
+        fakeActiveSessionHolder.fakeSession.fakeCryptoService.myDevicesInfoWithIdLiveData = MutableLiveData(Optional(deviceInfo))
+        fakeActiveSessionHolder.fakeSession.fakeCryptoService.myDevicesInfoWithIdLiveData.givenAsFlow()
+        val cryptoDeviceInfo = CryptoDeviceInfo(deviceId = A_DEVICE_ID, userId = "")
+        fakeActiveSessionHolder.fakeSession.fakeCryptoService.cryptoDeviceInfoWithIdLiveData = MutableLiveData(Optional(cryptoDeviceInfo))
+        fakeActiveSessionHolder.fakeSession.fakeCryptoService.cryptoDeviceInfoWithIdLiveData.givenAsFlow()
+
+        val deviceFullInfo = getDeviceFullInfoUseCase.execute(A_DEVICE_ID).firstOrNull()
+
+        deviceFullInfo shouldBeEqualTo Optional(DeviceFullInfo(deviceInfo = deviceInfo, cryptoDeviceInfo = cryptoDeviceInfo))
+        verify { fakeActiveSessionHolder.instance.getSafeActiveSession() }
+        verify { fakeActiveSessionHolder.fakeSession.fakeCryptoService.getMyDevicesInfoLive(A_DEVICE_ID).asFlow() }
+        verify { fakeActiveSessionHolder.fakeSession.fakeCryptoService.getLiveCryptoDeviceInfoWithId(A_DEVICE_ID).asFlow() }
+    }
+
+    @Test
+    fun `given an active session and no info for device when getting device info then the result is null`() = runTest {
+        fakeActiveSessionHolder.fakeSession.fakeCryptoService.myDevicesInfoWithIdLiveData = MutableLiveData(Optional(null))
+        fakeActiveSessionHolder.fakeSession.fakeCryptoService.myDevicesInfoWithIdLiveData.givenAsFlow()
+        fakeActiveSessionHolder.fakeSession.fakeCryptoService.cryptoDeviceInfoWithIdLiveData = MutableLiveData(Optional(null))
+        fakeActiveSessionHolder.fakeSession.fakeCryptoService.cryptoDeviceInfoWithIdLiveData.givenAsFlow()
+
+        val deviceFullInfo = getDeviceFullInfoUseCase.execute(A_DEVICE_ID).firstOrNull()
+
+        deviceFullInfo shouldBeEqualTo Optional(null)
+        verify { fakeActiveSessionHolder.instance.getSafeActiveSession() }
+        verify { fakeActiveSessionHolder.fakeSession.fakeCryptoService.getMyDevicesInfoLive(A_DEVICE_ID).asFlow() }
+        verify { fakeActiveSessionHolder.fakeSession.fakeCryptoService.getLiveCryptoDeviceInfoWithId(A_DEVICE_ID).asFlow() }
+    }
+
+    @Test
+    fun `given no active session when getting device info then the result is empty`() = runTest {
+        fakeActiveSessionHolder.givenGetSafeActiveSessionReturns(null)
+
+        val deviceFullInfo = getDeviceFullInfoUseCase.execute(A_DEVICE_ID).firstOrNull()
+
+        deviceFullInfo shouldBeEqualTo null
+        verify { fakeActiveSessionHolder.instance.getSafeActiveSession() }
+    }
+}
diff --git a/vector/src/test/java/im/vector/app/test/TestCoroutineDispatchers.kt b/vector/src/test/java/im/vector/app/test/TestCoroutineDispatchers.kt
index fb3c1bb70a..c4f4c2a19a 100644
--- a/vector/src/test/java/im/vector/app/test/TestCoroutineDispatchers.kt
+++ b/vector/src/test/java/im/vector/app/test/TestCoroutineDispatchers.kt
@@ -19,7 +19,7 @@ package im.vector.app.test
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
 
-private val testDispatcher = UnconfinedTestDispatcher()
+internal val testDispatcher = UnconfinedTestDispatcher()
 
 internal val testCoroutineDispatchers = MatrixCoroutineDispatchers(
         io = testDispatcher,
diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeActiveSessionHolder.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeActiveSessionHolder.kt
index 3065c18c30..bfc36ef06d 100644
--- a/vector/src/test/java/im/vector/app/test/fakes/FakeActiveSessionHolder.kt
+++ b/vector/src/test/java/im/vector/app/test/fakes/FakeActiveSessionHolder.kt
@@ -33,4 +33,8 @@ class FakeActiveSessionHolder(
     fun expectSetsActiveSession(session: Session) {
         justRun { instance.setActiveSession(session) }
     }
+
+    fun givenGetSafeActiveSessionReturns(session: Session?) {
+        every { instance.getSafeActiveSession() } returns session
+    }
 }
diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeCryptoService.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeCryptoService.kt
index ed571fc2f2..2c31933464 100644
--- a/vector/src/test/java/im/vector/app/test/fakes/FakeCryptoService.kt
+++ b/vector/src/test/java/im/vector/app/test/fakes/FakeCryptoService.kt
@@ -20,11 +20,15 @@ import androidx.lifecycle.MutableLiveData
 import io.mockk.mockk
 import org.matrix.android.sdk.api.session.crypto.CryptoService
 import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
+import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
+import org.matrix.android.sdk.api.util.Optional
 
 class FakeCryptoService : CryptoService by mockk() {
 
     var roomKeysExport = ByteArray(size = 1)
     var cryptoDeviceInfos = mutableMapOf<String, CryptoDeviceInfo>()
+    var cryptoDeviceInfoWithIdLiveData: MutableLiveData<Optional<CryptoDeviceInfo>> = MutableLiveData()
+    var myDevicesInfoWithIdLiveData: MutableLiveData<Optional<DeviceInfo>> = MutableLiveData()
 
     override suspend fun exportRoomKeys(password: String) = roomKeysExport
 
@@ -35,4 +39,8 @@ class FakeCryptoService : CryptoService by mockk() {
     override fun getLiveCryptoDeviceInfo(userIds: List<String>) = MutableLiveData(
             cryptoDeviceInfos.filterKeys { userIds.contains(it) }.values.toList()
     )
+
+    override fun getLiveCryptoDeviceInfoWithId(deviceId: String) = cryptoDeviceInfoWithIdLiveData
+
+    override fun getMyDevicesInfoLive(deviceId: String) = myDevicesInfoWithIdLiveData
 }
diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeFlowLiveDataConversions.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeFlowLiveDataConversions.kt
index 9abbcc174d..956a86f32e 100644
--- a/vector/src/test/java/im/vector/app/test/fakes/FakeFlowLiveDataConversions.kt
+++ b/vector/src/test/java/im/vector/app/test/fakes/FakeFlowLiveDataConversions.kt
@@ -28,6 +28,6 @@ class FakeFlowLiveDataConversions {
     }
 }
 
-fun <T> LiveData<T>.givenAsFlowReturns(value: T) {
-    every { asFlow() } returns flowOf(value)
+fun <T> LiveData<T>.givenAsFlow() {
+    every { asFlow() } returns flowOf(value!!)
 }

From 295ae55142e6c09256172d40509dfb37289d7235 Mon Sep 17 00:00:00 2001
From: Maxime NATUREL <maxime.naturel@niji.fr>
Date: Wed, 31 Aug 2022 14:12:16 +0200
Subject: [PATCH 011/108] Adding unit tests for mapper

---
 .../MyDeviceLastSeenInfoEntityMapper.kt       |  1 -
 .../MyDeviceLastSeenInfoEntityMapperTest.kt   | 52 +++++++++++++++++++
 2 files changed, 52 insertions(+), 1 deletion(-)
 create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/MyDeviceLastSeenInfoEntityMapperTest.kt

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/MyDeviceLastSeenInfoEntityMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/MyDeviceLastSeenInfoEntityMapper.kt
index ed44b0765a..76e3171f4d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/MyDeviceLastSeenInfoEntityMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/MyDeviceLastSeenInfoEntityMapper.kt
@@ -20,7 +20,6 @@ import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
 import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntity
 import javax.inject.Inject
 
-// TODO add unit tests
 internal class MyDeviceLastSeenInfoEntityMapper @Inject constructor() {
 
     fun map(entity: MyDeviceLastSeenInfoEntity): DeviceInfo {
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/MyDeviceLastSeenInfoEntityMapperTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/MyDeviceLastSeenInfoEntityMapperTest.kt
new file mode 100644
index 0000000000..e706fd6622
--- /dev/null
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/MyDeviceLastSeenInfoEntityMapperTest.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 org.matrix.android.sdk.internal.crypto.store.db.mapper
+
+import org.amshove.kluent.shouldBeEqualTo
+import org.junit.Test
+import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
+import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntity
+
+private const val A_DEVICE_ID = "device-id"
+private const val AN_IP_ADDRESS = "ip-address"
+private const val A_TIMESTAMP = 123L
+private const val A_DISPLAY_NAME = "display-name"
+
+class MyDeviceLastSeenInfoEntityMapperTest {
+
+    private val myDeviceLastSeenInfoEntityMapper = MyDeviceLastSeenInfoEntityMapper()
+
+    @Test
+    fun `given an entity when mapping to model then all fields are correctly mapped`() {
+        val entity = MyDeviceLastSeenInfoEntity(
+                deviceId = A_DEVICE_ID,
+                lastSeenIp = AN_IP_ADDRESS,
+                lastSeenTs = A_TIMESTAMP,
+                displayName = A_DISPLAY_NAME
+        )
+        val expectedDeviceInfo = DeviceInfo(
+                deviceId = A_DEVICE_ID,
+                lastSeenIp = AN_IP_ADDRESS,
+                lastSeenTs = A_TIMESTAMP,
+                displayName = A_DISPLAY_NAME
+        )
+
+        val deviceInfo = myDeviceLastSeenInfoEntityMapper.map(entity)
+
+        deviceInfo shouldBeEqualTo expectedDeviceInfo
+    }
+}

From 412fda27af501914c7a76c120a75e820dd084502 Mon Sep 17 00:00:00 2001
From: Maxime NATUREL <maxime.naturel@niji.fr>
Date: Wed, 31 Aug 2022 14:44:01 +0200
Subject: [PATCH 012/108] Adding unit tests for viewModel

---
 .../v2/overview/SessionOverviewViewModel.kt   |  1 -
 .../v2/overview/SessionOverviewViewState.kt   |  4 +
 .../overview/SessionOverviewViewModelTest.kt  | 80 +++++++++++++++++++
 .../im/vector/app/test/fakes/FakeSession.kt   |  5 ++
 4 files changed, 89 insertions(+), 1 deletion(-)
 create mode 100644 vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModelTest.kt

diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt
index 84c15301aa..9c40480270 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt
@@ -30,7 +30,6 @@ import kotlinx.coroutines.flow.mapNotNull
 import kotlinx.coroutines.flow.onEach
 import org.matrix.android.sdk.api.session.Session
 
-// TODO add unit tests
 class SessionOverviewViewModel @AssistedInject constructor(
         @Assisted val initialState: SessionOverviewViewState,
         session: Session,
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewState.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewState.kt
index e839348800..8fa19a6eee 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewState.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewState.kt
@@ -16,11 +16,15 @@
 
 package im.vector.app.features.settings.devices.v2.overview
 
+import com.airbnb.mvrx.Async
 import com.airbnb.mvrx.MavericksState
+import com.airbnb.mvrx.Uninitialized
+import im.vector.app.features.settings.devices.DeviceFullInfo
 
 data class SessionOverviewViewState(
         val sessionId: String,
         val isCurrentSession: Boolean = false,
+        val deviceInfo: Async<DeviceFullInfo> = Uninitialized,
 ) : MavericksState {
     constructor(args: SessionOverviewArgs) : this(
             sessionId = args.sessionId
diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModelTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModelTest.kt
new file mode 100644
index 0000000000..f15bc0860c
--- /dev/null
+++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModelTest.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.settings.devices.v2.overview
+
+import com.airbnb.mvrx.Success
+import com.airbnb.mvrx.test.MvRxTestRule
+import im.vector.app.features.settings.devices.DeviceFullInfo
+import im.vector.app.test.fakes.FakeSession
+import im.vector.app.test.test
+import io.mockk.every
+import io.mockk.mockk
+import io.mockk.verify
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Rule
+import org.junit.Test
+import org.matrix.android.sdk.api.auth.data.SessionParams
+import org.matrix.android.sdk.api.util.Optional
+
+private const val A_SESSION_ID = "session-id"
+
+class SessionOverviewViewModelTest {
+
+    @get:Rule
+    val mvRxTestRule = MvRxTestRule(testDispatcher = UnconfinedTestDispatcher())
+
+    private val args = SessionOverviewArgs(
+            sessionId = A_SESSION_ID
+    )
+    private val fakeSession = FakeSession()
+    private val getDeviceFullInfoUseCase = mockk<GetDeviceFullInfoUseCase>()
+
+    private fun createViewModel() = SessionOverviewViewModel(
+            initialState = SessionOverviewViewState(args),
+            session = fakeSession,
+            getDeviceFullInfoUseCase = getDeviceFullInfoUseCase
+    )
+
+    @Test
+    fun `given the viewModel has been initialized then viewState is updated with session info`() = runTest {
+        val sessionParams = givenIdForSession(A_SESSION_ID)
+        val deviceFullInfo = mockk<DeviceFullInfo>()
+        every { getDeviceFullInfoUseCase.execute(A_SESSION_ID) } returns flowOf(Optional(deviceFullInfo))
+        val expectedState = SessionOverviewViewState(
+                sessionId = A_SESSION_ID,
+                isCurrentSession = true,
+                deviceInfo = Success(deviceFullInfo)
+        )
+
+        val viewModel = createViewModel()
+
+        viewModel.test()
+                .assertLatestState { state -> state == expectedState }
+                .finish()
+        verify { sessionParams.deviceId }
+        verify { getDeviceFullInfoUseCase.execute(A_SESSION_ID) }
+    }
+
+    private fun givenIdForSession(deviceId: String): SessionParams {
+        val sessionParams = mockk<SessionParams>()
+        every { sessionParams.deviceId } returns deviceId
+        fakeSession.givenSessionParams(sessionParams)
+        return sessionParams
+    }
+}
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 ee016ecae3..71bcde5807 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
@@ -26,6 +26,7 @@ import io.mockk.coJustRun
 import io.mockk.every
 import io.mockk.mockk
 import io.mockk.mockkStatic
+import org.matrix.android.sdk.api.auth.data.SessionParams
 import org.matrix.android.sdk.api.session.Session
 import org.matrix.android.sdk.api.session.getRoomSummary
 import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
@@ -71,6 +72,10 @@ class FakeSession(
         }
     }
 
+    fun givenSessionParams(sessionParams: SessionParams) {
+        every { this@FakeSession.sessionParams } returns sessionParams
+    }
+
     companion object {
 
         fun withRoomSummary(roomSummary: RoomSummary) = FakeSession().apply {

From ca70eddaf5b1b0a68ed18478e4fb65eb4b3a4bb7 Mon Sep 17 00:00:00 2001
From: Maxime NATUREL <maxime.naturel@niji.fr>
Date: Thu, 1 Sep 2022 09:25:11 +0200
Subject: [PATCH 013/108] Introducing some reusable usecases

---
 .../devices/CurrentSessionCrossSigningInfo.kt | 26 ++++++++++
 .../settings/devices/DevicesViewModel.kt      | 28 +++--------
 ...etCurrentSessionCrossSigningInfoUseCase.kt | 37 ++++++++++++++
 ...yptionTrustLevelForCurrentDeviceUseCase.kt | 38 ++++++++++++++
 ...GetEncryptionTrustLevelForDeviceUseCase.kt | 40 +++++++++++++++
 ...cryptionTrustLevelForOtherDeviceUseCase.kt | 49 +++++++++++++++++++
 .../features/settings/devices/TrustUtils.kt   |  1 +
 .../v2/overview/GetDeviceFullInfoUseCase.kt   | 10 +++-
 8 files changed, 206 insertions(+), 23 deletions(-)
 create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/CurrentSessionCrossSigningInfo.kt
 create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/GetCurrentSessionCrossSigningInfoUseCase.kt
 create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForCurrentDeviceUseCase.kt
 create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForDeviceUseCase.kt
 create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForOtherDeviceUseCase.kt

diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/CurrentSessionCrossSigningInfo.kt b/vector/src/main/java/im/vector/app/features/settings/devices/CurrentSessionCrossSigningInfo.kt
new file mode 100644
index 0000000000..790de08823
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/CurrentSessionCrossSigningInfo.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.settings.devices
+
+/**
+ * Used to hold some info about the cross signing of the current Session.
+ */
+data class CurrentSessionCrossSigningInfo(
+        val deviceId: String?,
+        val isCrossSigningInitialized: Boolean,
+        val isCrossSigningVerified: Boolean,
+)
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewModel.kt
index 3b5bcb61d9..82c346b09c 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewModel.kt
@@ -101,6 +101,8 @@ class DevicesViewModel @AssistedInject constructor(
         private val stringProvider: StringProvider,
         private val matrix: Matrix,
         private val checkIfSessionIsInactiveUseCase: CheckIfSessionIsInactiveUseCase,
+        getCurrentSessionCrossSigningInfoUseCase: GetCurrentSessionCrossSigningInfoUseCase,
+        private val getEncryptionTrustLevelForDeviceUseCase: GetEncryptionTrustLevelForDeviceUseCase,
 ) : VectorViewModel<DevicesViewState, DevicesAction, DevicesViewEvents>(initialState), VerificationService.Listener {
 
     var uiaContinuation: Continuation<UIABaseAuth>? = null
@@ -116,8 +118,9 @@ class DevicesViewModel @AssistedInject constructor(
     private val refreshSource = PublishDataSource<Unit>()
 
     init {
-        val hasAccountCrossSigning = session.cryptoService().crossSigningService().isCrossSigningInitialized()
-        val accountCrossSigningIsTrusted = session.cryptoService().crossSigningService().isCrossSigningVerified()
+        val currentSessionCrossSigningInfo = getCurrentSessionCrossSigningInfoUseCase.execute()
+        val hasAccountCrossSigning = currentSessionCrossSigningInfo.isCrossSigningInitialized
+        val accountCrossSigningIsTrusted = currentSessionCrossSigningInfo.isCrossSigningVerified
 
         setState {
             copy(
@@ -143,12 +146,7 @@ class DevicesViewModel @AssistedInject constructor(
                     .sortedByDescending { it.lastSeenTs }
                     .map { deviceInfo ->
                         val cryptoDeviceInfo = cryptoList.firstOrNull { it.deviceId == deviceInfo.deviceId }
-                        val trustLevelForShield = computeTrustLevelForShield(
-                                currentSessionCrossTrusted = accountCrossSigningIsTrusted,
-                                legacyMode = !hasAccountCrossSigning,
-                                deviceTrustLevel = cryptoDeviceInfo?.trustLevel,
-                                isCurrentDevice = deviceInfo.deviceId == session.sessionParams.deviceId
-                        )
+                        val trustLevelForShield = getEncryptionTrustLevelForDeviceUseCase.execute(currentSessionCrossSigningInfo, cryptoDeviceInfo)
                         val isInactive = checkIfSessionIsInactiveUseCase.execute(deviceInfo.lastSeenTs ?: 0)
                         DeviceFullInfo(deviceInfo, cryptoDeviceInfo, trustLevelForShield, isInactive)
                     }
@@ -268,20 +266,6 @@ class DevicesViewModel @AssistedInject constructor(
         }
     }
 
-    private fun computeTrustLevelForShield(
-            currentSessionCrossTrusted: Boolean,
-            legacyMode: Boolean,
-            deviceTrustLevel: DeviceTrustLevel?,
-            isCurrentDevice: Boolean,
-    ): RoomEncryptionTrustLevel {
-        return TrustUtils.shieldForTrust(
-                currentDevice = isCurrentDevice,
-                trustMSK = currentSessionCrossTrusted,
-                legacyMode = legacyMode,
-                deviceTrustLevel = deviceTrustLevel
-        )
-    }
-
     private fun handleInteractiveVerification(action: DevicesAction.VerifyMyDevice) {
         val txID = session.cryptoService()
                 .verificationService()
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/GetCurrentSessionCrossSigningInfoUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/GetCurrentSessionCrossSigningInfoUseCase.kt
new file mode 100644
index 0000000000..aa0de9ddf1
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/GetCurrentSessionCrossSigningInfoUseCase.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.settings.devices
+
+import im.vector.app.core.di.ActiveSessionHolder
+import javax.inject.Inject
+
+// TODO add unit tests
+class GetCurrentSessionCrossSigningInfoUseCase @Inject constructor(
+        private val activeSessionHolder: ActiveSessionHolder,
+) {
+
+    fun execute(): CurrentSessionCrossSigningInfo {
+        val session = activeSessionHolder.getActiveSession()
+        val isCrossSigningInitialized = session.cryptoService().crossSigningService().isCrossSigningInitialized()
+        val isCrossSigningVerified = session.cryptoService().crossSigningService().isCrossSigningVerified()
+        return CurrentSessionCrossSigningInfo(
+                deviceId = session.sessionParams.deviceId,
+                isCrossSigningInitialized = isCrossSigningInitialized,
+                isCrossSigningVerified = isCrossSigningVerified
+        )
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForCurrentDeviceUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForCurrentDeviceUseCase.kt
new file mode 100644
index 0000000000..eaa72b424a
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForCurrentDeviceUseCase.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.features.settings.devices
+
+import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
+import javax.inject.Inject
+
+// TODO add unit tests
+class GetEncryptionTrustLevelForCurrentDeviceUseCase @Inject constructor() {
+
+    fun execute(trustMSK: Boolean, legacyMode: Boolean): RoomEncryptionTrustLevel {
+        return if (legacyMode) {
+            // In legacy, current session is always trusted
+            RoomEncryptionTrustLevel.Trusted
+        } else {
+            // If current session doesn't trust MSK, show red shield for current device
+            if (trustMSK) {
+                RoomEncryptionTrustLevel.Trusted
+            } else {
+                RoomEncryptionTrustLevel.Warning
+            }
+        }
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForDeviceUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForDeviceUseCase.kt
new file mode 100644
index 0000000000..d988f728ae
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForDeviceUseCase.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.settings.devices
+
+import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
+import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
+import javax.inject.Inject
+
+// TODO add unit tests
+class GetEncryptionTrustLevelForDeviceUseCase @Inject constructor(
+        private val getEncryptionTrustLevelForCurrentDeviceUseCase: GetEncryptionTrustLevelForCurrentDeviceUseCase,
+        private val getEncryptionTrustLevelForOtherDeviceUseCase: GetEncryptionTrustLevelForOtherDeviceUseCase,
+) {
+
+    fun execute(currentSessionCrossSigningInfo: CurrentSessionCrossSigningInfo, cryptoDeviceInfo: CryptoDeviceInfo?): RoomEncryptionTrustLevel {
+        val legacyMode = !currentSessionCrossSigningInfo.isCrossSigningInitialized
+        val trustMSK = currentSessionCrossSigningInfo.isCrossSigningVerified
+        val isCurrentDevice = !cryptoDeviceInfo?.deviceId.isNullOrEmpty() && cryptoDeviceInfo?.deviceId == currentSessionCrossSigningInfo.deviceId
+        val deviceTrustLevel = cryptoDeviceInfo?.trustLevel
+
+        return when {
+            isCurrentDevice -> getEncryptionTrustLevelForCurrentDeviceUseCase.execute(trustMSK, legacyMode)
+            else -> getEncryptionTrustLevelForOtherDeviceUseCase.execute(trustMSK, legacyMode, deviceTrustLevel)
+        }
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForOtherDeviceUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForOtherDeviceUseCase.kt
new file mode 100644
index 0000000000..41cdae23a4
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForOtherDeviceUseCase.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.settings.devices
+
+import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
+import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
+import javax.inject.Inject
+
+// TODO add unit tests
+class GetEncryptionTrustLevelForOtherDeviceUseCase @Inject constructor() {
+
+    fun execute(trustMSK: Boolean, legacyMode: Boolean, deviceTrustLevel: DeviceTrustLevel?): RoomEncryptionTrustLevel {
+        return if (legacyMode) {
+            // use local trust
+            if (deviceTrustLevel?.locallyVerified == true) {
+                RoomEncryptionTrustLevel.Trusted
+            } else {
+                RoomEncryptionTrustLevel.Warning
+            }
+        } else {
+            if (trustMSK) {
+                // use cross sign trust, put locally trusted in black
+                when {
+                    deviceTrustLevel?.crossSigningVerified == true -> RoomEncryptionTrustLevel.Trusted
+                    deviceTrustLevel?.locallyVerified == true -> RoomEncryptionTrustLevel.Default
+                    else -> RoomEncryptionTrustLevel.Warning
+                }
+            } else {
+                // The current session is untrusted, so displays others in black
+                // as we can't know the cross-signing state
+                RoomEncryptionTrustLevel.Default
+            }
+        }
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/TrustUtils.kt b/vector/src/main/java/im/vector/app/features/settings/devices/TrustUtils.kt
index da18154ea1..7709a63344 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/TrustUtils.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/TrustUtils.kt
@@ -19,6 +19,7 @@ package im.vector.app.features.settings.devices
 import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
 import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
 
+// TODO Replace usage by the use case GetEncryptionTrustLevelForDeviceUseCase
 object TrustUtils {
 
     fun shieldForTrust(
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt
index d20ca17471..51252de34a 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt
@@ -19,6 +19,8 @@ package im.vector.app.features.settings.devices.v2.overview
 import androidx.lifecycle.asFlow
 import im.vector.app.core.di.ActiveSessionHolder
 import im.vector.app.features.settings.devices.DeviceFullInfo
+import im.vector.app.features.settings.devices.GetCurrentSessionCrossSigningInfoUseCase
+import im.vector.app.features.settings.devices.GetEncryptionTrustLevelForDeviceUseCase
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.emptyFlow
@@ -26,12 +28,16 @@ import org.matrix.android.sdk.api.util.Optional
 import org.matrix.android.sdk.api.util.toOptional
 import javax.inject.Inject
 
+// TODO update unit test
 class GetDeviceFullInfoUseCase @Inject constructor(
         private val activeSessionHolder: ActiveSessionHolder,
+        private val getCurrentSessionCrossSigningInfoUseCase: GetCurrentSessionCrossSigningInfoUseCase,
+        private val getEncryptionTrustLevelForDeviceUseCase: GetEncryptionTrustLevelForDeviceUseCase,
 ) {
 
     fun execute(deviceId: String): Flow<Optional<DeviceFullInfo>> {
         return activeSessionHolder.getSafeActiveSession()?.let { session ->
+            val currentSessionCrossSigningInfo = getCurrentSessionCrossSigningInfoUseCase.execute()
             combine(
                     session.cryptoService().getMyDevicesInfoLive(deviceId).asFlow(),
                     session.cryptoService().getLiveCryptoDeviceInfoWithId(deviceId).asFlow()
@@ -39,9 +45,11 @@ class GetDeviceFullInfoUseCase @Inject constructor(
                 val info = deviceInfo.getOrNull()
                 val cryptoInfo = cryptoDeviceInfo.getOrNull()
                 val fullInfo = if (info != null && cryptoInfo != null) {
+                    val roomEncryptionTrustLevel = getEncryptionTrustLevelForDeviceUseCase.execute(currentSessionCrossSigningInfo, cryptoInfo)
                     DeviceFullInfo(
                             deviceInfo = info,
-                            cryptoDeviceInfo = cryptoInfo
+                            cryptoDeviceInfo = cryptoInfo,
+                            trustLevelForShield = roomEncryptionTrustLevel
                     )
                 } else {
                     null

From 7c32884df541c03ac9d523334e182bb5f5690049 Mon Sep 17 00:00:00 2001
From: Maxime NATUREL <maxime.naturel@niji.fr>
Date: Thu, 1 Sep 2022 09:32:14 +0200
Subject: [PATCH 014/108] Renaming CurrentSessionView into SessionInfoView to
 be more generic

---
 .../devices/v2/list/CurrentSessionView.kt     | 78 -------------------
 .../devices/v2/list/SessionInfoView.kt        | 78 +++++++++++++++++++
 ...rent_session.xml => view_session_info.xml} | 26 +++----
 3 files changed, 91 insertions(+), 91 deletions(-)
 delete mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/list/CurrentSessionView.kt
 create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoView.kt
 rename vector/src/main/res/layout/{view_current_session.xml => view_session_info.xml} (78%)

diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/CurrentSessionView.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/CurrentSessionView.kt
deleted file mode 100644
index 1ce035931f..0000000000
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/CurrentSessionView.kt
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (c) 2022 New Vector Ltd
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package im.vector.app.features.settings.devices.v2.list
-
-import android.content.Context
-import android.util.AttributeSet
-import androidx.constraintlayout.widget.ConstraintLayout
-import androidx.core.view.isVisible
-import im.vector.app.R
-import im.vector.app.databinding.ViewCurrentSessionBinding
-import im.vector.app.features.settings.devices.DeviceFullInfo
-import im.vector.app.features.themes.ThemeUtils
-import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
-
-class CurrentSessionView @JvmOverloads constructor(
-        context: Context,
-        attrs: AttributeSet? = null,
-        defStyleAttr: Int = 0
-) : ConstraintLayout(context, attrs, defStyleAttr) {
-
-    private val views: ViewCurrentSessionBinding
-
-    init {
-        inflate(context, R.layout.view_current_session, this)
-        views = ViewCurrentSessionBinding.bind(this)
-    }
-
-    val viewDetailsButton = views.currentSessionViewDetailsButton
-
-    fun render(currentDeviceInfo: DeviceFullInfo) {
-        renderDeviceInfo(currentDeviceInfo.deviceInfo.displayName.orEmpty())
-        renderVerificationStatus(currentDeviceInfo.trustLevelForShield)
-    }
-
-    private fun renderVerificationStatus(trustLevelForShield: RoomEncryptionTrustLevel) {
-        views.currentSessionVerificationStatusImageView.render(trustLevelForShield)
-        if (trustLevelForShield == RoomEncryptionTrustLevel.Trusted) {
-            renderCrossSigningVerified()
-        } else {
-            renderCrossSigningUnverified()
-        }
-    }
-
-    private fun renderCrossSigningVerified() {
-        views.currentSessionVerificationStatusTextView.text = context.getString(R.string.device_manager_verification_status_verified)
-        views.currentSessionVerificationStatusTextView.setTextColor(ThemeUtils.getColor(context, R.attr.colorPrimary))
-        views.currentSessionVerificationStatusDetailTextView.text = context.getString(R.string.device_manager_verification_status_detail_verified)
-        views.currentSessionVerifySessionButton.isVisible = false
-    }
-
-    private fun renderCrossSigningUnverified() {
-        views.currentSessionVerificationStatusTextView.text = context.getString(R.string.device_manager_verification_status_unverified)
-        views.currentSessionVerificationStatusTextView.setTextColor(ThemeUtils.getColor(context, R.attr.colorError))
-        views.currentSessionVerificationStatusDetailTextView.text = context.getString(R.string.device_manager_verification_status_detail_unverified)
-        views.currentSessionVerifySessionButton.isVisible = true
-    }
-
-    // TODO. We don't have this info yet. Update later accordingly.
-    private fun renderDeviceInfo(sessionName: String) {
-        views.currentSessionDeviceTypeImageView.setImageResource(R.drawable.ic_device_type_mobile)
-        views.currentSessionDeviceTypeImageView.contentDescription = context.getString(R.string.a11y_device_manager_device_type_mobile)
-        views.currentSessionNameTextView.text = sessionName
-    }
-}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoView.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoView.kt
new file mode 100644
index 0000000000..b79adfb2d4
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoView.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.settings.devices.v2.list
+
+import android.content.Context
+import android.util.AttributeSet
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.view.isVisible
+import im.vector.app.R
+import im.vector.app.databinding.ViewSessionInfoBinding
+import im.vector.app.features.settings.devices.DeviceFullInfo
+import im.vector.app.features.themes.ThemeUtils
+import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
+
+class SessionInfoView @JvmOverloads constructor(
+        context: Context,
+        attrs: AttributeSet? = null,
+        defStyleAttr: Int = 0
+) : ConstraintLayout(context, attrs, defStyleAttr) {
+
+    private val views: ViewSessionInfoBinding
+
+    init {
+        inflate(context, R.layout.view_session_info, this)
+        views = ViewSessionInfoBinding.bind(this)
+    }
+
+    val viewDetailsButton = views.sessionInfoViewDetailsButton
+
+    fun render(deviceInfo: DeviceFullInfo) {
+        renderDeviceInfo(deviceInfo.deviceInfo.displayName.orEmpty())
+        renderVerificationStatus(deviceInfo.trustLevelForShield)
+    }
+
+    private fun renderVerificationStatus(trustLevelForShield: RoomEncryptionTrustLevel) {
+        views.sessionInfoVerificationStatusImageView.render(trustLevelForShield)
+        if (trustLevelForShield == RoomEncryptionTrustLevel.Trusted) {
+            renderCrossSigningVerified()
+        } else {
+            renderCrossSigningUnverified()
+        }
+    }
+
+    private fun renderCrossSigningVerified() {
+        views.sessionInfoVerificationStatusTextView.text = context.getString(R.string.device_manager_verification_status_verified)
+        views.sessionInfoVerificationStatusTextView.setTextColor(ThemeUtils.getColor(context, R.attr.colorPrimary))
+        views.sessionInfoVerificationStatusDetailTextView.text = context.getString(R.string.device_manager_verification_status_detail_verified)
+        views.sessionInfoVerifySessionButton.isVisible = false
+    }
+
+    private fun renderCrossSigningUnverified() {
+        views.sessionInfoVerificationStatusTextView.text = context.getString(R.string.device_manager_verification_status_unverified)
+        views.sessionInfoVerificationStatusTextView.setTextColor(ThemeUtils.getColor(context, R.attr.colorError))
+        views.sessionInfoVerificationStatusDetailTextView.text = context.getString(R.string.device_manager_verification_status_detail_unverified)
+        views.sessionInfoVerifySessionButton.isVisible = true
+    }
+
+    // TODO. We don't have this info yet. Update later accordingly.
+    private fun renderDeviceInfo(sessionName: String) {
+        views.sessionInfoDeviceTypeImageView.setImageResource(R.drawable.ic_device_type_mobile)
+        views.sessionInfoDeviceTypeImageView.contentDescription = context.getString(R.string.a11y_device_manager_device_type_mobile)
+        views.sessionInfoNameTextView.text = sessionName
+    }
+}
diff --git a/vector/src/main/res/layout/view_current_session.xml b/vector/src/main/res/layout/view_session_info.xml
similarity index 78%
rename from vector/src/main/res/layout/view_current_session.xml
rename to vector/src/main/res/layout/view_session_info.xml
index 91977eba40..015f4961c9 100644
--- a/vector/src/main/res/layout/view_current_session.xml
+++ b/vector/src/main/res/layout/view_session_info.xml
@@ -8,7 +8,7 @@
     android:paddingBottom="16dp">
 
     <ImageView
-        android:id="@+id/currentSessionDeviceTypeImageView"
+        android:id="@+id/sessionInfoDeviceTypeImageView"
         android:layout_width="40dp"
         android:layout_height="40dp"
         android:layout_marginTop="16dp"
@@ -21,18 +21,18 @@
         tools:src="@drawable/ic_device_type_mobile" />
 
     <TextView
-        android:id="@+id/currentSessionNameTextView"
+        android:id="@+id/sessionInfoNameTextView"
         style="@style/TextAppearance.Vector.Subtitle.Medium.DevicesManagement"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_marginTop="4dp"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/currentSessionDeviceTypeImageView"
+        app:layout_constraintTop_toBottomOf="@id/sessionInfoDeviceTypeImageView"
         tools:text="Element Mobile: Android" />
 
     <LinearLayout
-        android:id="@+id/currentSessionVerificationStatusContainer"
+        android:id="@+id/sessionInfoVerificationStatusContainer"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_marginTop="12dp"
@@ -40,17 +40,17 @@
         android:orientation="horizontal"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/currentSessionNameTextView">
+        app:layout_constraintTop_toBottomOf="@id/sessionInfoNameTextView">
 
         <im.vector.app.core.ui.views.ShieldImageView
-            android:id="@+id/currentSessionVerificationStatusImageView"
+            android:id="@+id/sessionInfoVerificationStatusImageView"
             android:layout_width="16dp"
             android:layout_height="16dp"
             android:importantForAccessibility="no"
             tools:src="@drawable/ic_shield_trusted" />
 
         <TextView
-            android:id="@+id/currentSessionVerificationStatusTextView"
+            android:id="@+id/sessionInfoVerificationStatusTextView"
             style="@style/TextAppearance.Vector.Body"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
@@ -60,7 +60,7 @@
     </LinearLayout>
 
     <TextView
-        android:id="@+id/currentSessionVerificationStatusDetailTextView"
+        android:id="@+id/sessionInfoVerificationStatusDetailTextView"
         style="@style/TextAppearance.Vector.Body.DevicesManagement"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
@@ -69,11 +69,11 @@
         android:gravity="center"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/currentSessionVerificationStatusContainer"
+        app:layout_constraintTop_toBottomOf="@id/sessionInfoVerificationStatusContainer"
         tools:text="@string/device_manager_verification_status_detail_verified" />
 
     <Button
-        android:id="@+id/currentSessionVerifySessionButton"
+        android:id="@+id/sessionInfoVerifySessionButton"
         android:layout_width="0dp"
         android:layout_height="52dp"
         android:layout_marginHorizontal="24dp"
@@ -81,10 +81,10 @@
         android:text="@string/device_manager_verify_session"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/currentSessionVerificationStatusDetailTextView" />
+        app:layout_constraintTop_toBottomOf="@id/sessionInfoVerificationStatusDetailTextView" />
 
     <Button
-        android:id="@+id/currentSessionViewDetailsButton"
+        android:id="@+id/sessionInfoViewDetailsButton"
         style="@style/Widget.Vector.Button.Text"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
@@ -93,6 +93,6 @@
         android:text="@string/device_manager_view_details"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/currentSessionVerifySessionButton" />
+        app:layout_constraintTop_toBottomOf="@id/sessionInfoVerifySessionButton" />
 
 </androidx.constraintlayout.widget.ConstraintLayout>

From b626a1e4f9375f99fd0c1e3fdd600e3bc9f80a30 Mon Sep 17 00:00:00 2001
From: Maxime NATUREL <maxime.naturel@niji.fr>
Date: Thu, 1 Sep 2022 10:05:24 +0200
Subject: [PATCH 015/108] Show info in overview screen

---
 .../src/main/res/values/strings.xml           | 10 ++++-
 .../v2/VectorSettingsDevicesFragment.kt       |  7 +++-
 .../devices/v2/list/SessionInfoView.kt        | 40 +++++++++++++------
 .../devices/v2/list/SessionInfoViewState.kt   | 25 ++++++++++++
 .../v2/overview/SessionOverviewFragment.kt    | 32 +++++++++++++--
 .../res/layout/fragment_session_overview.xml  | 20 ++++++++++
 .../fragment_settings_session_overview.xml    |  6 ---
 .../src/main/res/layout/view_session_info.xml |  2 +-
 8 files changed, 115 insertions(+), 27 deletions(-)
 create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoViewState.kt
 create mode 100644 vector/src/main/res/layout/fragment_session_overview.xml
 delete mode 100644 vector/src/main/res/layout/fragment_settings_session_overview.xml

diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml
index 2b8501a249..15dd579386 100644
--- a/library/ui-strings/src/main/res/values/strings.xml
+++ b/library/ui-strings/src/main/res/values/strings.xml
@@ -3217,8 +3217,14 @@
     <string name="a11y_device_manager_device_type_unknown">Unknown device type</string>
     <string name="device_manager_verification_status_verified">Verified session</string>
     <string name="device_manager_verification_status_unverified">Unverified session</string>
-    <string name="device_manager_verification_status_detail_verified">Your current session is ready for secure messaging.</string>
-    <string name="device_manager_verification_status_detail_unverified">Verify your current session for enhanced secure messaging.</string>
+    <!-- TODO TO BE REMOVED: replaced by device_manager_verification_status_detail_current_session_verified -->
+    <string name="device_manager_verification_status_detail_verified" tools:ignore="UnusedResources">Your current session is ready for secure messaging.</string>
+    <!-- TODO TO BE REMOVED: replaced by device_manager_verification_status_detail_current_session_unverified -->
+    <string name="device_manager_verification_status_detail_unverified" tools:ignore="UnusedResources">Verify your current session for enhanced secure messaging.</string>
+    <string name="device_manager_verification_status_detail_current_session_verified">Your current session is ready for secure messaging.</string>
+    <string name="device_manager_verification_status_detail_other_session_verified">This session is ready for secure messaging.</string>
+    <string name="device_manager_verification_status_detail_current_session_unverified">Verify your current session for enhanced secure messaging.</string>
+    <string name="device_manager_verification_status_detail_other_session_unverified">Verify or sign out from this session for best security and reliability.</string>
     <string name="device_manager_verify_session">Verify Session</string>
     <string name="device_manager_view_details">View Details</string>
     <string name="device_manager_header_section_current_session">Current Session</string>
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt
index 2adf7969bf..8bab4ebd60 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt
@@ -42,6 +42,7 @@ import im.vector.app.features.settings.devices.DevicesViewEvents
 import im.vector.app.features.settings.devices.DevicesViewModel
 import im.vector.app.features.settings.devices.v2.list.SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS
 import im.vector.app.features.settings.devices.v2.list.SecurityRecommendationViewState
+import im.vector.app.features.settings.devices.v2.list.SessionInfoViewState
 import javax.inject.Inject
 
 /**
@@ -199,7 +200,11 @@ class VectorSettingsDevicesFragment :
         currentDeviceInfo?.let {
             views.deviceListHeaderCurrentSession.isVisible = true
             views.deviceListCurrentSession.isVisible = true
-            views.deviceListCurrentSession.render(it)
+            val viewState = SessionInfoViewState(
+                    isCurrentSession = true,
+                    deviceFullInfo = it
+            )
+            views.deviceListCurrentSession.render(viewState)
             views.deviceListCurrentSession.debouncedClicks {
                 currentDeviceInfo.deviceInfo.deviceId?.let { deviceId -> navigateToSessionOverview(deviceId) }
             }
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoView.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoView.kt
index b79adfb2d4..be6cfad1c8 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoView.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoView.kt
@@ -22,7 +22,6 @@ import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.core.view.isVisible
 import im.vector.app.R
 import im.vector.app.databinding.ViewSessionInfoBinding
-import im.vector.app.features.settings.devices.DeviceFullInfo
 import im.vector.app.features.themes.ThemeUtils
 import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
 
@@ -41,31 +40,42 @@ class SessionInfoView @JvmOverloads constructor(
 
     val viewDetailsButton = views.sessionInfoViewDetailsButton
 
-    fun render(deviceInfo: DeviceFullInfo) {
-        renderDeviceInfo(deviceInfo.deviceInfo.displayName.orEmpty())
-        renderVerificationStatus(deviceInfo.trustLevelForShield)
+    fun render(sessionInfoViewState: SessionInfoViewState) {
+        renderDeviceInfo(sessionInfoViewState.deviceFullInfo.deviceInfo.displayName.orEmpty())
+        renderVerificationStatus(sessionInfoViewState.deviceFullInfo.trustLevelForShield, sessionInfoViewState.isCurrentSession)
+        renderDetailsButton(sessionInfoViewState.isDetailsButtonVisible)
     }
 
-    private fun renderVerificationStatus(trustLevelForShield: RoomEncryptionTrustLevel) {
-        views.sessionInfoVerificationStatusImageView.render(trustLevelForShield)
-        if (trustLevelForShield == RoomEncryptionTrustLevel.Trusted) {
-            renderCrossSigningVerified()
+    private fun renderVerificationStatus(encryptionTrustLevel: RoomEncryptionTrustLevel, isCurrentSession: Boolean) {
+        views.sessionInfoVerificationStatusImageView.render(encryptionTrustLevel)
+        if (encryptionTrustLevel == RoomEncryptionTrustLevel.Trusted) {
+            renderCrossSigningVerified(isCurrentSession)
         } else {
-            renderCrossSigningUnverified()
+            renderCrossSigningUnverified(isCurrentSession)
         }
     }
 
-    private fun renderCrossSigningVerified() {
+    private fun renderCrossSigningVerified(isCurrentSession: Boolean) {
         views.sessionInfoVerificationStatusTextView.text = context.getString(R.string.device_manager_verification_status_verified)
         views.sessionInfoVerificationStatusTextView.setTextColor(ThemeUtils.getColor(context, R.attr.colorPrimary))
-        views.sessionInfoVerificationStatusDetailTextView.text = context.getString(R.string.device_manager_verification_status_detail_verified)
+        val statusResId = if (isCurrentSession) {
+            R.string.device_manager_verification_status_detail_current_session_verified
+        } else {
+            R.string.device_manager_verification_status_detail_other_session_verified
+        }
+        views.sessionInfoVerificationStatusDetailTextView.text = context.getString(statusResId)
         views.sessionInfoVerifySessionButton.isVisible = false
     }
 
-    private fun renderCrossSigningUnverified() {
+    private fun renderCrossSigningUnverified(isCurrentSession: Boolean) {
         views.sessionInfoVerificationStatusTextView.text = context.getString(R.string.device_manager_verification_status_unverified)
         views.sessionInfoVerificationStatusTextView.setTextColor(ThemeUtils.getColor(context, R.attr.colorError))
-        views.sessionInfoVerificationStatusDetailTextView.text = context.getString(R.string.device_manager_verification_status_detail_unverified)
+        val statusResId = if (isCurrentSession) {
+            R.string.device_manager_verification_status_detail_current_session_unverified
+        } else {
+            R.string.device_manager_verification_status_detail_other_session_unverified
+        }
+        views.sessionInfoVerificationStatusDetailTextView.text = context.getString(statusResId)
         views.sessionInfoVerifySessionButton.isVisible = true
     }
 
@@ -75,4 +85,8 @@ class SessionInfoView @JvmOverloads constructor(
         views.sessionInfoDeviceTypeImageView.contentDescription = context.getString(R.string.a11y_device_manager_device_type_mobile)
         views.sessionInfoNameTextView.text = sessionName
     }
+
+    private fun renderDetailsButton(isDetailsButtonVisible: Boolean) {
+        views.sessionInfoViewDetailsButton.isVisible = isDetailsButtonVisible
+    }
 }
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoViewState.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoViewState.kt
new file mode 100644
index 0000000000..c9a351f568
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoViewState.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.settings.devices.v2.list
+
+import im.vector.app.features.settings.devices.DeviceFullInfo
+
+data class SessionInfoViewState(
+        val isCurrentSession: Boolean,
+        val deviceFullInfo: DeviceFullInfo,
+        val isDetailsButtonVisible: Boolean = true,
+)
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt
index 1b8b231a5c..60d58c8a8d 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt
@@ -19,28 +19,38 @@ package im.vector.app.features.settings.devices.v2.overview
 import android.view.LayoutInflater
 import android.view.ViewGroup
 import androidx.appcompat.app.AppCompatActivity
+import androidx.core.view.isGone
+import androidx.core.view.isVisible
+import com.airbnb.mvrx.Success
 import com.airbnb.mvrx.fragmentViewModel
 import com.airbnb.mvrx.withState
 import dagger.hilt.android.AndroidEntryPoint
 import im.vector.app.R
 import im.vector.app.core.platform.VectorBaseFragment
-import im.vector.app.databinding.FragmentSettingsSessionOverviewBinding
+import im.vector.app.databinding.FragmentSessionOverviewBinding
+import im.vector.app.features.settings.devices.DeviceFullInfo
+import im.vector.app.features.settings.devices.v2.list.SessionInfoViewState
 
 /**
  * Display the overview info about a Session.
  */
 @AndroidEntryPoint
 class SessionOverviewFragment :
-        VectorBaseFragment<FragmentSettingsSessionOverviewBinding>() {
+        VectorBaseFragment<FragmentSessionOverviewBinding>() {
 
     private val viewModel: SessionOverviewViewModel by fragmentViewModel()
 
-    override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentSettingsSessionOverviewBinding {
-        return FragmentSettingsSessionOverviewBinding.inflate(inflater, container, false)
+    override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentSessionOverviewBinding {
+        return FragmentSessionOverviewBinding.inflate(inflater, container, false)
     }
 
     override fun invalidate() = withState(viewModel) { state ->
         updateToolbar(state.isCurrentSession)
+        if (state.deviceInfo is Success) {
+            renderSessionInfo(state.isCurrentSession, state.deviceInfo.invoke())
+        } else {
+            hideSessionInfo()
+        }
     }
 
     private fun updateToolbar(isCurrentSession: Boolean) {
@@ -49,4 +59,18 @@ class SessionOverviewFragment :
                 ?.supportActionBar
                 ?.setTitle(titleResId)
     }
+
+    private fun renderSessionInfo(isCurrentSession: Boolean, deviceFullInfo: DeviceFullInfo) {
+        views.sessionOverviewInfo.isVisible = true
+        val viewState = SessionInfoViewState(
+                isCurrentSession = isCurrentSession,
+                deviceFullInfo = deviceFullInfo,
+                isDetailsButtonVisible = false
+        )
+        views.sessionOverviewInfo.render(viewState)
+    }
+
+    private fun hideSessionInfo() {
+        views.sessionOverviewInfo.isGone = true
+    }
 }
diff --git a/vector/src/main/res/layout/fragment_session_overview.xml b/vector/src/main/res/layout/fragment_session_overview.xml
new file mode 100644
index 0000000000..156e61673b
--- /dev/null
+++ b/vector/src/main/res/layout/fragment_session_overview.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <im.vector.app.features.settings.devices.v2.list.SessionInfoView
+        android:id="@+id/sessionOverviewInfo"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginHorizontal="16dp"
+        android:layout_marginVertical="24dp"
+        android:visibility="gone"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:visibility="visible" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/vector/src/main/res/layout/fragment_settings_session_overview.xml b/vector/src/main/res/layout/fragment_settings_session_overview.xml
deleted file mode 100644
index 1354408486..0000000000
--- a/vector/src/main/res/layout/fragment_settings_session_overview.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-
-</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/vector/src/main/res/layout/view_session_info.xml b/vector/src/main/res/layout/view_session_info.xml
index 015f4961c9..02aad7b19d 100644
--- a/vector/src/main/res/layout/view_session_info.xml
+++ b/vector/src/main/res/layout/view_session_info.xml
@@ -70,7 +70,7 @@
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toBottomOf="@id/sessionInfoVerificationStatusContainer"
-        tools:text="@string/device_manager_verification_status_detail_verified" />
+        tools:text="@string/device_manager_verification_status_detail_current_session_verified" />
 
     <Button
         android:id="@+id/sessionInfoVerifySessionButton"

From 30710f7f15db0730236daa9737b41c08ac2468a7 Mon Sep 17 00:00:00 2001
From: Maxime NATUREL <maxime.naturel@niji.fr>
Date: Thu, 1 Sep 2022 10:43:17 +0200
Subject: [PATCH 016/108] Navigation from other session item

---
 .../devices/v2/VectorSettingsDevicesFragment.kt        | 10 ++++++++++
 .../settings/devices/v2/list/OtherSessionItem.kt       | 10 ++++++++++
 .../devices/v2/list/OtherSessionsController.kt         |  7 +++++++
 .../settings/devices/v2/list/OtherSessionsView.kt      |  5 +++++
 vector/src/main/res/layout/item_other_session.xml      |  1 +
 5 files changed, 33 insertions(+)

diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt
index 8bab4ebd60..f7f6ca6db4 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt
@@ -42,6 +42,7 @@ import im.vector.app.features.settings.devices.DevicesViewEvents
 import im.vector.app.features.settings.devices.DevicesViewModel
 import im.vector.app.features.settings.devices.v2.list.SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS
 import im.vector.app.features.settings.devices.v2.list.SecurityRecommendationViewState
+import im.vector.app.features.settings.devices.v2.list.OtherSessionsController
 import im.vector.app.features.settings.devices.v2.list.SessionInfoViewState
 import javax.inject.Inject
 
@@ -76,6 +77,7 @@ class VectorSettingsDevicesFragment :
 
         initLearnMoreButtons()
         initWaitingView()
+        initOtherSessionsView()
         observeViewEvents()
     }
 
@@ -114,6 +116,14 @@ class VectorSettingsDevicesFragment :
         views.waitingView.waitingStatusText.isVisible = true
     }
 
+    private fun initOtherSessionsView() {
+        views.deviceListOtherSessions.setCallback(object : OtherSessionsController.Callback {
+            override fun onItemClicked(deviceId: String) {
+                navigateToSessionOverview(deviceId)
+            }
+        })
+    }
+
     override fun onDestroyView() {
         cleanUpLearnMoreButtonsListeners()
         super.onDestroyView()
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionItem.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionItem.kt
index e9376953e0..c73389d775 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionItem.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionItem.kt
@@ -22,8 +22,10 @@ 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.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.resources.StringProvider
 import im.vector.app.core.ui.views.ShieldImageView
 import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
@@ -49,8 +51,16 @@ abstract class OtherSessionItem : VectorEpoxyModel<OtherSessionItem.Holder>(R.la
     @EpoxyAttribute
     lateinit var stringProvider: StringProvider
 
+    @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
+    var clickListener: ClickListener? = null
+
     override fun bind(holder: Holder) {
         super.bind(holder)
+        holder.view.onClick(clickListener)
+        if (clickListener == null) {
+            holder.view.isClickable = false
+        }
+
         when (deviceType) {
             DeviceType.MOBILE -> {
                 holder.otherSessionDeviceTypeImageView.setImageResource(R.drawable.ic_device_type_mobile)
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt
index 8a5ee05af7..6419d02fc9 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt
@@ -35,6 +35,12 @@ class OtherSessionsController @Inject constructor(
         private val colorProvider: ColorProvider,
 ) : TypedEpoxyController<List<DeviceFullInfo>>() {
 
+    var callback: Callback? = null
+
+    interface Callback {
+        fun onItemClicked(deviceId: String)
+    }
+
     override fun buildModels(data: List<DeviceFullInfo>?) {
         val host = this
 
@@ -70,6 +76,7 @@ class OtherSessionsController @Inject constructor(
                     sessionDescription(description)
                     sessionDescriptionDrawable(descriptionDrawable)
                     stringProvider(this@OtherSessionsController.stringProvider)
+                    clickListener { device.deviceInfo.deviceId?.let { host.callback?.onItemClicked(it) } }
                 }
             }
         }
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsView.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsView.kt
index 55978e61fd..682a9c6e64 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsView.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsView.kt
@@ -49,7 +49,12 @@ class OtherSessionsView @JvmOverloads constructor(
         otherSessionsController.setData(devices)
     }
 
+    fun setCallback(callback: OtherSessionsController.Callback) {
+        otherSessionsController.callback = callback
+    }
+
     override fun onDetachedFromWindow() {
+        otherSessionsController.callback = null
         views.otherSessionsRecyclerView.cleanup()
         super.onDetachedFromWindow()
     }
diff --git a/vector/src/main/res/layout/item_other_session.xml b/vector/src/main/res/layout/item_other_session.xml
index 2c41ce6a56..2f93c2be5d 100644
--- a/vector/src/main/res/layout/item_other_session.xml
+++ b/vector/src/main/res/layout/item_other_session.xml
@@ -4,6 +4,7 @@
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
+    android:foreground="?selectableItemBackground"
     android:paddingTop="16dp">
 
     <ImageView

From 31c908c873c0e85b058e7375485353df5b705f69 Mon Sep 17 00:00:00 2001
From: Maxime NATUREL <maxime.naturel@niji.fr>
Date: Thu, 1 Sep 2022 14:20:58 +0200
Subject: [PATCH 017/108] Updating existing unit tests

---
 .../v2/overview/GetDeviceFullInfoUseCase.kt   |  1 -
 .../overview/GetDeviceFullInfoUseCaseTest.kt  | 41 ++++++++++++++++++-
 2 files changed, 39 insertions(+), 3 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt
index 51252de34a..3cde519385 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt
@@ -28,7 +28,6 @@ import org.matrix.android.sdk.api.util.Optional
 import org.matrix.android.sdk.api.util.toOptional
 import javax.inject.Inject
 
-// TODO update unit test
 class GetDeviceFullInfoUseCase @Inject constructor(
         private val activeSessionHolder: ActiveSessionHolder,
         private val getCurrentSessionCrossSigningInfoUseCase: GetCurrentSessionCrossSigningInfoUseCase,
diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCaseTest.kt
index 32d7b6edfe..3d56f4ff11 100644
--- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCaseTest.kt
+++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCaseTest.kt
@@ -18,10 +18,15 @@ package im.vector.app.features.settings.devices.v2.overview
 
 import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.asFlow
+import im.vector.app.features.settings.devices.CurrentSessionCrossSigningInfo
 import im.vector.app.features.settings.devices.DeviceFullInfo
+import im.vector.app.features.settings.devices.GetCurrentSessionCrossSigningInfoUseCase
+import im.vector.app.features.settings.devices.GetEncryptionTrustLevelForDeviceUseCase
 import im.vector.app.test.fakes.FakeActiveSessionHolder
 import im.vector.app.test.fakes.FakeFlowLiveDataConversions
 import im.vector.app.test.fakes.givenAsFlow
+import io.mockk.every
+import io.mockk.mockk
 import io.mockk.unmockkAll
 import io.mockk.verify
 import kotlinx.coroutines.flow.firstOrNull
@@ -32,6 +37,7 @@ import org.junit.Before
 import org.junit.Test
 import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
 import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
+import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
 import org.matrix.android.sdk.api.util.Optional
 
 private const val A_DEVICE_ID = "device-id"
@@ -39,10 +45,14 @@ private const val A_DEVICE_ID = "device-id"
 class GetDeviceFullInfoUseCaseTest {
 
     private val fakeActiveSessionHolder = FakeActiveSessionHolder()
+    private val getCurrentSessionCrossSigningInfoUseCase = mockk<GetCurrentSessionCrossSigningInfoUseCase>()
+    private val getEncryptionTrustLevelForDeviceUseCase = mockk<GetEncryptionTrustLevelForDeviceUseCase>()
     private val fakeFlowLiveDataConversions = FakeFlowLiveDataConversions()
 
     private val getDeviceFullInfoUseCase = GetDeviceFullInfoUseCase(
-            activeSessionHolder = fakeActiveSessionHolder.instance
+            activeSessionHolder = fakeActiveSessionHolder.instance,
+            getCurrentSessionCrossSigningInfoUseCase = getCurrentSessionCrossSigningInfoUseCase,
+            getEncryptionTrustLevelForDeviceUseCase = getEncryptionTrustLevelForDeviceUseCase
     )
 
     @Before
@@ -57,23 +67,34 @@ class GetDeviceFullInfoUseCaseTest {
 
     @Test
     fun `given an active session and info for device when getting device info then the result is correct`() = runTest {
+        val currentSessionCrossSigningInfo = givenCurrentSessionCrossSigningInfo()
         val deviceInfo = DeviceInfo()
         fakeActiveSessionHolder.fakeSession.fakeCryptoService.myDevicesInfoWithIdLiveData = MutableLiveData(Optional(deviceInfo))
         fakeActiveSessionHolder.fakeSession.fakeCryptoService.myDevicesInfoWithIdLiveData.givenAsFlow()
         val cryptoDeviceInfo = CryptoDeviceInfo(deviceId = A_DEVICE_ID, userId = "")
         fakeActiveSessionHolder.fakeSession.fakeCryptoService.cryptoDeviceInfoWithIdLiveData = MutableLiveData(Optional(cryptoDeviceInfo))
         fakeActiveSessionHolder.fakeSession.fakeCryptoService.cryptoDeviceInfoWithIdLiveData.givenAsFlow()
+        val trustLevel = givenTrustLevel(currentSessionCrossSigningInfo, cryptoDeviceInfo)
 
         val deviceFullInfo = getDeviceFullInfoUseCase.execute(A_DEVICE_ID).firstOrNull()
 
-        deviceFullInfo shouldBeEqualTo Optional(DeviceFullInfo(deviceInfo = deviceInfo, cryptoDeviceInfo = cryptoDeviceInfo))
+        deviceFullInfo shouldBeEqualTo Optional(
+                DeviceFullInfo(
+                        deviceInfo = deviceInfo,
+                        cryptoDeviceInfo = cryptoDeviceInfo,
+                        trustLevelForShield = trustLevel
+                )
+        )
         verify { fakeActiveSessionHolder.instance.getSafeActiveSession() }
+        verify { getCurrentSessionCrossSigningInfoUseCase.execute() }
+        verify { getEncryptionTrustLevelForDeviceUseCase.execute(currentSessionCrossSigningInfo, cryptoDeviceInfo) }
         verify { fakeActiveSessionHolder.fakeSession.fakeCryptoService.getMyDevicesInfoLive(A_DEVICE_ID).asFlow() }
         verify { fakeActiveSessionHolder.fakeSession.fakeCryptoService.getLiveCryptoDeviceInfoWithId(A_DEVICE_ID).asFlow() }
     }
 
     @Test
     fun `given an active session and no info for device when getting device info then the result is null`() = runTest {
+        givenCurrentSessionCrossSigningInfo()
         fakeActiveSessionHolder.fakeSession.fakeCryptoService.myDevicesInfoWithIdLiveData = MutableLiveData(Optional(null))
         fakeActiveSessionHolder.fakeSession.fakeCryptoService.myDevicesInfoWithIdLiveData.givenAsFlow()
         fakeActiveSessionHolder.fakeSession.fakeCryptoService.cryptoDeviceInfoWithIdLiveData = MutableLiveData(Optional(null))
@@ -96,4 +117,20 @@ class GetDeviceFullInfoUseCaseTest {
         deviceFullInfo shouldBeEqualTo null
         verify { fakeActiveSessionHolder.instance.getSafeActiveSession() }
     }
+
+    private fun givenCurrentSessionCrossSigningInfo(): CurrentSessionCrossSigningInfo {
+        val currentSessionCrossSigningInfo = CurrentSessionCrossSigningInfo(
+                deviceId = A_DEVICE_ID,
+                isCrossSigningInitialized = true,
+                isCrossSigningVerified = false
+        )
+        every { getCurrentSessionCrossSigningInfoUseCase.execute() } returns currentSessionCrossSigningInfo
+        return currentSessionCrossSigningInfo
+    }
+
+    private fun givenTrustLevel(currentSessionCrossSigningInfo: CurrentSessionCrossSigningInfo, cryptoDeviceInfo: CryptoDeviceInfo?): RoomEncryptionTrustLevel {
+        val trustLevel = RoomEncryptionTrustLevel.Trusted
+        every { getEncryptionTrustLevelForDeviceUseCase.execute(currentSessionCrossSigningInfo, cryptoDeviceInfo) } returns trustLevel
+        return trustLevel
+    }
 }

From af985d9b1f935cc0d200bfba3064c85550328c90 Mon Sep 17 00:00:00 2001
From: Maxime NATUREL <maxime.naturel@niji.fr>
Date: Thu, 1 Sep 2022 14:40:40 +0200
Subject: [PATCH 018/108] Unit tests for
 GetCurrentSessionCrossSigningInfoUseCase

---
 ...etCurrentSessionCrossSigningInfoUseCase.kt |  1 -
 ...rrentSessionCrossSigningInfoUseCaseTest.kt | 62 +++++++++++++++++++
 .../app/test/fakes/FakeCrossSigningService.kt | 32 ++++++++++
 .../app/test/fakes/FakeCryptoService.kt       |  6 +-
 4 files changed, 99 insertions(+), 2 deletions(-)
 create mode 100644 vector/src/test/java/im/vector/app/features/settings/devices/GetCurrentSessionCrossSigningInfoUseCaseTest.kt
 create mode 100644 vector/src/test/java/im/vector/app/test/fakes/FakeCrossSigningService.kt

diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/GetCurrentSessionCrossSigningInfoUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/GetCurrentSessionCrossSigningInfoUseCase.kt
index aa0de9ddf1..d07bd5daae 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/GetCurrentSessionCrossSigningInfoUseCase.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/GetCurrentSessionCrossSigningInfoUseCase.kt
@@ -19,7 +19,6 @@ package im.vector.app.features.settings.devices
 import im.vector.app.core.di.ActiveSessionHolder
 import javax.inject.Inject
 
-// TODO add unit tests
 class GetCurrentSessionCrossSigningInfoUseCase @Inject constructor(
         private val activeSessionHolder: ActiveSessionHolder,
 ) {
diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/GetCurrentSessionCrossSigningInfoUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/GetCurrentSessionCrossSigningInfoUseCaseTest.kt
new file mode 100644
index 0000000000..f3684fd8cf
--- /dev/null
+++ b/vector/src/test/java/im/vector/app/features/settings/devices/GetCurrentSessionCrossSigningInfoUseCaseTest.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.settings.devices
+
+import im.vector.app.test.fakes.FakeActiveSessionHolder
+import io.mockk.every
+import io.mockk.mockk
+import kotlinx.coroutines.test.runTest
+import org.amshove.kluent.shouldBeEqualTo
+import org.junit.Test
+import org.matrix.android.sdk.api.auth.data.SessionParams
+
+private const val A_DEVICE_ID = "device-id"
+
+class GetCurrentSessionCrossSigningInfoUseCaseTest {
+
+    private val fakeActiveSessionHolder = FakeActiveSessionHolder()
+
+    private val getCurrentSessionCrossSigningInfoUseCase = GetCurrentSessionCrossSigningInfoUseCase(
+            activeSessionHolder = fakeActiveSessionHolder.instance
+    )
+
+    @Test
+    fun `given the active session when getting cross signing info then the result is correct`() = runTest {
+        val sessionParams = mockk<SessionParams>()
+        every { sessionParams.deviceId } returns A_DEVICE_ID
+        fakeActiveSessionHolder.fakeSession.givenSessionParams(sessionParams)
+        val isCrossSigningInitialized = true
+        fakeActiveSessionHolder.fakeSession
+                .fakeCryptoService
+                .fakeCrossSigningService
+                .givenIsCrossSigningInitializedReturns(isCrossSigningInitialized)
+        val isCrossSigningVerified = true
+        fakeActiveSessionHolder.fakeSession
+                .fakeCryptoService
+                .fakeCrossSigningService
+                .givenIsCrossSigningVerifiedReturns(isCrossSigningVerified)
+        val expectedResult = CurrentSessionCrossSigningInfo(
+                deviceId = A_DEVICE_ID,
+                isCrossSigningInitialized = isCrossSigningInitialized,
+                isCrossSigningVerified = isCrossSigningVerified
+        )
+
+        val result = getCurrentSessionCrossSigningInfoUseCase.execute()
+
+        result shouldBeEqualTo expectedResult
+    }
+}
diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeCrossSigningService.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeCrossSigningService.kt
new file mode 100644
index 0000000000..e9a5365b1c
--- /dev/null
+++ b/vector/src/test/java/im/vector/app/test/fakes/FakeCrossSigningService.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.test.fakes
+
+import io.mockk.every
+import io.mockk.mockk
+import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
+
+class FakeCrossSigningService : CrossSigningService by mockk() {
+
+    fun givenIsCrossSigningInitializedReturns(isInitialized: Boolean) {
+        every { isCrossSigningInitialized() } returns isInitialized
+    }
+
+    fun givenIsCrossSigningVerifiedReturns(isVerified: Boolean) {
+        every { isCrossSigningVerified() } returns isVerified
+    }
+}
diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeCryptoService.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeCryptoService.kt
index 2c31933464..197ccf4cd2 100644
--- a/vector/src/test/java/im/vector/app/test/fakes/FakeCryptoService.kt
+++ b/vector/src/test/java/im/vector/app/test/fakes/FakeCryptoService.kt
@@ -23,13 +23,17 @@ import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
 import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
 import org.matrix.android.sdk.api.util.Optional
 
-class FakeCryptoService : CryptoService by mockk() {
+class FakeCryptoService(
+        val fakeCrossSigningService: FakeCrossSigningService = FakeCrossSigningService()
+) : CryptoService by mockk() {
 
     var roomKeysExport = ByteArray(size = 1)
     var cryptoDeviceInfos = mutableMapOf<String, CryptoDeviceInfo>()
     var cryptoDeviceInfoWithIdLiveData: MutableLiveData<Optional<CryptoDeviceInfo>> = MutableLiveData()
     var myDevicesInfoWithIdLiveData: MutableLiveData<Optional<DeviceInfo>> = MutableLiveData()
 
+    override fun crossSigningService() = fakeCrossSigningService
+
     override suspend fun exportRoomKeys(password: String) = roomKeysExport
 
     override fun getLiveCryptoDeviceInfo() = MutableLiveData(cryptoDeviceInfos.values.toList())

From 384c118b8d39156bfdd0d374afca9288142a0b8f Mon Sep 17 00:00:00 2001
From: Maxime NATUREL <maxime.naturel@niji.fr>
Date: Thu, 1 Sep 2022 16:11:38 +0200
Subject: [PATCH 019/108] Unit tests for computing trust level of device

---
 ...yptionTrustLevelForCurrentDeviceUseCase.kt |   1 -
 ...GetEncryptionTrustLevelForDeviceUseCase.kt |   1 -
 ...cryptionTrustLevelForOtherDeviceUseCase.kt |   1 -
 ...rrentSessionCrossSigningInfoUseCaseTest.kt |   3 +-
 ...onTrustLevelForCurrentDeviceUseCaseTest.kt |  56 +++++++++
 ...ncryptionTrustLevelForDeviceUseCaseTest.kt | 114 ++++++++++++++++++
 ...tionTrustLevelForOtherDeviceUseCaseTest.kt | 100 +++++++++++++++
 .../overview/SessionOverviewViewModelTest.kt  |   3 +-
 8 files changed, 272 insertions(+), 7 deletions(-)
 create mode 100644 vector/src/test/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForCurrentDeviceUseCaseTest.kt
 create mode 100644 vector/src/test/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForDeviceUseCaseTest.kt
 create mode 100644 vector/src/test/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForOtherDeviceUseCaseTest.kt

diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForCurrentDeviceUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForCurrentDeviceUseCase.kt
index eaa72b424a..0d30aba318 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForCurrentDeviceUseCase.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForCurrentDeviceUseCase.kt
@@ -19,7 +19,6 @@ package im.vector.app.features.settings.devices
 import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
 import javax.inject.Inject
 
-// TODO add unit tests
 class GetEncryptionTrustLevelForCurrentDeviceUseCase @Inject constructor() {
 
     fun execute(trustMSK: Boolean, legacyMode: Boolean): RoomEncryptionTrustLevel {
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForDeviceUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForDeviceUseCase.kt
index d988f728ae..e5ef4b446b 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForDeviceUseCase.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForDeviceUseCase.kt
@@ -20,7 +20,6 @@ import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
 import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
 import javax.inject.Inject
 
-// TODO add unit tests
 class GetEncryptionTrustLevelForDeviceUseCase @Inject constructor(
         private val getEncryptionTrustLevelForCurrentDeviceUseCase: GetEncryptionTrustLevelForCurrentDeviceUseCase,
         private val getEncryptionTrustLevelForOtherDeviceUseCase: GetEncryptionTrustLevelForOtherDeviceUseCase,
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForOtherDeviceUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForOtherDeviceUseCase.kt
index 41cdae23a4..11bc3a8ede 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForOtherDeviceUseCase.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForOtherDeviceUseCase.kt
@@ -20,7 +20,6 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
 import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
 import javax.inject.Inject
 
-// TODO add unit tests
 class GetEncryptionTrustLevelForOtherDeviceUseCase @Inject constructor() {
 
     fun execute(trustMSK: Boolean, legacyMode: Boolean, deviceTrustLevel: DeviceTrustLevel?): RoomEncryptionTrustLevel {
diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/GetCurrentSessionCrossSigningInfoUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/GetCurrentSessionCrossSigningInfoUseCaseTest.kt
index f3684fd8cf..7c8ee008eb 100644
--- a/vector/src/test/java/im/vector/app/features/settings/devices/GetCurrentSessionCrossSigningInfoUseCaseTest.kt
+++ b/vector/src/test/java/im/vector/app/features/settings/devices/GetCurrentSessionCrossSigningInfoUseCaseTest.kt
@@ -19,7 +19,6 @@ package im.vector.app.features.settings.devices
 import im.vector.app.test.fakes.FakeActiveSessionHolder
 import io.mockk.every
 import io.mockk.mockk
-import kotlinx.coroutines.test.runTest
 import org.amshove.kluent.shouldBeEqualTo
 import org.junit.Test
 import org.matrix.android.sdk.api.auth.data.SessionParams
@@ -35,7 +34,7 @@ class GetCurrentSessionCrossSigningInfoUseCaseTest {
     )
 
     @Test
-    fun `given the active session when getting cross signing info then the result is correct`() = runTest {
+    fun `given the active session when getting cross signing info then the result is correct`() {
         val sessionParams = mockk<SessionParams>()
         every { sessionParams.deviceId } returns A_DEVICE_ID
         fakeActiveSessionHolder.fakeSession.givenSessionParams(sessionParams)
diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForCurrentDeviceUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForCurrentDeviceUseCaseTest.kt
new file mode 100644
index 0000000000..830eab5dcb
--- /dev/null
+++ b/vector/src/test/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForCurrentDeviceUseCaseTest.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.settings.devices
+
+import org.amshove.kluent.shouldBeEqualTo
+import org.junit.Test
+import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
+
+class GetEncryptionTrustLevelForCurrentDeviceUseCaseTest {
+
+    private val getEncryptionTrustLevelForCurrentDeviceUseCase = GetEncryptionTrustLevelForCurrentDeviceUseCase()
+
+    @Test
+    fun `given in legacy mode when computing trust level then device is trusted`() {
+        val trustMSK = false
+        val legacyMode = true
+
+        val result = getEncryptionTrustLevelForCurrentDeviceUseCase.execute(trustMSK = trustMSK, legacyMode = legacyMode)
+
+        result shouldBeEqualTo RoomEncryptionTrustLevel.Trusted
+    }
+
+    @Test
+    fun `given trustMSK is true and not in legacy mode when computing trust level then device is trusted`() {
+        val trustMSK = true
+        val legacyMode = false
+
+        val result = getEncryptionTrustLevelForCurrentDeviceUseCase.execute(trustMSK = trustMSK, legacyMode = legacyMode)
+
+        result shouldBeEqualTo RoomEncryptionTrustLevel.Trusted
+    }
+
+    @Test
+    fun `given trustMSK is false and not in legacy mode when computing trust level then device is unverified`() {
+        val trustMSK = false
+        val legacyMode = false
+
+        val result = getEncryptionTrustLevelForCurrentDeviceUseCase.execute(trustMSK = trustMSK, legacyMode = legacyMode)
+
+        result shouldBeEqualTo RoomEncryptionTrustLevel.Warning
+    }
+}
diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForDeviceUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForDeviceUseCaseTest.kt
new file mode 100644
index 0000000000..8d54b31ab4
--- /dev/null
+++ b/vector/src/test/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForDeviceUseCaseTest.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.settings.devices
+
+import io.mockk.every
+import io.mockk.mockk
+import io.mockk.verify
+import org.amshove.kluent.shouldBeEqualTo
+import org.junit.Test
+import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
+import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
+import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
+
+private const val A_DEVICE_ID = "device-id"
+private const val A_DEVICE_ID_2 = "device-id-2"
+
+class GetEncryptionTrustLevelForDeviceUseCaseTest {
+
+    private val getEncryptionTrustLevelForCurrentDeviceUseCase = mockk<GetEncryptionTrustLevelForCurrentDeviceUseCase>()
+    private val getEncryptionTrustLevelForOtherDeviceUseCase = mockk<GetEncryptionTrustLevelForOtherDeviceUseCase>()
+
+    private val getEncryptionTrustLevelForDeviceUseCase = GetEncryptionTrustLevelForDeviceUseCase(
+            getEncryptionTrustLevelForCurrentDeviceUseCase = getEncryptionTrustLevelForCurrentDeviceUseCase,
+            getEncryptionTrustLevelForOtherDeviceUseCase = getEncryptionTrustLevelForOtherDeviceUseCase,
+    )
+
+    @Test
+    fun `given is current device when computing trust level then the correct sub use case result is returned`() {
+        val currentSessionCrossSigningInfo = givenCurrentSessionCrossSigningInfo(
+                deviceId = A_DEVICE_ID,
+                isCrossSigningInitialized = true,
+                isCrossSigningVerified = false
+        )
+        val cryptoDeviceInfo = givenCryptoDeviceInfo(
+                deviceId = A_DEVICE_ID,
+                trustLevel = null
+        )
+        val trustLevel = RoomEncryptionTrustLevel.Trusted
+        every { getEncryptionTrustLevelForCurrentDeviceUseCase.execute(any(), any()) } returns trustLevel
+
+        val result = getEncryptionTrustLevelForDeviceUseCase.execute(currentSessionCrossSigningInfo, cryptoDeviceInfo)
+
+        result shouldBeEqualTo trustLevel
+        verify {
+            getEncryptionTrustLevelForCurrentDeviceUseCase.execute(
+                    trustMSK = currentSessionCrossSigningInfo.isCrossSigningVerified,
+                    legacyMode = !currentSessionCrossSigningInfo.isCrossSigningInitialized
+            )
+        }
+    }
+
+    @Test
+    fun `given is not current device when computing trust level then the correct sub use case result is returned`() {
+        val currentSessionCrossSigningInfo = givenCurrentSessionCrossSigningInfo(
+                deviceId = A_DEVICE_ID,
+                isCrossSigningInitialized = true,
+                isCrossSigningVerified = false
+        )
+        val cryptoDeviceInfo = givenCryptoDeviceInfo(
+                deviceId = A_DEVICE_ID_2,
+                trustLevel = null
+        )
+        val trustLevel = RoomEncryptionTrustLevel.Trusted
+        every { getEncryptionTrustLevelForOtherDeviceUseCase.execute(any(), any(), any()) } returns trustLevel
+
+        val result = getEncryptionTrustLevelForDeviceUseCase.execute(currentSessionCrossSigningInfo, cryptoDeviceInfo)
+
+        result shouldBeEqualTo trustLevel
+        verify {
+            getEncryptionTrustLevelForOtherDeviceUseCase.execute(
+                    trustMSK = currentSessionCrossSigningInfo.isCrossSigningVerified,
+                    legacyMode = !currentSessionCrossSigningInfo.isCrossSigningInitialized,
+                    deviceTrustLevel = cryptoDeviceInfo.trustLevel
+            )
+        }
+    }
+
+    private fun givenCurrentSessionCrossSigningInfo(
+            deviceId: String?,
+            isCrossSigningInitialized: Boolean,
+            isCrossSigningVerified: Boolean
+    ): CurrentSessionCrossSigningInfo {
+        return CurrentSessionCrossSigningInfo(
+                deviceId = deviceId,
+                isCrossSigningInitialized = isCrossSigningInitialized,
+                isCrossSigningVerified = isCrossSigningVerified
+        )
+    }
+
+    private fun givenCryptoDeviceInfo(
+            deviceId: String,
+            trustLevel: DeviceTrustLevel?
+    ): CryptoDeviceInfo {
+        return CryptoDeviceInfo(
+                userId = "",
+                deviceId = deviceId,
+                trustLevel = trustLevel
+        )
+    }
+}
diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForOtherDeviceUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForOtherDeviceUseCaseTest.kt
new file mode 100644
index 0000000000..9dc87c2a16
--- /dev/null
+++ b/vector/src/test/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForOtherDeviceUseCaseTest.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.settings.devices
+
+import org.amshove.kluent.shouldBeEqualTo
+import org.junit.Test
+import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
+import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
+
+class GetEncryptionTrustLevelForOtherDeviceUseCaseTest {
+
+    private val getEncryptionTrustLevelForOtherDeviceUseCase = GetEncryptionTrustLevelForOtherDeviceUseCase()
+
+    @Test
+    fun `given in legacy mode and device locally verified when computing trust level then device is trusted`() {
+        val trustMSK = false
+        val legacyMode = true
+        val deviceTrustLevel = givenDeviceTrustLevel(locallyVerified = true, crossSigningVerified = false)
+
+        val result = getEncryptionTrustLevelForOtherDeviceUseCase.execute(trustMSK = trustMSK, legacyMode = legacyMode, deviceTrustLevel = deviceTrustLevel)
+
+        result shouldBeEqualTo RoomEncryptionTrustLevel.Trusted
+    }
+
+    @Test
+    fun `given in legacy mode and device not locally verified when computing trust level then device is unverified`() {
+        val trustMSK = false
+        val legacyMode = true
+        val deviceTrustLevel = givenDeviceTrustLevel(locallyVerified = false, crossSigningVerified = false)
+
+        val result = getEncryptionTrustLevelForOtherDeviceUseCase.execute(trustMSK = trustMSK, legacyMode = legacyMode, deviceTrustLevel = deviceTrustLevel)
+
+        result shouldBeEqualTo RoomEncryptionTrustLevel.Warning
+    }
+
+    @Test
+    fun `given trustMSK is true and not in legacy mode and device cross signing verified when computing trust level then device is trusted`() {
+        val trustMSK = true
+        val legacyMode = false
+        val deviceTrustLevel = givenDeviceTrustLevel(locallyVerified = false, crossSigningVerified = true)
+
+        val result = getEncryptionTrustLevelForOtherDeviceUseCase.execute(trustMSK = trustMSK, legacyMode = legacyMode, deviceTrustLevel = deviceTrustLevel)
+
+        result shouldBeEqualTo RoomEncryptionTrustLevel.Trusted
+    }
+
+    @Test
+    fun `given trustMSK is true and not in legacy mode and device locally verified when computing trust level then device has default trust level`() {
+        val trustMSK = true
+        val legacyMode = false
+        val deviceTrustLevel = givenDeviceTrustLevel(locallyVerified = true, crossSigningVerified = false)
+
+        val result = getEncryptionTrustLevelForOtherDeviceUseCase.execute(trustMSK = trustMSK, legacyMode = legacyMode, deviceTrustLevel = deviceTrustLevel)
+
+        result shouldBeEqualTo RoomEncryptionTrustLevel.Default
+    }
+
+    @Test
+    fun `given trustMSK is true and not in legacy mode and device not verified when computing trust level then device is unverified`() {
+        val trustMSK = true
+        val legacyMode = false
+        val deviceTrustLevel = givenDeviceTrustLevel(locallyVerified = false, crossSigningVerified = false)
+
+        val result = getEncryptionTrustLevelForOtherDeviceUseCase.execute(trustMSK = trustMSK, legacyMode = legacyMode, deviceTrustLevel = deviceTrustLevel)
+
+        result shouldBeEqualTo RoomEncryptionTrustLevel.Warning
+    }
+
+    @Test
+    fun `given trustMSK is false and not in legacy mode when computing trust level then device has default trust level`() {
+        val trustMSK = false
+        val legacyMode = false
+        val deviceTrustLevel = givenDeviceTrustLevel(locallyVerified = false, crossSigningVerified = false)
+
+        val result = getEncryptionTrustLevelForOtherDeviceUseCase.execute(trustMSK = trustMSK, legacyMode = legacyMode, deviceTrustLevel = deviceTrustLevel)
+
+        result shouldBeEqualTo RoomEncryptionTrustLevel.Default
+    }
+
+    private fun givenDeviceTrustLevel(locallyVerified: Boolean?, crossSigningVerified: Boolean): DeviceTrustLevel {
+        return DeviceTrustLevel(
+                crossSigningVerified = crossSigningVerified,
+                locallyVerified = locallyVerified
+        )
+    }
+}
diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModelTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModelTest.kt
index f15bc0860c..10b1c0fdb1 100644
--- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModelTest.kt
+++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModelTest.kt
@@ -26,7 +26,6 @@ import io.mockk.mockk
 import io.mockk.verify
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import kotlinx.coroutines.test.runTest
 import org.junit.Rule
 import org.junit.Test
 import org.matrix.android.sdk.api.auth.data.SessionParams
@@ -52,7 +51,7 @@ class SessionOverviewViewModelTest {
     )
 
     @Test
-    fun `given the viewModel has been initialized then viewState is updated with session info`() = runTest {
+    fun `given the viewModel has been initialized then viewState is updated with session info`() {
         val sessionParams = givenIdForSession(A_SESSION_ID)
         val deviceFullInfo = mockk<DeviceFullInfo>()
         every { getDeviceFullInfoUseCase.execute(A_SESSION_ID) } returns flowOf(Optional(deviceFullInfo))

From 3eaf5f7fe0bad399f4edc2d37d4f04ef6f1269df Mon Sep 17 00:00:00 2001
From: Maxime NATUREL <maxime.naturel@niji.fr>
Date: Thu, 1 Sep 2022 17:46:15 +0200
Subject: [PATCH 020/108] Adding learn more link in verification status details

---
 .../devices/v2/list/SessionInfoView.kt        | 35 +++++++++++++++++--
 .../devices/v2/list/SessionInfoViewState.kt   |  1 +
 .../v2/overview/SessionOverviewFragment.kt    | 26 +++++++++++++-
 3 files changed, 59 insertions(+), 3 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoView.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoView.kt
index be6cfad1c8..536184faec 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoView.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoView.kt
@@ -21,6 +21,7 @@ import android.util.AttributeSet
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.core.view.isVisible
 import im.vector.app.R
+import im.vector.app.core.extensions.setTextWithColoredPart
 import im.vector.app.databinding.ViewSessionInfoBinding
 import im.vector.app.features.themes.ThemeUtils
 import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
@@ -33,6 +34,8 @@ class SessionInfoView @JvmOverloads constructor(
 
     private val views: ViewSessionInfoBinding
 
+    var onLearnMoreClickListener: (() -> Unit)? = null
+
     init {
         inflate(context, R.layout.view_session_info, this)
         views = ViewSessionInfoBinding.bind(this)
@@ -42,17 +45,45 @@ class SessionInfoView @JvmOverloads constructor(
 
     fun render(sessionInfoViewState: SessionInfoViewState) {
         renderDeviceInfo(sessionInfoViewState.deviceFullInfo.deviceInfo.displayName.orEmpty())
-        renderVerificationStatus(sessionInfoViewState.deviceFullInfo.trustLevelForShield, sessionInfoViewState.isCurrentSession)
+        renderVerificationStatus(
+                sessionInfoViewState.deviceFullInfo.trustLevelForShield,
+                sessionInfoViewState.isCurrentSession,
+                sessionInfoViewState.hasLearnMoreLink
+        )
         renderDetailsButton(sessionInfoViewState.isDetailsButtonVisible)
     }
 
-    private fun renderVerificationStatus(encryptionTrustLevel: RoomEncryptionTrustLevel, isCurrentSession: Boolean) {
+    private fun renderVerificationStatus(
+            encryptionTrustLevel: RoomEncryptionTrustLevel,
+            isCurrentSession: Boolean,
+            hasLearnMoreLink: Boolean,
+    ) {
         views.sessionInfoVerificationStatusImageView.render(encryptionTrustLevel)
         if (encryptionTrustLevel == RoomEncryptionTrustLevel.Trusted) {
             renderCrossSigningVerified(isCurrentSession)
         } else {
             renderCrossSigningUnverified(isCurrentSession)
         }
+        if (hasLearnMoreLink) {
+            appendLearnMoreToVerificationStatus()
+        }
+    }
+
+    private fun appendLearnMoreToVerificationStatus() {
+        val status = views.sessionInfoVerificationStatusDetailTextView.text
+        val learnMore = context.getString(R.string.action_learn_more)
+        val stringBuilder = StringBuilder()
+        stringBuilder.append(status)
+        stringBuilder.append(" ")
+        stringBuilder.append(learnMore)
+
+        views.sessionInfoVerificationStatusDetailTextView.setTextWithColoredPart(
+                fullText = stringBuilder.toString(),
+                coloredPart = learnMore,
+                underline = false
+        ) {
+            onLearnMoreClickListener?.invoke()
+        }
     }
 
     private fun renderCrossSigningVerified(isCurrentSession: Boolean) {
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoViewState.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoViewState.kt
index c9a351f568..cf7c6f0ae8 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoViewState.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoViewState.kt
@@ -22,4 +22,5 @@ data class SessionInfoViewState(
         val isCurrentSession: Boolean,
         val deviceFullInfo: DeviceFullInfo,
         val isDetailsButtonVisible: Boolean = true,
+        val hasLearnMoreLink: Boolean = false
 )
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt
index 60d58c8a8d..eb2a3aa93f 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt
@@ -16,8 +16,11 @@
 
 package im.vector.app.features.settings.devices.v2.overview
 
+import android.os.Bundle
 import android.view.LayoutInflater
+import android.view.View
 import android.view.ViewGroup
+import android.widget.Toast
 import androidx.appcompat.app.AppCompatActivity
 import androidx.core.view.isGone
 import androidx.core.view.isVisible
@@ -44,6 +47,26 @@ class SessionOverviewFragment :
         return FragmentSessionOverviewBinding.inflate(inflater, container, false)
     }
 
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        initSessionInfoView()
+    }
+
+    private fun initSessionInfoView() {
+        views.sessionOverviewInfo.onLearnMoreClickListener = {
+            Toast.makeText(context, "Learn more verification status", Toast.LENGTH_LONG).show()
+        }
+    }
+
+    override fun onDestroyView() {
+        cleanUpSessionInfoView()
+        super.onDestroyView()
+    }
+
+    private fun cleanUpSessionInfoView() {
+        views.sessionOverviewInfo.onLearnMoreClickListener = null
+    }
+
     override fun invalidate() = withState(viewModel) { state ->
         updateToolbar(state.isCurrentSession)
         if (state.deviceInfo is Success) {
@@ -65,7 +88,8 @@ class SessionOverviewFragment :
         val viewState = SessionInfoViewState(
                 isCurrentSession = isCurrentSession,
                 deviceFullInfo = deviceFullInfo,
-                isDetailsButtonVisible = false
+                isDetailsButtonVisible = false,
+                hasLearnMoreLink = true
         )
         views.sessionOverviewInfo.render(viewState)
     }

From bbe238e9c6fbaeaead6c6fbd471179ef7a692283 Mon Sep 17 00:00:00 2001
From: Maxime NATUREL <maxime.naturel@niji.fr>
Date: Fri, 2 Sep 2022 10:02:54 +0200
Subject: [PATCH 021/108] Adding last seen details + fix observation of wrong
 deviceId in ViewModel

---
 .../src/main/res/values/strings.xml           |  1 +
 .../v2/VectorSettingsDevicesFragment.kt       |  9 +++--
 .../v2/VectorSettingsDevicesViewNavigator.kt  |  4 +--
 .../devices/v2/list/SessionInfoView.kt        | 36 +++++++++++++++++--
 .../devices/v2/list/SessionInfoViewState.kt   |  3 +-
 .../v2/overview/SessionOverviewActivity.kt    |  4 +--
 .../v2/overview/SessionOverviewArgs.kt        |  2 +-
 .../v2/overview/SessionOverviewFragment.kt    |  9 +++--
 .../v2/overview/SessionOverviewViewModel.kt   |  4 +--
 .../v2/overview/SessionOverviewViewState.kt   |  4 +--
 .../src/main/res/layout/view_session_info.xml | 31 +++++++++++++++-
 .../overview/SessionOverviewViewModelTest.kt  |  4 +--
 12 files changed, 91 insertions(+), 20 deletions(-)

diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml
index 15dd579386..892c31ecf8 100644
--- a/library/ui-strings/src/main/res/values/strings.xml
+++ b/library/ui-strings/src/main/res/values/strings.xml
@@ -3247,5 +3247,6 @@
     </plurals>
     <string name="device_manager_current_session_title">Current Session</string>
     <string name="device_manager_session_title">Session</string>
+    <string name="device_manager_session_last_activity">Last activity %1$s</string>
 
 </resources>
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt
index f7f6ca6db4..2262f083da 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt
@@ -31,6 +31,7 @@ import com.airbnb.mvrx.fragmentViewModel
 import com.airbnb.mvrx.withState
 import dagger.hilt.android.AndroidEntryPoint
 import im.vector.app.R
+import im.vector.app.core.date.VectorDateFormatter
 import im.vector.app.core.dialogs.ManuallyVerifyDialog
 import im.vector.app.core.platform.VectorBaseFragment
 import im.vector.app.databinding.FragmentSettingsDevicesBinding
@@ -55,6 +56,8 @@ class VectorSettingsDevicesFragment :
 
     @Inject lateinit var viewNavigator: VectorSettingsDevicesViewNavigator
 
+    @Inject lateinit var dateFormatter: VectorDateFormatter
+
     private val viewModel: DevicesViewModel by fragmentViewModel()
 
     override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentSettingsDevicesBinding {
@@ -214,7 +217,7 @@ class VectorSettingsDevicesFragment :
                     isCurrentSession = true,
                     deviceFullInfo = it
             )
-            views.deviceListCurrentSession.render(viewState)
+            views.deviceListCurrentSession.render(viewState, dateFormatter)
             views.deviceListCurrentSession.debouncedClicks {
                 currentDeviceInfo.deviceInfo.deviceId?.let { deviceId -> navigateToSessionOverview(deviceId) }
             }
@@ -226,10 +229,10 @@ class VectorSettingsDevicesFragment :
         }
     }
 
-    private fun navigateToSessionOverview(sessionId: String) {
+    private fun navigateToSessionOverview(deviceId: String) {
         viewNavigator.navigateToSessionOverview(
                 context = requireActivity(),
-                sessionId = sessionId
+                deviceId = deviceId
         )
     }
 
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesViewNavigator.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesViewNavigator.kt
index 25c971aacb..54eed3bc14 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesViewNavigator.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesViewNavigator.kt
@@ -22,7 +22,7 @@ import javax.inject.Inject
 
 class VectorSettingsDevicesViewNavigator @Inject constructor() {
 
-    fun navigateToSessionOverview(context: Context, sessionId: String) {
-        context.startActivity(SessionOverviewActivity.newIntent(context, sessionId))
+    fun navigateToSessionOverview(context: Context, deviceId: String) {
+        context.startActivity(SessionOverviewActivity.newIntent(context, deviceId))
     }
 }
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoView.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoView.kt
index 536184faec..df50666b3b 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoView.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoView.kt
@@ -19,11 +19,15 @@ package im.vector.app.features.settings.devices.v2.list
 import android.content.Context
 import android.util.AttributeSet
 import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.view.isGone
 import androidx.core.view.isVisible
 import im.vector.app.R
+import im.vector.app.core.date.DateFormatKind
+import im.vector.app.core.date.VectorDateFormatter
 import im.vector.app.core.extensions.setTextWithColoredPart
 import im.vector.app.databinding.ViewSessionInfoBinding
 import im.vector.app.features.themes.ThemeUtils
+import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
 import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
 
 class SessionInfoView @JvmOverloads constructor(
@@ -43,13 +47,14 @@ class SessionInfoView @JvmOverloads constructor(
 
     val viewDetailsButton = views.sessionInfoViewDetailsButton
 
-    fun render(sessionInfoViewState: SessionInfoViewState) {
+    fun render(sessionInfoViewState: SessionInfoViewState, dateFormatter: VectorDateFormatter) {
         renderDeviceInfo(sessionInfoViewState.deviceFullInfo.deviceInfo.displayName.orEmpty())
         renderVerificationStatus(
                 sessionInfoViewState.deviceFullInfo.trustLevelForShield,
                 sessionInfoViewState.isCurrentSession,
-                sessionInfoViewState.hasLearnMoreLink
+                sessionInfoViewState.isLearnMoreLinkVisible
         )
+        renderDeviceLastSeenDetails(sessionInfoViewState.deviceFullInfo.deviceInfo, dateFormatter, sessionInfoViewState.isLastSeenDetailsVisible)
         renderDetailsButton(sessionInfoViewState.isDetailsButtonVisible)
     }
 
@@ -117,6 +122,33 @@ class SessionInfoView @JvmOverloads constructor(
         views.sessionInfoNameTextView.text = sessionName
     }
 
+    private fun renderDeviceLastSeenDetails(
+            deviceInfo: DeviceInfo,
+            dateFormatter: VectorDateFormatter,
+            isLastSeenDetailsVisible: Boolean,
+    ) {
+        deviceInfo.lastSeenTs
+                ?.takeIf { isLastSeenDetailsVisible }
+                ?.let { timestamp ->
+                    views.sessionInfoLastActivityTextView.isVisible = true
+                    val formattedTs = dateFormatter.format(timestamp, DateFormatKind.DEFAULT_DATE_AND_TIME)
+                    views.sessionInfoLastActivityTextView.text = context.getString(R.string.device_manager_session_last_activity, formattedTs)
+                }
+                ?: run {
+                    views.sessionInfoLastActivityTextView.isGone = true
+                }
+
+        deviceInfo.lastSeenIp
+                ?.takeIf { isLastSeenDetailsVisible }
+                ?.let { ipAddress ->
+                    views.sessionInfoLastIPAddressTextView.isVisible = true
+                    views.sessionInfoLastIPAddressTextView.text = ipAddress
+                }
+                ?: run {
+                    views.sessionInfoLastIPAddressTextView.isGone = true
+                }
+    }
+
     private fun renderDetailsButton(isDetailsButtonVisible: Boolean) {
         views.sessionInfoViewDetailsButton.isVisible = isDetailsButtonVisible
     }
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoViewState.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoViewState.kt
index cf7c6f0ae8..22ad710676 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoViewState.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoViewState.kt
@@ -22,5 +22,6 @@ data class SessionInfoViewState(
         val isCurrentSession: Boolean,
         val deviceFullInfo: DeviceFullInfo,
         val isDetailsButtonVisible: Boolean = true,
-        val hasLearnMoreLink: Boolean = false
+        val isLearnMoreLinkVisible: Boolean = false,
+        val isLastSeenDetailsVisible: Boolean = false,
 )
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewActivity.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewActivity.kt
index a663c0ff2a..015fcccf51 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewActivity.kt
@@ -43,9 +43,9 @@ class SessionOverviewActivity : SimpleFragmentActivity() {
     }
 
     companion object {
-        fun newIntent(context: Context, sessionId: String): Intent {
+        fun newIntent(context: Context, deviceId: String): Intent {
             return Intent(context, SessionOverviewActivity::class.java).apply {
-                putExtra(Mavericks.KEY_ARG, SessionOverviewArgs(sessionId))
+                putExtra(Mavericks.KEY_ARG, SessionOverviewArgs(deviceId))
             }
         }
     }
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewArgs.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewArgs.kt
index 87ea883362..27c8d6fb2e 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewArgs.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewArgs.kt
@@ -21,5 +21,5 @@ import kotlinx.parcelize.Parcelize
 
 @Parcelize
 data class SessionOverviewArgs(
-        val sessionId: String
+        val deviceId: String
 ) : Parcelable
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt
index eb2a3aa93f..dbe75c94cc 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt
@@ -29,10 +29,12 @@ import com.airbnb.mvrx.fragmentViewModel
 import com.airbnb.mvrx.withState
 import dagger.hilt.android.AndroidEntryPoint
 import im.vector.app.R
+import im.vector.app.core.date.VectorDateFormatter
 import im.vector.app.core.platform.VectorBaseFragment
 import im.vector.app.databinding.FragmentSessionOverviewBinding
 import im.vector.app.features.settings.devices.DeviceFullInfo
 import im.vector.app.features.settings.devices.v2.list.SessionInfoViewState
+import javax.inject.Inject
 
 /**
  * Display the overview info about a Session.
@@ -41,6 +43,8 @@ import im.vector.app.features.settings.devices.v2.list.SessionInfoViewState
 class SessionOverviewFragment :
         VectorBaseFragment<FragmentSessionOverviewBinding>() {
 
+    @Inject lateinit var dateFormatter: VectorDateFormatter
+
     private val viewModel: SessionOverviewViewModel by fragmentViewModel()
 
     override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentSessionOverviewBinding {
@@ -89,9 +93,10 @@ class SessionOverviewFragment :
                 isCurrentSession = isCurrentSession,
                 deviceFullInfo = deviceFullInfo,
                 isDetailsButtonVisible = false,
-                hasLearnMoreLink = true
+                isLearnMoreLinkVisible = true,
+                isLastSeenDetailsVisible = true,
         )
-        views.sessionOverviewInfo.render(viewState)
+        views.sessionOverviewInfo.render(viewState, dateFormatter)
     }
 
     private fun hideSessionInfo() {
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt
index 9c40480270..1a1d3640a2 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt
@@ -46,10 +46,10 @@ class SessionOverviewViewModel @AssistedInject constructor(
     init {
         val currentDeviceId = session.sessionParams.deviceId.orEmpty()
         setState {
-            copy(isCurrentSession = sessionId.isNotEmpty() && sessionId == currentDeviceId)
+            copy(isCurrentSession = deviceId.isNotEmpty() && deviceId == currentDeviceId)
         }
 
-        observeSessionInfo(currentDeviceId)
+        observeSessionInfo(initialState.deviceId)
     }
 
     private fun observeSessionInfo(deviceId: String) {
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewState.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewState.kt
index 8fa19a6eee..c9f5635cbd 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewState.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewState.kt
@@ -22,11 +22,11 @@ import com.airbnb.mvrx.Uninitialized
 import im.vector.app.features.settings.devices.DeviceFullInfo
 
 data class SessionOverviewViewState(
-        val sessionId: String,
+        val deviceId: String,
         val isCurrentSession: Boolean = false,
         val deviceInfo: Async<DeviceFullInfo> = Uninitialized,
 ) : MavericksState {
     constructor(args: SessionOverviewArgs) : this(
-            sessionId = args.sessionId
+            deviceId = args.deviceId
     )
 }
diff --git a/vector/src/main/res/layout/view_session_info.xml b/vector/src/main/res/layout/view_session_info.xml
index 02aad7b19d..49e1ebbb77 100644
--- a/vector/src/main/res/layout/view_session_info.xml
+++ b/vector/src/main/res/layout/view_session_info.xml
@@ -72,6 +72,35 @@
         app:layout_constraintTop_toBottomOf="@id/sessionInfoVerificationStatusContainer"
         tools:text="@string/device_manager_verification_status_detail_current_session_verified" />
 
+    <TextView
+        android:id="@+id/sessionInfoLastActivityTextView"
+        style="@style/TextAppearance.Vector.Body.DevicesManagement"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginHorizontal="32dp"
+        android:layout_marginTop="12dp"
+        android:gravity="center"
+        android:visibility="gone"
+        tools:visibility="visible"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/sessionInfoVerificationStatusDetailTextView"
+        tools:text="Last activity Fri 14:59" />
+
+    <TextView
+        android:id="@+id/sessionInfoLastIPAddressTextView"
+        style="@style/TextAppearance.Vector.Body.DevicesManagement"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginHorizontal="32dp"
+        android:gravity="center"
+        android:visibility="gone"
+        tools:visibility="visible"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/sessionInfoLastActivityTextView"
+        tools:text="81.235.41.100 (United Kingdom)" />
+
     <Button
         android:id="@+id/sessionInfoVerifySessionButton"
         android:layout_width="0dp"
@@ -81,7 +110,7 @@
         android:text="@string/device_manager_verify_session"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/sessionInfoVerificationStatusDetailTextView" />
+        app:layout_constraintTop_toBottomOf="@id/sessionInfoLastIPAddressTextView" />
 
     <Button
         android:id="@+id/sessionInfoViewDetailsButton"
diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModelTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModelTest.kt
index 10b1c0fdb1..735c553808 100644
--- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModelTest.kt
+++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModelTest.kt
@@ -39,7 +39,7 @@ class SessionOverviewViewModelTest {
     val mvRxTestRule = MvRxTestRule(testDispatcher = UnconfinedTestDispatcher())
 
     private val args = SessionOverviewArgs(
-            sessionId = A_SESSION_ID
+            deviceId = A_SESSION_ID
     )
     private val fakeSession = FakeSession()
     private val getDeviceFullInfoUseCase = mockk<GetDeviceFullInfoUseCase>()
@@ -56,7 +56,7 @@ class SessionOverviewViewModelTest {
         val deviceFullInfo = mockk<DeviceFullInfo>()
         every { getDeviceFullInfoUseCase.execute(A_SESSION_ID) } returns flowOf(Optional(deviceFullInfo))
         val expectedState = SessionOverviewViewState(
-                sessionId = A_SESSION_ID,
+                deviceId = A_SESSION_ID,
                 isCurrentSession = true,
                 deviceInfo = Success(deviceFullInfo)
         )

From 19578cfa66ccdf491f20b8fa1325b95e6caa4e8c Mon Sep 17 00:00:00 2001
From: Maxime NATUREL <maxime.naturel@niji.fr>
Date: Fri, 2 Sep 2022 10:11:54 +0200
Subject: [PATCH 022/108] Fixing wrong copyright title

---
 .../crypto/store/db/mapper/MyDeviceLastSeenInfoEntityMapper.kt  | 2 +-
 .../store/db/mapper/MyDeviceLastSeenInfoEntityMapperTest.kt     | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/MyDeviceLastSeenInfoEntityMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/MyDeviceLastSeenInfoEntityMapper.kt
index 76e3171f4d..38a7569aab 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/MyDeviceLastSeenInfoEntityMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/MyDeviceLastSeenInfoEntityMapper.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 New Vector Ltd
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/MyDeviceLastSeenInfoEntityMapperTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/MyDeviceLastSeenInfoEntityMapperTest.kt
index e706fd6622..a27f430edc 100644
--- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/MyDeviceLastSeenInfoEntityMapperTest.kt
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/MyDeviceLastSeenInfoEntityMapperTest.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 New Vector Ltd
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.

From 9dcb2b31a3199f7ac5c9d08ff8c3f8c5b8738c39 Mon Sep 17 00:00:00 2001
From: Maxime NATUREL <maxime.naturel@niji.fr>
Date: Fri, 2 Sep 2022 16:45:00 +0200
Subject: [PATCH 023/108] Fix post rebase

---
 .../devices/v2/overview/GetDeviceFullInfoUseCase.kt        | 7 ++++++-
 vector/src/main/res/layout/fragment_settings_devices.xml   | 2 +-
 2 files changed, 7 insertions(+), 2 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt
index 3cde519385..07d29fc4e8 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt
@@ -21,6 +21,7 @@ import im.vector.app.core.di.ActiveSessionHolder
 import im.vector.app.features.settings.devices.DeviceFullInfo
 import im.vector.app.features.settings.devices.GetCurrentSessionCrossSigningInfoUseCase
 import im.vector.app.features.settings.devices.GetEncryptionTrustLevelForDeviceUseCase
+import im.vector.app.features.settings.devices.v2.list.CheckIfSessionIsInactiveUseCase
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.emptyFlow
@@ -32,8 +33,10 @@ class GetDeviceFullInfoUseCase @Inject constructor(
         private val activeSessionHolder: ActiveSessionHolder,
         private val getCurrentSessionCrossSigningInfoUseCase: GetCurrentSessionCrossSigningInfoUseCase,
         private val getEncryptionTrustLevelForDeviceUseCase: GetEncryptionTrustLevelForDeviceUseCase,
+        private val checkIfSessionIsInactiveUseCase: CheckIfSessionIsInactiveUseCase,
 ) {
 
+    // TODO update unit tests
     fun execute(deviceId: String): Flow<Optional<DeviceFullInfo>> {
         return activeSessionHolder.getSafeActiveSession()?.let { session ->
             val currentSessionCrossSigningInfo = getCurrentSessionCrossSigningInfoUseCase.execute()
@@ -45,10 +48,12 @@ class GetDeviceFullInfoUseCase @Inject constructor(
                 val cryptoInfo = cryptoDeviceInfo.getOrNull()
                 val fullInfo = if (info != null && cryptoInfo != null) {
                     val roomEncryptionTrustLevel = getEncryptionTrustLevelForDeviceUseCase.execute(currentSessionCrossSigningInfo, cryptoInfo)
+                    val isInactive = checkIfSessionIsInactiveUseCase.execute(info.lastSeenTs ?: 0)
                     DeviceFullInfo(
                             deviceInfo = info,
                             cryptoDeviceInfo = cryptoInfo,
-                            trustLevelForShield = roomEncryptionTrustLevel
+                            trustLevelForShield = roomEncryptionTrustLevel,
+                            isInactive = isInactive
                     )
                 } else {
                     null
diff --git a/vector/src/main/res/layout/fragment_settings_devices.xml b/vector/src/main/res/layout/fragment_settings_devices.xml
index b4f47302e1..9cefd6aa24 100644
--- a/vector/src/main/res/layout/fragment_settings_devices.xml
+++ b/vector/src/main/res/layout/fragment_settings_devices.xml
@@ -8,7 +8,7 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content">
 
-        <im.vector.app.features.settings.devices.v2.list.DevicesListHeaderView
+        <im.vector.app.features.settings.devices.v2.list.SessionsListHeaderView
             android:id="@+id/deviceListHeaderSectionSecurityRecommendations"
             android:layout_width="0dp"
             android:layout_height="wrap_content"

From 1c501a00833f54042100ea3f19cf697d8e2481e9 Mon Sep 17 00:00:00 2001
From: Maxime NATUREL <maxime.naturel@niji.fr>
Date: Fri, 2 Sep 2022 16:59:49 +0200
Subject: [PATCH 024/108] Adding comment with examples of some parametrized
 strings

---
 library/ui-strings/src/main/res/values/strings.xml | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml
index 892c31ecf8..6c71cd8c0a 100644
--- a/library/ui-strings/src/main/res/values/strings.xml
+++ b/library/ui-strings/src/main/res/values/strings.xml
@@ -3229,7 +3229,9 @@
     <string name="device_manager_view_details">View Details</string>
     <string name="device_manager_header_section_current_session">Current Session</string>
     <string name="device_manager_other_sessions_view_all">View All (%1$d)</string>
+    <!--  Examples: Verified · Last activity Yesterday at 6PM, Verified · Last activity Aug 31 at 5:47PM -->
     <string name="device_manager_other_sessions_description_verified">Verified · Last activity %1$s</string>
+    <!--  Examples: Unverified · Last activity Yesterday at 6PM, Unverified · Last activity Aug 31 at 5:47PM -->
     <string name="device_manager_other_sessions_description_unverified">Unverified · Last activity %1$s</string>
     <!--  Example: Inactive for 90+ days (Dec 25, 2021) -->
     <plurals name="device_manager_other_sessions_description_inactive">
@@ -3247,6 +3249,7 @@
     </plurals>
     <string name="device_manager_current_session_title">Current Session</string>
     <string name="device_manager_session_title">Session</string>
+    <!--  Examples: Last activity Yesterday at 6PM, Last activity Aug 31 at 5:47PM -->
     <string name="device_manager_session_last_activity">Last activity %1$s</string>
 
 </resources>

From af484813b57107469d8ad27bf1c2f73b67464f1e Mon Sep 17 00:00:00 2001
From: Maxime NATUREL <maxime.naturel@niji.fr>
Date: Mon, 5 Sep 2022 09:40:02 +0200
Subject: [PATCH 025/108] Rendering inactive status in SessionInfoView

---
 .../res/values/styles_devices_management.xml  |  1 +
 .../v2/VectorSettingsDevicesFragment.kt       | 10 +++-
 .../devices/v2/list/SessionInfoView.kt        | 46 ++++++++++++++++---
 .../v2/overview/SessionOverviewFragment.kt    |  8 +++-
 .../src/main/res/layout/view_session_info.xml | 10 ++--
 5 files changed, 61 insertions(+), 14 deletions(-)

diff --git a/library/ui-styles/src/main/res/values/styles_devices_management.xml b/library/ui-styles/src/main/res/values/styles_devices_management.xml
index 2a63c2ed36..6fb236d3e6 100644
--- a/library/ui-styles/src/main/res/values/styles_devices_management.xml
+++ b/library/ui-styles/src/main/res/values/styles_devices_management.xml
@@ -7,6 +7,7 @@
 
     <style name="TextAppearance.Vector.Body.DevicesManagement">
         <item name="android:textColor">?vctr_content_secondary</item>
+        <item name="android:drawablePadding">12dp</item>
     </style>
 
 </resources>
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt
index 2262f083da..10ebf3a42f 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt
@@ -34,6 +34,8 @@ import im.vector.app.R
 import im.vector.app.core.date.VectorDateFormatter
 import im.vector.app.core.dialogs.ManuallyVerifyDialog
 import im.vector.app.core.platform.VectorBaseFragment
+import im.vector.app.core.resources.ColorProvider
+import im.vector.app.core.resources.DrawableProvider
 import im.vector.app.databinding.FragmentSettingsDevicesBinding
 import im.vector.app.features.crypto.recover.SetupMode
 import im.vector.app.features.crypto.verification.VerificationBottomSheet
@@ -41,9 +43,9 @@ import im.vector.app.features.settings.devices.DeviceFullInfo
 import im.vector.app.features.settings.devices.DevicesAction
 import im.vector.app.features.settings.devices.DevicesViewEvents
 import im.vector.app.features.settings.devices.DevicesViewModel
+import im.vector.app.features.settings.devices.v2.list.OtherSessionsController
 import im.vector.app.features.settings.devices.v2.list.SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS
 import im.vector.app.features.settings.devices.v2.list.SecurityRecommendationViewState
-import im.vector.app.features.settings.devices.v2.list.OtherSessionsController
 import im.vector.app.features.settings.devices.v2.list.SessionInfoViewState
 import javax.inject.Inject
 
@@ -58,6 +60,10 @@ class VectorSettingsDevicesFragment :
 
     @Inject lateinit var dateFormatter: VectorDateFormatter
 
+    @Inject lateinit var drawableProvider: DrawableProvider
+
+    @Inject lateinit var colorProvider: ColorProvider
+
     private val viewModel: DevicesViewModel by fragmentViewModel()
 
     override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentSettingsDevicesBinding {
@@ -217,7 +223,7 @@ class VectorSettingsDevicesFragment :
                     isCurrentSession = true,
                     deviceFullInfo = it
             )
-            views.deviceListCurrentSession.render(viewState, dateFormatter)
+            views.deviceListCurrentSession.render(viewState, dateFormatter, drawableProvider, colorProvider)
             views.deviceListCurrentSession.debouncedClicks {
                 currentDeviceInfo.deviceInfo.deviceId?.let { deviceId -> navigateToSessionOverview(deviceId) }
             }
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoView.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoView.kt
index df50666b3b..767f09482b 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoView.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoView.kt
@@ -25,6 +25,8 @@ import im.vector.app.R
 import im.vector.app.core.date.DateFormatKind
 import im.vector.app.core.date.VectorDateFormatter
 import im.vector.app.core.extensions.setTextWithColoredPart
+import im.vector.app.core.resources.ColorProvider
+import im.vector.app.core.resources.DrawableProvider
 import im.vector.app.databinding.ViewSessionInfoBinding
 import im.vector.app.features.themes.ThemeUtils
 import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
@@ -47,14 +49,26 @@ class SessionInfoView @JvmOverloads constructor(
 
     val viewDetailsButton = views.sessionInfoViewDetailsButton
 
-    fun render(sessionInfoViewState: SessionInfoViewState, dateFormatter: VectorDateFormatter) {
+    fun render(
+            sessionInfoViewState: SessionInfoViewState,
+            dateFormatter: VectorDateFormatter,
+            drawableProvider: DrawableProvider,
+            colorProvider: ColorProvider,
+    ) {
         renderDeviceInfo(sessionInfoViewState.deviceFullInfo.deviceInfo.displayName.orEmpty())
         renderVerificationStatus(
                 sessionInfoViewState.deviceFullInfo.trustLevelForShield,
                 sessionInfoViewState.isCurrentSession,
-                sessionInfoViewState.isLearnMoreLinkVisible
+                sessionInfoViewState.isLearnMoreLinkVisible,
+        )
+        renderDeviceLastSeenDetails(
+                sessionInfoViewState.deviceFullInfo.isInactive,
+                sessionInfoViewState.deviceFullInfo.deviceInfo,
+                sessionInfoViewState.isLastSeenDetailsVisible,
+                dateFormatter,
+                drawableProvider,
+                colorProvider,
         )
-        renderDeviceLastSeenDetails(sessionInfoViewState.deviceFullInfo.deviceInfo, dateFormatter, sessionInfoViewState.isLastSeenDetailsVisible)
         renderDetailsButton(sessionInfoViewState.isDetailsButtonVisible)
     }
 
@@ -123,16 +137,36 @@ class SessionInfoView @JvmOverloads constructor(
     }
 
     private fun renderDeviceLastSeenDetails(
+            isInactive: Boolean,
             deviceInfo: DeviceInfo,
-            dateFormatter: VectorDateFormatter,
             isLastSeenDetailsVisible: Boolean,
+            dateFormatter: VectorDateFormatter,
+            drawableProvider: DrawableProvider,
+            colorProvider: ColorProvider,
     ) {
         deviceInfo.lastSeenTs
                 ?.takeIf { isLastSeenDetailsVisible }
                 ?.let { timestamp ->
                     views.sessionInfoLastActivityTextView.isVisible = true
-                    val formattedTs = dateFormatter.format(timestamp, DateFormatKind.DEFAULT_DATE_AND_TIME)
-                    views.sessionInfoLastActivityTextView.text = context.getString(R.string.device_manager_session_last_activity, formattedTs)
+                    views.sessionInfoLastActivityTextView.text = if (isInactive) {
+                        val formattedTs = dateFormatter.format(timestamp, DateFormatKind.TIMELINE_DAY_DIVIDER)
+                        context.resources.getQuantityString(
+                                R.plurals.device_manager_other_sessions_description_inactive,
+                                SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS,
+                                SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS,
+                                formattedTs
+                        )
+                    } else {
+                        val formattedTs = dateFormatter.format(timestamp, DateFormatKind.DEFAULT_DATE_AND_TIME)
+                        context.getString(R.string.device_manager_session_last_activity, formattedTs)
+                    }
+                    val drawable = if (isInactive) {
+                        val drawableColor = colorProvider.getColorFromAttribute(R.attr.vctr_content_secondary)
+                        drawableProvider.getDrawable(R.drawable.ic_inactive_sessions, drawableColor)
+                    } else {
+                        null
+                    }
+                    views.sessionInfoLastActivityTextView.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null)
                 }
                 ?: run {
                     views.sessionInfoLastActivityTextView.isGone = true
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt
index dbe75c94cc..a6bac6087b 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt
@@ -31,6 +31,8 @@ import dagger.hilt.android.AndroidEntryPoint
 import im.vector.app.R
 import im.vector.app.core.date.VectorDateFormatter
 import im.vector.app.core.platform.VectorBaseFragment
+import im.vector.app.core.resources.ColorProvider
+import im.vector.app.core.resources.DrawableProvider
 import im.vector.app.databinding.FragmentSessionOverviewBinding
 import im.vector.app.features.settings.devices.DeviceFullInfo
 import im.vector.app.features.settings.devices.v2.list.SessionInfoViewState
@@ -45,6 +47,10 @@ class SessionOverviewFragment :
 
     @Inject lateinit var dateFormatter: VectorDateFormatter
 
+    @Inject lateinit var drawableProvider: DrawableProvider
+
+    @Inject lateinit var colorProvider: ColorProvider
+
     private val viewModel: SessionOverviewViewModel by fragmentViewModel()
 
     override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentSessionOverviewBinding {
@@ -96,7 +102,7 @@ class SessionOverviewFragment :
                 isLearnMoreLinkVisible = true,
                 isLastSeenDetailsVisible = true,
         )
-        views.sessionOverviewInfo.render(viewState, dateFormatter)
+        views.sessionOverviewInfo.render(viewState, dateFormatter, drawableProvider, colorProvider)
     }
 
     private fun hideSessionInfo() {
diff --git a/vector/src/main/res/layout/view_session_info.xml b/vector/src/main/res/layout/view_session_info.xml
index 49e1ebbb77..18daae825a 100644
--- a/vector/src/main/res/layout/view_session_info.xml
+++ b/vector/src/main/res/layout/view_session_info.xml
@@ -79,13 +79,13 @@
         android:layout_height="wrap_content"
         android:layout_marginHorizontal="32dp"
         android:layout_marginTop="12dp"
-        android:gravity="center"
         android:visibility="gone"
-        tools:visibility="visible"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toBottomOf="@id/sessionInfoVerificationStatusDetailTextView"
-        tools:text="Last activity Fri 14:59" />
+        app:layout_constraintWidth="wrap_content_constrained"
+        tools:text="Last activity Fri 14:59"
+        tools:visibility="visible" />
 
     <TextView
         android:id="@+id/sessionInfoLastIPAddressTextView"
@@ -95,11 +95,11 @@
         android:layout_marginHorizontal="32dp"
         android:gravity="center"
         android:visibility="gone"
-        tools:visibility="visible"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toBottomOf="@id/sessionInfoLastActivityTextView"
-        tools:text="81.235.41.100 (United Kingdom)" />
+        tools:text="81.235.41.100 (United Kingdom)"
+        tools:visibility="visible" />
 
     <Button
         android:id="@+id/sessionInfoVerifySessionButton"

From 838064dad36dff5ffb2d5ed7e9e7ee7a016d4788 Mon Sep 17 00:00:00 2001
From: Maxime NATUREL <maxime.naturel@niji.fr>
Date: Mon, 5 Sep 2022 09:50:16 +0200
Subject: [PATCH 026/108] Update unit tests

---
 .../v2/overview/GetDeviceFullInfoUseCase.kt   |  1 -
 .../overview/GetDeviceFullInfoUseCaseTest.kt  | 22 ++++++++++++++-----
 2 files changed, 16 insertions(+), 7 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt
index 07d29fc4e8..c3579b68c3 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt
@@ -36,7 +36,6 @@ class GetDeviceFullInfoUseCase @Inject constructor(
         private val checkIfSessionIsInactiveUseCase: CheckIfSessionIsInactiveUseCase,
 ) {
 
-    // TODO update unit tests
     fun execute(deviceId: String): Flow<Optional<DeviceFullInfo>> {
         return activeSessionHolder.getSafeActiveSession()?.let { session ->
             val currentSessionCrossSigningInfo = getCurrentSessionCrossSigningInfoUseCase.execute()
diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCaseTest.kt
index 3d56f4ff11..e3d62961a7 100644
--- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCaseTest.kt
+++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCaseTest.kt
@@ -22,6 +22,7 @@ import im.vector.app.features.settings.devices.CurrentSessionCrossSigningInfo
 import im.vector.app.features.settings.devices.DeviceFullInfo
 import im.vector.app.features.settings.devices.GetCurrentSessionCrossSigningInfoUseCase
 import im.vector.app.features.settings.devices.GetEncryptionTrustLevelForDeviceUseCase
+import im.vector.app.features.settings.devices.v2.list.CheckIfSessionIsInactiveUseCase
 import im.vector.app.test.fakes.FakeActiveSessionHolder
 import im.vector.app.test.fakes.FakeFlowLiveDataConversions
 import im.vector.app.test.fakes.givenAsFlow
@@ -41,18 +42,21 @@ import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
 import org.matrix.android.sdk.api.util.Optional
 
 private const val A_DEVICE_ID = "device-id"
+private const val A_TIMESTAMP = 123L
 
 class GetDeviceFullInfoUseCaseTest {
 
     private val fakeActiveSessionHolder = FakeActiveSessionHolder()
     private val getCurrentSessionCrossSigningInfoUseCase = mockk<GetCurrentSessionCrossSigningInfoUseCase>()
     private val getEncryptionTrustLevelForDeviceUseCase = mockk<GetEncryptionTrustLevelForDeviceUseCase>()
+    private val checkIfSessionIsInactiveUseCase = mockk<CheckIfSessionIsInactiveUseCase>()
     private val fakeFlowLiveDataConversions = FakeFlowLiveDataConversions()
 
     private val getDeviceFullInfoUseCase = GetDeviceFullInfoUseCase(
             activeSessionHolder = fakeActiveSessionHolder.instance,
             getCurrentSessionCrossSigningInfoUseCase = getCurrentSessionCrossSigningInfoUseCase,
-            getEncryptionTrustLevelForDeviceUseCase = getEncryptionTrustLevelForDeviceUseCase
+            getEncryptionTrustLevelForDeviceUseCase = getEncryptionTrustLevelForDeviceUseCase,
+            checkIfSessionIsInactiveUseCase = checkIfSessionIsInactiveUseCase,
     )
 
     @Before
@@ -66,15 +70,19 @@ class GetDeviceFullInfoUseCaseTest {
     }
 
     @Test
-    fun `given an active session and info for device when getting device info then the result is correct`() = runTest {
+    fun `given current session and info for device when getting device info then the result is correct`() = runTest {
         val currentSessionCrossSigningInfo = givenCurrentSessionCrossSigningInfo()
-        val deviceInfo = DeviceInfo()
+        val deviceInfo = DeviceInfo(
+                lastSeenTs = A_TIMESTAMP
+        )
         fakeActiveSessionHolder.fakeSession.fakeCryptoService.myDevicesInfoWithIdLiveData = MutableLiveData(Optional(deviceInfo))
         fakeActiveSessionHolder.fakeSession.fakeCryptoService.myDevicesInfoWithIdLiveData.givenAsFlow()
         val cryptoDeviceInfo = CryptoDeviceInfo(deviceId = A_DEVICE_ID, userId = "")
         fakeActiveSessionHolder.fakeSession.fakeCryptoService.cryptoDeviceInfoWithIdLiveData = MutableLiveData(Optional(cryptoDeviceInfo))
         fakeActiveSessionHolder.fakeSession.fakeCryptoService.cryptoDeviceInfoWithIdLiveData.givenAsFlow()
         val trustLevel = givenTrustLevel(currentSessionCrossSigningInfo, cryptoDeviceInfo)
+        val isInactive = false
+        every { checkIfSessionIsInactiveUseCase.execute(any()) } returns isInactive
 
         val deviceFullInfo = getDeviceFullInfoUseCase.execute(A_DEVICE_ID).firstOrNull()
 
@@ -82,7 +90,8 @@ class GetDeviceFullInfoUseCaseTest {
                 DeviceFullInfo(
                         deviceInfo = deviceInfo,
                         cryptoDeviceInfo = cryptoDeviceInfo,
-                        trustLevelForShield = trustLevel
+                        trustLevelForShield = trustLevel,
+                        isInactive = isInactive,
                 )
         )
         verify { fakeActiveSessionHolder.instance.getSafeActiveSession() }
@@ -90,10 +99,11 @@ class GetDeviceFullInfoUseCaseTest {
         verify { getEncryptionTrustLevelForDeviceUseCase.execute(currentSessionCrossSigningInfo, cryptoDeviceInfo) }
         verify { fakeActiveSessionHolder.fakeSession.fakeCryptoService.getMyDevicesInfoLive(A_DEVICE_ID).asFlow() }
         verify { fakeActiveSessionHolder.fakeSession.fakeCryptoService.getLiveCryptoDeviceInfoWithId(A_DEVICE_ID).asFlow() }
+        verify { checkIfSessionIsInactiveUseCase.execute(A_TIMESTAMP) }
     }
 
     @Test
-    fun `given an active session and no info for device when getting device info then the result is null`() = runTest {
+    fun `given current session and no info for device when getting device info then the result is null`() = runTest {
         givenCurrentSessionCrossSigningInfo()
         fakeActiveSessionHolder.fakeSession.fakeCryptoService.myDevicesInfoWithIdLiveData = MutableLiveData(Optional(null))
         fakeActiveSessionHolder.fakeSession.fakeCryptoService.myDevicesInfoWithIdLiveData.givenAsFlow()
@@ -109,7 +119,7 @@ class GetDeviceFullInfoUseCaseTest {
     }
 
     @Test
-    fun `given no active session when getting device info then the result is empty`() = runTest {
+    fun `given no current session when getting device info then the result is empty`() = runTest {
         fakeActiveSessionHolder.givenGetSafeActiveSessionReturns(null)
 
         val deviceFullInfo = getDeviceFullInfoUseCase.execute(A_DEVICE_ID).firstOrNull()

From eb59a534e0fbe018117ed4fd46ded23096472c3f Mon Sep 17 00:00:00 2001
From: Maxime NATUREL <maxime.naturel@niji.fr>
Date: Mon, 5 Sep 2022 10:54:38 +0200
Subject: [PATCH 027/108] Fix unused string warning

---
 library/ui-strings/src/main/res/values/strings.xml | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml
index 6c71cd8c0a..7e6ed622c5 100644
--- a/library/ui-strings/src/main/res/values/strings.xml
+++ b/library/ui-strings/src/main/res/values/strings.xml
@@ -3227,7 +3227,8 @@
     <string name="device_manager_verification_status_detail_other_session_unverified">Verify or sign out from this session for best security and reliability.</string>
     <string name="device_manager_verify_session">Verify Session</string>
     <string name="device_manager_view_details">View Details</string>
-    <string name="device_manager_header_section_current_session">Current Session</string>
+    <!-- TODO TO BE REMOVED: replaced by device_manager_current_session_title -->
+    <string name="device_manager_header_section_current_session" tools:ignore="UnusedResources">Current Session</string>
     <string name="device_manager_other_sessions_view_all">View All (%1$d)</string>
     <!--  Examples: Verified · Last activity Yesterday at 6PM, Verified · Last activity Aug 31 at 5:47PM -->
     <string name="device_manager_other_sessions_description_verified">Verified · Last activity %1$s</string>

From 83990b6a0be82abb0fff1437eb4e4f5824a13c63 Mon Sep 17 00:00:00 2001
From: Onuray Sahin <onuray.sahin@gmail.com>
Date: Tue, 6 Sep 2022 14:48:39 +0300
Subject: [PATCH 028/108] Add string resources.

---
 library/ui-strings/src/main/res/values/strings.xml | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml
index 7e6ed622c5..fe17d2ae21 100644
--- a/library/ui-strings/src/main/res/values/strings.xml
+++ b/library/ui-strings/src/main/res/values/strings.xml
@@ -3252,5 +3252,13 @@
     <string name="device_manager_session_title">Session</string>
     <!--  Examples: Last activity Yesterday at 6PM, Last activity Aug 31 at 5:47PM -->
     <string name="device_manager_session_last_activity">Last activity %1$s</string>
+    <string name="device_manager_filter_bottom_sheet_title">Filter</string>
+    <string name="device_manager_filter_option_all_sessions">All session</string>
+    <string name="device_manager_filter_option_verified">Verified</string>
+    <string name="device_manager_filter_option_verified_description">Ready for secure messaging</string>
+    <string name="device_manager_filter_option_unverified">Unverified</string>
+    <string name="device_manager_filter_option_unverified_description">Not ready for secure messaging</string>
+    <string name="device_manager_filter_option_inactive">Inactive</string>
+    <string name="device_manager_filter_option_inactive_description">Inactive for %1$d days or longer</string>
 
 </resources>

From 8ac876380b19ec319b748b746e99123c5eb87e08 Mon Sep 17 00:00:00 2001
From: Onuray Sahin <onuray.sahin@gmail.com>
Date: Tue, 6 Sep 2022 14:49:33 +0300
Subject: [PATCH 029/108] Create filter bottom sheet layout.

---
 .../bottom_sheet_device_manager_filter.xml    | 85 +++++++++++++++++++
 1 file changed, 85 insertions(+)
 create mode 100644 vector/src/main/res/layout/bottom_sheet_device_manager_filter.xml

diff --git a/vector/src/main/res/layout/bottom_sheet_device_manager_filter.xml b/vector/src/main/res/layout/bottom_sheet_device_manager_filter.xml
new file mode 100644
index 0000000000..309ce1ec84
--- /dev/null
+++ b/vector/src/main/res/layout/bottom_sheet_device_manager_filter.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:paddingHorizontal="24dp">
+
+    <View
+        android:layout_width="36dp"
+        android:layout_height="6dp"
+        android:layout_gravity="center_horizontal"
+        android:layout_marginTop="8dp"
+        android:background="@drawable/ic_bottom_sheet_handle" />
+
+    <TextView
+        style="@style/TextAppearance.Vector.Subtitle.Medium"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="12dp"
+        android:text="@string/device_manager_filter_bottom_sheet_title" />
+
+    <RadioGroup
+        android:id="@+id/filterOptionsRadioGroup"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="24dp"
+        android:layoutDirection="rtl"
+        android:showDividers="none">
+
+        <RadioButton
+            android:id="@+id/filterOptionAllSessionsRadioButton"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:checked="true"
+            android:minHeight="0dp"
+            android:text="@string/device_manager_filter_option_all_sessions" />
+
+        <RadioButton
+            android:id="@+id/filterOptionVerifiedRadioButton"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="24dp"
+            android:minHeight="0dp"
+            android:text="@string/device_manager_filter_option_verified" />
+
+        <TextView
+            style="@style/TextAppearance.Vector.Body.DevicesManagement"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="end"
+            android:text="@string/device_manager_filter_option_verified_description" />
+
+        <RadioButton
+            android:id="@+id/filterOptionUnverifiedRadioButton"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="16dp"
+            android:minHeight="0dp"
+            android:text="@string/device_manager_filter_option_unverified" />
+
+        <TextView
+            style="@style/TextAppearance.Vector.Body.DevicesManagement"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="end"
+            android:text="@string/device_manager_filter_option_unverified_description" />
+
+        <RadioButton
+            android:id="@+id/filterOptionInactiveRadioButton"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="16dp"
+            android:minHeight="0dp"
+            android:text="@string/device_manager_filter_option_inactive" />
+
+        <TextView
+            style="@style/TextAppearance.Vector.Body.DevicesManagement"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="end"
+            android:text="@string/device_manager_filter_option_inactive_description" />
+
+    </RadioGroup>
+
+</LinearLayout>

From 5485b9a53076ff43506fdd009abfd0c98c08ea77 Mon Sep 17 00:00:00 2001
From: Onuray Sahin <onuray.sahin@gmail.com>
Date: Tue, 6 Sep 2022 15:56:50 +0300
Subject: [PATCH 030/108] Implement device manager filter bottom sheet.

---
 .../src/main/res/values/strings.xml           |  5 +-
 .../filter/DeviceManagerFilterBottomSheet.kt  | 73 +++++++++++++++++++
 .../v2/filter/DeviceManagerFilterType.kt      | 24 ++++++
 3 files changed, 101 insertions(+), 1 deletion(-)
 create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/filter/DeviceManagerFilterBottomSheet.kt
 create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/filter/DeviceManagerFilterType.kt

diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml
index fe17d2ae21..05283fd7aa 100644
--- a/library/ui-strings/src/main/res/values/strings.xml
+++ b/library/ui-strings/src/main/res/values/strings.xml
@@ -3259,6 +3259,9 @@
     <string name="device_manager_filter_option_unverified">Unverified</string>
     <string name="device_manager_filter_option_unverified_description">Not ready for secure messaging</string>
     <string name="device_manager_filter_option_inactive">Inactive</string>
-    <string name="device_manager_filter_option_inactive_description">Inactive for %1$d days or longer</string>
+    <plurals name="device_manager_filter_option_inactive_description">
+        <item quantity="one">Inactive for %1$d day or longer</item>
+        <item quantity="other">Inactive for %1$d days or longer</item>
+    </plurals>
 
 </resources>
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/filter/DeviceManagerFilterBottomSheet.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/filter/DeviceManagerFilterBottomSheet.kt
new file mode 100644
index 0000000000..4848f24b5f
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/filter/DeviceManagerFilterBottomSheet.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.settings.devices.v2.filter
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import dagger.hilt.android.AndroidEntryPoint
+import im.vector.app.R
+import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
+import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment.ResultListener.Companion.RESULT_OK
+import im.vector.app.databinding.BottomSheetDeviceManagerFilterBinding
+import im.vector.app.features.settings.devices.v2.list.SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS
+
+@AndroidEntryPoint
+class DeviceManagerFilterBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetDeviceManagerFilterBinding>() {
+
+    override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetDeviceManagerFilterBinding {
+        return BottomSheetDeviceManagerFilterBinding.inflate(inflater, container, false)
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        initFilterRadioGroup()
+    }
+
+    private fun initFilterRadioGroup() {
+        views.filterOptionInactiveRadioButton.text = resources.getQuantityString(
+                R.plurals.device_manager_filter_option_inactive_description,
+                SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS,
+                SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS
+        )
+
+        views.filterOptionsRadioGroup.setOnCheckedChangeListener { _, checkedId ->
+            onFilterTypeChanged(checkedId)
+        }
+    }
+
+    private fun onFilterTypeChanged(checkedId: Int) {
+        val filterType = when (checkedId) {
+            R.id.filterOptionAllSessionsRadioButton -> DeviceManagerFilterType.ALL_SESSIONS
+            R.id.filterOptionVerifiedRadioButton -> DeviceManagerFilterType.VERIFIED
+            R.id.filterOptionUnverifiedRadioButton -> DeviceManagerFilterType.UNVERIFIED
+            R.id.filterOptionInactiveRadioButton -> DeviceManagerFilterType.INACTIVE
+            else -> DeviceManagerFilterType.ALL_SESSIONS
+        }
+        resultListener?.onBottomSheetResult(RESULT_OK, filterType)
+        dismiss()
+    }
+
+    companion object {
+        fun newInstance(resultListener: ResultListener): DeviceManagerFilterBottomSheet {
+            val bottomSheet = DeviceManagerFilterBottomSheet()
+            bottomSheet.resultListener = resultListener
+            return bottomSheet
+        }
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/filter/DeviceManagerFilterType.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/filter/DeviceManagerFilterType.kt
new file mode 100644
index 0000000000..a1ef08f7df
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/filter/DeviceManagerFilterType.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.settings.devices.v2.filter
+
+enum class DeviceManagerFilterType {
+    ALL_SESSIONS,
+    VERIFIED,
+    UNVERIFIED,
+    INACTIVE,
+}

From 604b7dafbdf272ea529f052163682f1ad9a0007f Mon Sep 17 00:00:00 2001
From: Onuray Sahin <onuray.sahin@gmail.com>
Date: Tue, 6 Sep 2022 17:09:07 +0300
Subject: [PATCH 031/108] Create other sessions fragment.

---
 .../src/main/res/values/strings.xml           |  2 +
 .../v2/othersessions/OtherSessionsFragment.kt | 56 +++++++++++++++++++
 .../circle_with_transparent_border.xml        | 14 +++++
 .../res/layout/fragment_other_sessions.xml    | 48 ++++++++++++++++
 4 files changed, 120 insertions(+)
 create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt
 create mode 100644 vector/src/main/res/drawable/circle_with_transparent_border.xml
 create mode 100644 vector/src/main/res/layout/fragment_other_sessions.xml

diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml
index 05283fd7aa..8621fa7a4b 100644
--- a/library/ui-strings/src/main/res/values/strings.xml
+++ b/library/ui-strings/src/main/res/values/strings.xml
@@ -3263,5 +3263,7 @@
         <item quantity="one">Inactive for %1$d day or longer</item>
         <item quantity="other">Inactive for %1$d days or longer</item>
     </plurals>
+    <string name="device_manager_other_sessions_title">Other sessions</string>
+    <string name="a11y_device_manager_filter">Filter</string>
 
 </resources>
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt
new file mode 100644
index 0000000000..af4eef450f
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.settings.devices.v2.othersessions
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Toast
+import dagger.hilt.android.AndroidEntryPoint
+import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
+import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment.ResultListener.Companion.RESULT_OK
+import im.vector.app.core.platform.VectorBaseFragment
+import im.vector.app.databinding.FragmentOtherSessionsBinding
+import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterBottomSheet
+
+@AndroidEntryPoint
+class OtherSessionsFragment : VectorBaseFragment<FragmentOtherSessionsBinding>(), VectorBaseBottomSheetDialogFragment.ResultListener {
+
+    override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentOtherSessionsBinding {
+        return FragmentOtherSessionsBinding.inflate(layoutInflater, container, false)
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        initFilterView()
+    }
+
+    private fun initFilterView() {
+        views.otherSessionsFilterFrameLayout.setOnClickListener {
+            DeviceManagerFilterBottomSheet
+                    .newInstance(this)
+                    .show(requireActivity().supportFragmentManager, "SHOW_DEVICE_MANAGER_FILTER_BOTTOM_SHEET")
+        }
+    }
+
+    override fun onBottomSheetResult(resultCode: Int, data: Any?) {
+        if (resultCode == RESULT_OK && data != null) {
+            Toast.makeText(requireContext(), data.toString(), Toast.LENGTH_LONG)
+        }
+    }
+}
diff --git a/vector/src/main/res/drawable/circle_with_transparent_border.xml b/vector/src/main/res/drawable/circle_with_transparent_border.xml
new file mode 100644
index 0000000000..610b8ff4e2
--- /dev/null
+++ b/vector/src/main/res/drawable/circle_with_transparent_border.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="ring"
+    android:innerRadius="0dp"
+    android:thicknessRatio="2"
+    android:useLevel="false">
+
+    <solid android:color="?colorPrimary" />
+
+    <stroke
+        android:width="3dp"
+        android:color="?android:colorBackground" />
+
+</shape>
diff --git a/vector/src/main/res/layout/fragment_other_sessions.xml b/vector/src/main/res/layout/fragment_other_sessions.xml
new file mode 100644
index 0000000000..669d56ba15
--- /dev/null
+++ b/vector/src/main/res/layout/fragment_other_sessions.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <com.google.android.material.appbar.AppBarLayout
+        android:id="@+id/appBarLayout"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent">
+
+        <com.google.android.material.appbar.MaterialToolbar
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            app:navigationIcon="@drawable/ic_back_24dp"
+            app:title="@string/device_manager_other_sessions_title">
+
+            <FrameLayout
+                android:id="@+id/otherSessionsFilterFrameLayout"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="end"
+                android:layout_marginEnd="16dp">
+
+                <ImageView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:contentDescription="@string/a11y_device_manager_filter"
+                    android:src="@drawable/ic_filter" />
+
+                <ImageView
+                    android:id="@+id/otherSessionsFilterBadgeImageView"
+                    android:layout_width="12dp"
+                    android:layout_height="12dp"
+                    android:layout_marginStart="12dp"
+                    android:importantForAccessibility="no"
+                    android:src="@drawable/circle_with_transparent_border" />
+
+            </FrameLayout>
+
+        </com.google.android.material.appbar.MaterialToolbar>
+
+    </com.google.android.material.appbar.AppBarLayout>
+
+</androidx.constraintlayout.widget.ConstraintLayout>

From 3bfeaa764cb3181fa05ce486e68a5de7be887971 Mon Sep 17 00:00:00 2001
From: Onuray Sahin <onuray.sahin@gmail.com>
Date: Tue, 6 Sep 2022 17:23:03 +0300
Subject: [PATCH 032/108] Create other sessions activity.

---
 .../v2/VectorSettingsDevicesViewNavigator.kt  |  5 +++
 .../v2/othersessions/OtherSessionsActivity.kt | 45 +++++++++++++++++++
 .../v2/othersessions/OtherSessionsFragment.kt |  5 ++-
 .../res/layout/fragment_other_sessions.xml    |  1 +
 4 files changed, 54 insertions(+), 2 deletions(-)
 create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsActivity.kt

diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesViewNavigator.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesViewNavigator.kt
index 54eed3bc14..486785c918 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesViewNavigator.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesViewNavigator.kt
@@ -17,6 +17,7 @@
 package im.vector.app.features.settings.devices.v2
 
 import android.content.Context
+import im.vector.app.features.settings.devices.v2.othersessions.OtherSessionsActivity
 import im.vector.app.features.settings.devices.v2.overview.SessionOverviewActivity
 import javax.inject.Inject
 
@@ -25,4 +26,8 @@ class VectorSettingsDevicesViewNavigator @Inject constructor() {
     fun navigateToSessionOverview(context: Context, deviceId: String) {
         context.startActivity(SessionOverviewActivity.newIntent(context, deviceId))
     }
+
+    fun navigateToOtherSessions(context: Context) {
+        context.startActivity(OtherSessionsActivity.newIntent(context))
+    }
 }
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsActivity.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsActivity.kt
new file mode 100644
index 0000000000..ba832c5b00
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsActivity.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.settings.devices.v2.othersessions
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import dagger.hilt.android.AndroidEntryPoint
+import im.vector.app.core.extensions.addFragment
+import im.vector.app.core.platform.SimpleFragmentActivity
+
+@AndroidEntryPoint
+class OtherSessionsActivity : SimpleFragmentActivity() {
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        if (isFirstCreation()) {
+            addFragment(
+                    container = views.container,
+                    fragmentClass = OtherSessionsFragment::class.java
+            )
+        }
+    }
+
+    companion object {
+        fun newIntent(context: Context): Intent {
+            return Intent(context, OtherSessionsActivity::class.java)
+        }
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt
index af4eef450f..d28b2a40f7 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt
@@ -37,11 +37,12 @@ class OtherSessionsFragment : VectorBaseFragment<FragmentOtherSessionsBinding>()
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
+        setupToolbar(views.otherSessionsToolbar)
         initFilterView()
     }
 
     private fun initFilterView() {
-        views.otherSessionsFilterFrameLayout.setOnClickListener {
+        views.otherSessionsFilterFrameLayout.debouncedClicks {
             DeviceManagerFilterBottomSheet
                     .newInstance(this)
                     .show(requireActivity().supportFragmentManager, "SHOW_DEVICE_MANAGER_FILTER_BOTTOM_SHEET")
@@ -50,7 +51,7 @@ class OtherSessionsFragment : VectorBaseFragment<FragmentOtherSessionsBinding>()
 
     override fun onBottomSheetResult(resultCode: Int, data: Any?) {
         if (resultCode == RESULT_OK && data != null) {
-            Toast.makeText(requireContext(), data.toString(), Toast.LENGTH_LONG)
+            Toast.makeText(requireContext(), data.toString(), Toast.LENGTH_LONG).show()
         }
     }
 }
diff --git a/vector/src/main/res/layout/fragment_other_sessions.xml b/vector/src/main/res/layout/fragment_other_sessions.xml
index 669d56ba15..e0450fb5e5 100644
--- a/vector/src/main/res/layout/fragment_other_sessions.xml
+++ b/vector/src/main/res/layout/fragment_other_sessions.xml
@@ -13,6 +13,7 @@
         app:layout_constraintTop_toTopOf="parent">
 
         <com.google.android.material.appbar.MaterialToolbar
+            android:id="@+id/otherSessionsToolbar"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             app:navigationIcon="@drawable/ic_back_24dp"

From 39364a68b1d58d2427520d6869a1b5e705c03bc0 Mon Sep 17 00:00:00 2001
From: Onuray Sahin <onuray.sahin@gmail.com>
Date: Tue, 6 Sep 2022 17:46:56 +0300
Subject: [PATCH 033/108] Navigate to other sessions screen.

---
 vector/src/main/AndroidManifest.xml                      | 1 +
 .../settings/devices/v2/VectorSettingsDevicesFragment.kt | 9 ++++++++-
 .../settings/devices/v2/list/OtherSessionsView.kt        | 9 +++++++++
 .../devices/v2/othersessions/OtherSessionsActivity.kt    | 3 +++
 .../devices/v2/othersessions/OtherSessionsFragment.kt    | 2 +-
 .../res/layout/bottom_sheet_device_manager_filter.xml    | 3 +--
 6 files changed, 23 insertions(+), 4 deletions(-)

diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml
index 7ab9e85edc..11c42355cb 100644
--- a/vector/src/main/AndroidManifest.xml
+++ b/vector/src/main/AndroidManifest.xml
@@ -339,6 +339,7 @@
         <activity android:name=".features.call.dialpad.PstnDialActivity" />
         <activity android:name=".features.home.room.list.home.invites.InvitesActivity"/>
         <activity android:name=".features.settings.devices.v2.overview.SessionOverviewActivity"/>
+        <activity android:name=".features.settings.devices.v2.othersessions.OtherSessionsActivity" />
 
         <!-- Services -->
 
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt
index 10ebf3a42f..03e2d2fd98 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt
@@ -44,6 +44,7 @@ import im.vector.app.features.settings.devices.DevicesAction
 import im.vector.app.features.settings.devices.DevicesViewEvents
 import im.vector.app.features.settings.devices.DevicesViewModel
 import im.vector.app.features.settings.devices.v2.list.OtherSessionsController
+import im.vector.app.features.settings.devices.v2.list.OtherSessionsView
 import im.vector.app.features.settings.devices.v2.list.SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS
 import im.vector.app.features.settings.devices.v2.list.SecurityRecommendationViewState
 import im.vector.app.features.settings.devices.v2.list.SessionInfoViewState
@@ -54,7 +55,8 @@ import javax.inject.Inject
  */
 @AndroidEntryPoint
 class VectorSettingsDevicesFragment :
-        VectorBaseFragment<FragmentSettingsDevicesBinding>() {
+        VectorBaseFragment<FragmentSettingsDevicesBinding>(),
+        OtherSessionsView.Callback {
 
     @Inject lateinit var viewNavigator: VectorSettingsDevicesViewNavigator
 
@@ -126,6 +128,7 @@ class VectorSettingsDevicesFragment :
     }
 
     private fun initOtherSessionsView() {
+        views.deviceListOtherSessions.callback = this
         views.deviceListOtherSessions.setCallback(object : OtherSessionsController.Callback {
             override fun onItemClicked(deviceId: String) {
                 navigateToSessionOverview(deviceId)
@@ -260,4 +263,8 @@ class VectorSettingsDevicesFragment :
             else -> false
         }
     }
+
+    override fun onViewAllOtherSessionsClicked() {
+        viewNavigator.navigateToOtherSessions(requireActivity())
+    }
 }
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsView.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsView.kt
index 682a9c6e64..c6f8c02d22 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsView.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsView.kt
@@ -34,13 +34,22 @@ class OtherSessionsView @JvmOverloads constructor(
         defStyleAttr: Int = 0
 ) : ConstraintLayout(context, attrs, defStyleAttr) {
 
+    interface Callback {
+        fun onViewAllOtherSessionsClicked()
+    }
+
     @Inject lateinit var otherSessionsController: OtherSessionsController
 
     private val views: ViewOtherSessionsBinding
+    var callback: Callback? = null
 
     init {
         inflate(context, R.layout.view_other_sessions, this)
         views = ViewOtherSessionsBinding.bind(this)
+
+        views.otherSessionsViewAllButton.setOnClickListener {
+            callback?.onViewAllOtherSessionsClicked()
+        }
     }
 
     fun render(devices: List<DeviceFullInfo>) {
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsActivity.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsActivity.kt
index ba832c5b00..b9ab59d8f5 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsActivity.kt
@@ -19,6 +19,7 @@ package im.vector.app.features.settings.devices.v2.othersessions
 import android.content.Context
 import android.content.Intent
 import android.os.Bundle
+import android.view.View
 import dagger.hilt.android.AndroidEntryPoint
 import im.vector.app.core.extensions.addFragment
 import im.vector.app.core.platform.SimpleFragmentActivity
@@ -29,6 +30,8 @@ class OtherSessionsActivity : SimpleFragmentActivity() {
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
 
+        views.toolbar.visibility = View.GONE
+
         if (isFirstCreation()) {
             addFragment(
                     container = views.container,
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt
index d28b2a40f7..43d3005f16 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt
@@ -37,7 +37,7 @@ class OtherSessionsFragment : VectorBaseFragment<FragmentOtherSessionsBinding>()
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
-        setupToolbar(views.otherSessionsToolbar)
+        setupToolbar(views.otherSessionsToolbar).allowBack()
         initFilterView()
     }
 
diff --git a/vector/src/main/res/layout/bottom_sheet_device_manager_filter.xml b/vector/src/main/res/layout/bottom_sheet_device_manager_filter.xml
index 309ce1ec84..ca9092e70d 100644
--- a/vector/src/main/res/layout/bottom_sheet_device_manager_filter.xml
+++ b/vector/src/main/res/layout/bottom_sheet_device_manager_filter.xml
@@ -77,8 +77,7 @@
             style="@style/TextAppearance.Vector.Body.DevicesManagement"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_gravity="end"
-            android:text="@string/device_manager_filter_option_inactive_description" />
+            android:layout_gravity="end" />
 
     </RadioGroup>
 

From 392cbeca8ad2cf4e130e49a68c7466ee38a70dfd Mon Sep 17 00:00:00 2001
From: Onuray Sahin <onuray.sahin@gmail.com>
Date: Tue, 6 Sep 2022 18:13:03 +0300
Subject: [PATCH 034/108] Fix UI styles.

---
 .../devices/v2/filter/DeviceManagerFilterBottomSheet.kt   | 2 +-
 .../main/res/drawable/circle_with_transparent_border.xml  | 2 +-
 .../res/layout/bottom_sheet_device_manager_filter.xml     | 8 +++++++-
 3 files changed, 9 insertions(+), 3 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/filter/DeviceManagerFilterBottomSheet.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/filter/DeviceManagerFilterBottomSheet.kt
index 4848f24b5f..4eee482348 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/filter/DeviceManagerFilterBottomSheet.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/filter/DeviceManagerFilterBottomSheet.kt
@@ -40,7 +40,7 @@ class DeviceManagerFilterBottomSheet : VectorBaseBottomSheetDialogFragment<Botto
     }
 
     private fun initFilterRadioGroup() {
-        views.filterOptionInactiveRadioButton.text = resources.getQuantityString(
+        views.filterOptionInactiveRadioButtonDescription.text = resources.getQuantityString(
                 R.plurals.device_manager_filter_option_inactive_description,
                 SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS,
                 SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS
diff --git a/vector/src/main/res/drawable/circle_with_transparent_border.xml b/vector/src/main/res/drawable/circle_with_transparent_border.xml
index 610b8ff4e2..22b092a71e 100644
--- a/vector/src/main/res/drawable/circle_with_transparent_border.xml
+++ b/vector/src/main/res/drawable/circle_with_transparent_border.xml
@@ -9,6 +9,6 @@
 
     <stroke
         android:width="3dp"
-        android:color="?android:colorBackground" />
+        android:color="?vctr_toolbar_background" />
 
 </shape>
diff --git a/vector/src/main/res/layout/bottom_sheet_device_manager_filter.xml b/vector/src/main/res/layout/bottom_sheet_device_manager_filter.xml
index ca9092e70d..73e1971820 100644
--- a/vector/src/main/res/layout/bottom_sheet_device_manager_filter.xml
+++ b/vector/src/main/res/layout/bottom_sheet_device_manager_filter.xml
@@ -3,7 +3,8 @@
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:orientation="vertical"
-    android:paddingHorizontal="24dp">
+    android:paddingHorizontal="24dp"
+    android:paddingBottom="32dp">
 
     <View
         android:layout_width="36dp"
@@ -29,6 +30,7 @@
 
         <RadioButton
             android:id="@+id/filterOptionAllSessionsRadioButton"
+            style="@style/TextAppearance.Vector.Subtitle.Medium.DevicesManagement"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:checked="true"
@@ -37,6 +39,7 @@
 
         <RadioButton
             android:id="@+id/filterOptionVerifiedRadioButton"
+            style="@style/TextAppearance.Vector.Subtitle.Medium.DevicesManagement"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_marginTop="24dp"
@@ -52,6 +55,7 @@
 
         <RadioButton
             android:id="@+id/filterOptionUnverifiedRadioButton"
+            style="@style/TextAppearance.Vector.Subtitle.Medium.DevicesManagement"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_marginTop="16dp"
@@ -67,6 +71,7 @@
 
         <RadioButton
             android:id="@+id/filterOptionInactiveRadioButton"
+            style="@style/TextAppearance.Vector.Subtitle.Medium.DevicesManagement"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_marginTop="16dp"
@@ -74,6 +79,7 @@
             android:text="@string/device_manager_filter_option_inactive" />
 
         <TextView
+            android:id="@+id/filterOptionInactiveRadioButtonDescription"
             style="@style/TextAppearance.Vector.Body.DevicesManagement"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"

From 1a5db3cc2cc69edad905cfaf9b95ae11637689c3 Mon Sep 17 00:00:00 2001
From: Onuray Sahin <onuray.sahin@gmail.com>
Date: Tue, 6 Sep 2022 18:49:15 +0300
Subject: [PATCH 035/108] Add changelog.

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

diff --git a/changelog.d/7045.wip b/changelog.d/7045.wip
new file mode 100644
index 0000000000..8976ca9744
--- /dev/null
+++ b/changelog.d/7045.wip
@@ -0,0 +1 @@
+[Device Manager] Filter Other Sessions

From 7248692273ad8e261a1487de52fc532d7d6d809e Mon Sep 17 00:00:00 2001
From: Maxime NATUREL <maxime.naturel@niji.fr>
Date: Mon, 5 Sep 2022 11:52:35 +0200
Subject: [PATCH 036/108] Empty ViewModel V2

---
 .../app/core/di/MavericksViewModelModule.kt   |  5 ++
 .../settings/devices/v2/DeviceFullInfo.kt     | 28 ++++++++++
 .../settings/devices/v2/DevicesAction.kt      | 21 +++++++
 .../settings/devices/v2/DevicesViewEvent.kt   | 34 +++++++++++
 .../settings/devices/v2/DevicesViewModel.kt   | 56 +++++++++++++++++++
 .../settings/devices/v2/DevicesViewState.kt   | 30 ++++++++++
 6 files changed, 174 insertions(+)
 create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/DeviceFullInfo.kt
 create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesAction.kt
 create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewEvent.kt
 create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt
 create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewState.kt

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 40484f57e8..8bcfd4e422 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
@@ -352,6 +352,11 @@ interface MavericksViewModelModule {
     @MavericksViewModelKey(DevicesViewModel::class)
     fun devicesViewModelFactory(factory: DevicesViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
 
+    @Binds
+    @IntoMap
+    @MavericksViewModelKey(im.vector.app.features.settings.devices.v2.DevicesViewModel::class)
+    fun devicesViewModelV2Factory(factory: im.vector.app.features.settings.devices.v2.DevicesViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
+
     @Binds
     @IntoMap
     @MavericksViewModelKey(KeyRequestListViewModel::class)
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DeviceFullInfo.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DeviceFullInfo.kt
new file mode 100644
index 0000000000..f0a91c6183
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DeviceFullInfo.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.features.settings.devices.v2
+
+import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
+import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
+import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
+
+data class DeviceFullInfo(
+        val deviceInfo: DeviceInfo,
+        val cryptoDeviceInfo: CryptoDeviceInfo?,
+        val roomEncryptionTrustLevel: RoomEncryptionTrustLevel,
+        val isInactive: Boolean,
+)
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesAction.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesAction.kt
new file mode 100644
index 0000000000..6fb24c96b2
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesAction.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.settings.devices.v2
+
+import im.vector.app.core.platform.VectorViewModelAction
+
+sealed class DevicesAction : VectorViewModelAction
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewEvent.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewEvent.kt
new file mode 100644
index 0000000000..e83004843d
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewEvent.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.features.settings.devices.v2
+
+import im.vector.app.core.platform.VectorViewEvents
+import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
+import org.matrix.android.sdk.api.session.Session
+import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
+import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
+
+sealed class DevicesViewEvent : VectorViewEvents {
+    data class Loading(val message: CharSequence? = null) : DevicesViewEvent()
+    data class Failure(val throwable: Throwable) : DevicesViewEvent()
+    data class RequestReAuth(val registrationFlowResponse: RegistrationFlowResponse, val lastErrorCode: String?) : DevicesViewEvent()
+    data class PromptRenameDevice(val deviceInfo: DeviceInfo) : DevicesViewEvent()
+    data class ShowVerifyDevice(val userId: String, val transactionId: String?) : DevicesViewEvent()
+    data class SelfVerification(val session: Session) : DevicesViewEvent()
+    data class ShowManuallyVerify(val cryptoDeviceInfo: CryptoDeviceInfo) : DevicesViewEvent()
+    object PromptResetSecrets : DevicesViewEvent()
+}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt
new file mode 100644
index 0000000000..f496fae596
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.settings.devices.v2
+
+import com.airbnb.mvrx.MavericksViewModelFactory
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import im.vector.app.core.di.MavericksAssistedViewModelFactory
+import im.vector.app.core.di.hiltMavericksViewModelFactory
+import im.vector.app.core.platform.VectorViewModel
+import im.vector.app.core.resources.StringProvider
+import im.vector.app.features.login.ReAuthHelper
+import im.vector.app.features.settings.devices.GetCurrentSessionCrossSigningInfoUseCase
+import im.vector.app.features.settings.devices.GetEncryptionTrustLevelForDeviceUseCase
+import im.vector.app.features.settings.devices.v2.list.CheckIfSessionIsInactiveUseCase
+import org.matrix.android.sdk.api.Matrix
+import org.matrix.android.sdk.api.session.Session
+
+class DevicesViewModel @AssistedInject constructor(
+        @Assisted initialState: DevicesViewState,
+        private val session: Session,
+        private val reAuthHelper: ReAuthHelper,
+        private val stringProvider: StringProvider,
+        private val matrix: Matrix,
+        private val checkIfSessionIsInactiveUseCase: CheckIfSessionIsInactiveUseCase,
+        getCurrentSessionCrossSigningInfoUseCase: GetCurrentSessionCrossSigningInfoUseCase,
+        private val getEncryptionTrustLevelForDeviceUseCase: GetEncryptionTrustLevelForDeviceUseCase,
+) : VectorViewModel<DevicesViewState, DevicesAction, DevicesViewEvent>(initialState) {
+
+    @AssistedFactory
+    interface Factory : MavericksAssistedViewModelFactory<DevicesViewModel, DevicesViewState> {
+        override fun create(initialState: DevicesViewState): DevicesViewModel
+    }
+
+    companion object : MavericksViewModelFactory<DevicesViewModel, DevicesViewState> by hiltMavericksViewModelFactory()
+
+    override fun handle(action: DevicesAction) {
+        TODO("Not yet implemented")
+    }
+}
+
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewState.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewState.kt
new file mode 100644
index 0000000000..284520f5b2
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewState.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.features.settings.devices.v2
+
+import com.airbnb.mvrx.Async
+import com.airbnb.mvrx.MavericksState
+import com.airbnb.mvrx.Uninitialized
+
+data class DevicesViewState(
+        val myDeviceId: String = "",
+        val devices: Async<List<DeviceFullInfo>> = Uninitialized,
+        val hasAccountCrossSigning: Boolean = false,
+        val accountCrossSigningIsTrusted: Boolean = false,
+        val unverifiedSessionsCount: Int = 0,
+        val inactiveSessionsCount: Int = 0,
+) : MavericksState

From 801eef3ce770c30be94e2606155dff8f327be574 Mon Sep 17 00:00:00 2001
From: Maxime NATUREL <maxime.naturel@niji.fr>
Date: Mon, 5 Sep 2022 11:59:12 +0200
Subject: [PATCH 037/108] Declare MarkAsManuallyVerified action

---
 .../app/features/settings/devices/v2/DevicesAction.kt     | 5 ++++-
 .../app/features/settings/devices/v2/DevicesViewModel.kt  | 8 +++++++-
 2 files changed, 11 insertions(+), 2 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesAction.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesAction.kt
index 6fb24c96b2..8c7718bfcf 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesAction.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesAction.kt
@@ -17,5 +17,8 @@
 package im.vector.app.features.settings.devices.v2
 
 import im.vector.app.core.platform.VectorViewModelAction
+import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
 
-sealed class DevicesAction : VectorViewModelAction
+sealed class DevicesAction : VectorViewModelAction {
+    data class MarkAsManuallyVerified(val cryptoDeviceInfo: CryptoDeviceInfo) : DevicesAction()
+}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt
index f496fae596..00fae17cad 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt
@@ -50,7 +50,13 @@ class DevicesViewModel @AssistedInject constructor(
     companion object : MavericksViewModelFactory<DevicesViewModel, DevicesViewState> by hiltMavericksViewModelFactory()
 
     override fun handle(action: DevicesAction) {
-        TODO("Not yet implemented")
+        when(action) {
+            is DevicesAction.MarkAsManuallyVerified -> handleMarkAsManuallyVerifiedAction()
+        }
+    }
+
+    private fun handleMarkAsManuallyVerifiedAction() {
+        // TODO implement when needed
     }
 }
 

From 3a73e72b16d3c67effffd51414718c31e07f22c7 Mon Sep 17 00:00:00 2001
From: Maxime NATUREL <maxime.naturel@niji.fr>
Date: Mon, 5 Sep 2022 11:59:32 +0200
Subject: [PATCH 038/108] Inject new ViewModel in the fragment V2 + add use
 cases

---
 ...etCurrentSessionCrossSigningInfoUseCase.kt |  3 +-
 ...GetEncryptionTrustLevelForDeviceUseCase.kt |  1 +
 .../CurrentSessionCrossSigningInfo.kt         |  8 +--
 .../settings/devices/v2/DevicesViewModel.kt   | 58 +++++++++++-----
 .../settings/devices/v2/DevicesViewState.kt   |  5 +-
 ...etCurrentSessionCrossSigningInfoUseCase.kt | 49 ++++++++++++++
 .../v2/GetDeviceFullInfoListUseCase.kt        | 67 +++++++++++++++++++
 .../v2/VectorSettingsDevicesFragment.kt       | 36 ++++------
 .../v2/list/OtherSessionsController.kt        |  6 +-
 .../devices/v2/list/OtherSessionsView.kt      |  2 +-
 .../devices/v2/list/SessionInfoView.kt        |  2 +-
 .../devices/v2/list/SessionInfoViewState.kt   |  2 +-
 .../v2/overview/GetDeviceFullInfoUseCase.kt   |  4 +-
 .../v2/overview/SessionOverviewFragment.kt    |  2 +-
 .../v2/overview/SessionOverviewViewState.kt   |  2 +-
 ...rrentSessionCrossSigningInfoUseCaseTest.kt |  1 +
 ...ncryptionTrustLevelForDeviceUseCaseTest.kt |  1 +
 .../overview/GetDeviceFullInfoUseCaseTest.kt  |  2 +-
 18 files changed, 195 insertions(+), 56 deletions(-)
 rename vector/src/main/java/im/vector/app/features/settings/devices/{ => v2}/CurrentSessionCrossSigningInfo.kt (78%)
 create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/GetCurrentSessionCrossSigningInfoUseCase.kt
 create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/GetDeviceFullInfoListUseCase.kt

diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/GetCurrentSessionCrossSigningInfoUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/GetCurrentSessionCrossSigningInfoUseCase.kt
index d07bd5daae..8b58bd0536 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/GetCurrentSessionCrossSigningInfoUseCase.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/GetCurrentSessionCrossSigningInfoUseCase.kt
@@ -17,6 +17,7 @@
 package im.vector.app.features.settings.devices
 
 import im.vector.app.core.di.ActiveSessionHolder
+import im.vector.app.features.settings.devices.v2.CurrentSessionCrossSigningInfo
 import javax.inject.Inject
 
 class GetCurrentSessionCrossSigningInfoUseCase @Inject constructor(
@@ -28,7 +29,7 @@ class GetCurrentSessionCrossSigningInfoUseCase @Inject constructor(
         val isCrossSigningInitialized = session.cryptoService().crossSigningService().isCrossSigningInitialized()
         val isCrossSigningVerified = session.cryptoService().crossSigningService().isCrossSigningVerified()
         return CurrentSessionCrossSigningInfo(
-                deviceId = session.sessionParams.deviceId,
+                deviceId = session.sessionParams.deviceId.orEmpty(),
                 isCrossSigningInitialized = isCrossSigningInitialized,
                 isCrossSigningVerified = isCrossSigningVerified
         )
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForDeviceUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForDeviceUseCase.kt
index e5ef4b446b..433c4da233 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForDeviceUseCase.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForDeviceUseCase.kt
@@ -16,6 +16,7 @@
 
 package im.vector.app.features.settings.devices
 
+import im.vector.app.features.settings.devices.v2.CurrentSessionCrossSigningInfo
 import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
 import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
 import javax.inject.Inject
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/CurrentSessionCrossSigningInfo.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/CurrentSessionCrossSigningInfo.kt
similarity index 78%
rename from vector/src/main/java/im/vector/app/features/settings/devices/CurrentSessionCrossSigningInfo.kt
rename to vector/src/main/java/im/vector/app/features/settings/devices/v2/CurrentSessionCrossSigningInfo.kt
index 790de08823..cccdb23d52 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/CurrentSessionCrossSigningInfo.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/CurrentSessionCrossSigningInfo.kt
@@ -14,13 +14,13 @@
  * limitations under the License.
  */
 
-package im.vector.app.features.settings.devices
+package im.vector.app.features.settings.devices.v2
 
 /**
  * Used to hold some info about the cross signing of the current Session.
  */
 data class CurrentSessionCrossSigningInfo(
-        val deviceId: String?,
-        val isCrossSigningInitialized: Boolean,
-        val isCrossSigningVerified: Boolean,
+        val deviceId: String = "",
+        val isCrossSigningInitialized: Boolean = false,
+        val isCrossSigningVerified: Boolean = false,
 )
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt
index 00fae17cad..09de9ca8e7 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt
@@ -17,29 +17,22 @@
 package im.vector.app.features.settings.devices.v2
 
 import com.airbnb.mvrx.MavericksViewModelFactory
+import com.airbnb.mvrx.Success
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 import im.vector.app.core.di.MavericksAssistedViewModelFactory
 import im.vector.app.core.di.hiltMavericksViewModelFactory
 import im.vector.app.core.platform.VectorViewModel
-import im.vector.app.core.resources.StringProvider
-import im.vector.app.features.login.ReAuthHelper
-import im.vector.app.features.settings.devices.GetCurrentSessionCrossSigningInfoUseCase
-import im.vector.app.features.settings.devices.GetEncryptionTrustLevelForDeviceUseCase
-import im.vector.app.features.settings.devices.v2.list.CheckIfSessionIsInactiveUseCase
-import org.matrix.android.sdk.api.Matrix
-import org.matrix.android.sdk.api.session.Session
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import org.matrix.android.sdk.api.extensions.orFalse
 
+// TODO add unit tests
 class DevicesViewModel @AssistedInject constructor(
         @Assisted initialState: DevicesViewState,
-        private val session: Session,
-        private val reAuthHelper: ReAuthHelper,
-        private val stringProvider: StringProvider,
-        private val matrix: Matrix,
-        private val checkIfSessionIsInactiveUseCase: CheckIfSessionIsInactiveUseCase,
-        getCurrentSessionCrossSigningInfoUseCase: GetCurrentSessionCrossSigningInfoUseCase,
-        private val getEncryptionTrustLevelForDeviceUseCase: GetEncryptionTrustLevelForDeviceUseCase,
+        private val getCurrentSessionCrossSigningInfoUseCase: GetCurrentSessionCrossSigningInfoUseCase,
+        private val getDeviceFullInfoListUseCase: GetDeviceFullInfoListUseCase,
 ) : VectorViewModel<DevicesViewState, DevicesAction, DevicesViewEvent>(initialState) {
 
     @AssistedFactory
@@ -49,8 +42,43 @@ class DevicesViewModel @AssistedInject constructor(
 
     companion object : MavericksViewModelFactory<DevicesViewModel, DevicesViewState> by hiltMavericksViewModelFactory()
 
+    init {
+        observeCurrentSessionCrossSigningInfo()
+        observeDevices()
+    }
+
+    private fun observeCurrentSessionCrossSigningInfo() {
+        getCurrentSessionCrossSigningInfoUseCase.execute()
+                .onEach { crossSigningInfo ->
+                    setState {
+                        copy(currentSessionCrossSigningInfo = crossSigningInfo)
+                    }
+                }
+                .launchIn(viewModelScope)
+    }
+
+    private fun observeDevices() {
+        getDeviceFullInfoListUseCase.execute()
+                .execute { async ->
+                    if (async is Success) {
+                        val deviceFullInfoList = async.invoke()
+                        val unverifiedSessionsCount = deviceFullInfoList.count { !it.cryptoDeviceInfo?.trustLevel?.isVerified().orFalse() }
+                        val inactiveSessionsCount = deviceFullInfoList.count { it.isInactive }
+                        copy(
+                                devices = async,
+                                unverifiedSessionsCount = unverifiedSessionsCount,
+                                inactiveSessionsCount = inactiveSessionsCount,
+                        )
+                    } else {
+                        copy(
+                                devices = async
+                        )
+                    }
+                }
+    }
+
     override fun handle(action: DevicesAction) {
-        when(action) {
+        when (action) {
             is DevicesAction.MarkAsManuallyVerified -> handleMarkAsManuallyVerifiedAction()
         }
     }
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewState.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewState.kt
index 284520f5b2..3fc061daa4 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewState.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewState.kt
@@ -21,10 +21,9 @@ import com.airbnb.mvrx.MavericksState
 import com.airbnb.mvrx.Uninitialized
 
 data class DevicesViewState(
-        val myDeviceId: String = "",
+        val currentSessionCrossSigningInfo: CurrentSessionCrossSigningInfo = CurrentSessionCrossSigningInfo(),
         val devices: Async<List<DeviceFullInfo>> = Uninitialized,
-        val hasAccountCrossSigning: Boolean = false,
-        val accountCrossSigningIsTrusted: Boolean = false,
         val unverifiedSessionsCount: Int = 0,
         val inactiveSessionsCount: Int = 0,
+        val isLoading: Boolean = false,
 ) : MavericksState
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/GetCurrentSessionCrossSigningInfoUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/GetCurrentSessionCrossSigningInfoUseCase.kt
new file mode 100644
index 0000000000..9f7a3d8208
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/GetCurrentSessionCrossSigningInfoUseCase.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.settings.devices.v2
+
+import im.vector.app.core.di.ActiveSessionHolder
+import im.vector.app.features.settings.devices.v2.CurrentSessionCrossSigningInfo
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.map
+import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
+import org.matrix.android.sdk.api.util.Optional
+import org.matrix.android.sdk.flow.flow
+import javax.inject.Inject
+
+// TODO add unit tests
+class GetCurrentSessionCrossSigningInfoUseCase @Inject constructor(
+        private val activeSessionHolder: ActiveSessionHolder,
+) {
+
+    fun execute(): Flow<CurrentSessionCrossSigningInfo> {
+        return activeSessionHolder.getSafeActiveSession()
+                ?.let { session ->
+                    session.flow().liveCrossSigningInfo(session.myUserId)
+                            .map { convertToSigningInfo(session.sessionParams.deviceId.orEmpty(), it) }
+                } ?: emptyFlow()
+    }
+
+    private fun convertToSigningInfo(deviceId: String, mxCrossSigningInfo: Optional<MXCrossSigningInfo>): CurrentSessionCrossSigningInfo {
+        return CurrentSessionCrossSigningInfo(
+                deviceId = deviceId,
+                isCrossSigningInitialized = mxCrossSigningInfo.getOrNull() != null,
+                isCrossSigningVerified = mxCrossSigningInfo.getOrNull()?.isTrusted() == true
+        )
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/GetDeviceFullInfoListUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/GetDeviceFullInfoListUseCase.kt
new file mode 100644
index 0000000000..fbe6609ef4
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/GetDeviceFullInfoListUseCase.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.settings.devices.v2
+
+import im.vector.app.core.di.ActiveSessionHolder
+import im.vector.app.features.settings.devices.GetEncryptionTrustLevelForDeviceUseCase
+import im.vector.app.features.settings.devices.v2.list.CheckIfSessionIsInactiveUseCase
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.emptyFlow
+import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
+import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
+import org.matrix.android.sdk.flow.flow
+import javax.inject.Inject
+
+// TODO add unit tests
+class GetDeviceFullInfoListUseCase @Inject constructor(
+        private val activeSessionHolder: ActiveSessionHolder,
+        private val checkIfSessionIsInactiveUseCase: CheckIfSessionIsInactiveUseCase,
+        private val getEncryptionTrustLevelForDeviceUseCase: GetEncryptionTrustLevelForDeviceUseCase,
+        private val getCurrentSessionCrossSigningInfoUseCase: GetCurrentSessionCrossSigningInfoUseCase,
+) {
+
+    fun execute(): Flow<List<DeviceFullInfo>> {
+        return activeSessionHolder.getSafeActiveSession()?.let { session ->
+            val deviceFullInfoFlow = combine(
+                    getCurrentSessionCrossSigningInfoUseCase.execute(),
+                    session.flow().liveUserCryptoDevices(session.myUserId),
+                    session.flow().liveMyDevicesInfo()
+            ) { currentSessionCrossSigningInfo, cryptoList, infoList ->
+                convertToDeviceFullInfoList(currentSessionCrossSigningInfo, cryptoList, infoList)
+            }
+
+            deviceFullInfoFlow.distinctUntilChanged()
+        } ?: emptyFlow()
+    }
+
+    private fun convertToDeviceFullInfoList(
+            currentSessionCrossSigningInfo: CurrentSessionCrossSigningInfo,
+            cryptoList: List<CryptoDeviceInfo>,
+            infoList: List<DeviceInfo>,
+    ): List<DeviceFullInfo> {
+        return infoList
+                .sortedByDescending { it.lastSeenTs }
+                .map { deviceInfo ->
+                    val cryptoDeviceInfo = cryptoList.firstOrNull { it.deviceId == deviceInfo.deviceId }
+                    val trustLevelForShield = getEncryptionTrustLevelForDeviceUseCase.execute(currentSessionCrossSigningInfo, cryptoDeviceInfo)
+                    val isInactive = checkIfSessionIsInactiveUseCase.execute(deviceInfo.lastSeenTs ?: 0)
+                    DeviceFullInfo(deviceInfo, cryptoDeviceInfo, trustLevelForShield, isInactive)
+                }
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt
index 10ebf3a42f..90c31351ad 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt
@@ -24,8 +24,6 @@ import android.view.ViewGroup
 import android.widget.Toast
 import androidx.appcompat.app.AppCompatActivity
 import androidx.core.view.isVisible
-import com.airbnb.mvrx.Async
-import com.airbnb.mvrx.Loading
 import com.airbnb.mvrx.Success
 import com.airbnb.mvrx.fragmentViewModel
 import com.airbnb.mvrx.withState
@@ -39,10 +37,6 @@ import im.vector.app.core.resources.DrawableProvider
 import im.vector.app.databinding.FragmentSettingsDevicesBinding
 import im.vector.app.features.crypto.recover.SetupMode
 import im.vector.app.features.crypto.verification.VerificationBottomSheet
-import im.vector.app.features.settings.devices.DeviceFullInfo
-import im.vector.app.features.settings.devices.DevicesAction
-import im.vector.app.features.settings.devices.DevicesViewEvents
-import im.vector.app.features.settings.devices.DevicesViewModel
 import im.vector.app.features.settings.devices.v2.list.OtherSessionsController
 import im.vector.app.features.settings.devices.v2.list.SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS
 import im.vector.app.features.settings.devices.v2.list.SecurityRecommendationViewState
@@ -93,27 +87,27 @@ class VectorSettingsDevicesFragment :
     private fun observeViewEvents() {
         viewModel.observeViewEvents {
             when (it) {
-                is DevicesViewEvents.Loading -> showLoading(it.message)
-                is DevicesViewEvents.Failure -> showFailure(it.throwable)
-                is DevicesViewEvents.RequestReAuth -> Unit // TODO. Next PR
-                is DevicesViewEvents.PromptRenameDevice -> Unit // TODO. Next PR
-                is DevicesViewEvents.ShowVerifyDevice -> {
+                is DevicesViewEvent.Loading -> showLoading(it.message)
+                is DevicesViewEvent.Failure -> showFailure(it.throwable)
+                is DevicesViewEvent.RequestReAuth -> Unit // TODO. Next PR
+                is DevicesViewEvent.PromptRenameDevice -> Unit // TODO. Next PR
+                is DevicesViewEvent.ShowVerifyDevice -> {
                     VerificationBottomSheet.withArgs(
                             roomId = null,
                             otherUserId = it.userId,
                             transactionId = it.transactionId
                     ).show(childFragmentManager, "REQPOP")
                 }
-                is DevicesViewEvents.SelfVerification -> {
+                is DevicesViewEvent.SelfVerification -> {
                     VerificationBottomSheet.forSelfVerification(it.session)
                             .show(childFragmentManager, "REQPOP")
                 }
-                is DevicesViewEvents.ShowManuallyVerify -> {
+                is DevicesViewEvent.ShowManuallyVerify -> {
                     ManuallyVerifyDialog.show(requireActivity(), it.cryptoDeviceInfo) {
                         viewModel.handle(DevicesAction.MarkAsManuallyVerified(it.cryptoDeviceInfo))
                     }
                 }
-                is DevicesViewEvents.PromptResetSecrets -> {
+                is DevicesViewEvent.PromptResetSecrets -> {
                     navigator.open4SSetup(requireContext(), SetupMode.PASSPHRASE_AND_NEEDED_SECRETS_RESET)
                 }
             }
@@ -151,10 +145,11 @@ class VectorSettingsDevicesFragment :
     override fun invalidate() = withState(viewModel) { state ->
         if (state.devices is Success) {
             val devices = state.devices()
+            val currentDeviceId = state.currentSessionCrossSigningInfo.deviceId
             val currentDeviceInfo = devices?.firstOrNull {
-                it.deviceInfo.deviceId == state.myDeviceId
+                it.deviceInfo.deviceId == currentDeviceId
             }
-            val otherDevices = devices?.filter { it.deviceInfo.deviceId != state.myDeviceId }
+            val otherDevices = devices?.filter { it.deviceInfo.deviceId != currentDeviceId }
 
             renderSecurityRecommendations(state.inactiveSessionsCount, state.unverifiedSessionsCount)
             renderCurrentDevice(currentDeviceInfo)
@@ -165,7 +160,7 @@ class VectorSettingsDevicesFragment :
             hideOtherSessionsView()
         }
 
-        handleRequestStatus(state.request)
+        handleLoadingStatus(state.isLoading)
     }
 
     private fun renderSecurityRecommendations(inactiveSessionsCount: Int, unverifiedSessionsCount: Int) {
@@ -254,10 +249,7 @@ class VectorSettingsDevicesFragment :
         }
     }
 
-    private fun handleRequestStatus(unIgnoreRequest: Async<Unit>) {
-        views.waitingView.root.isVisible = when (unIgnoreRequest) {
-            is Loading -> true
-            else -> false
-        }
+    private fun handleLoadingStatus(isLoading: Boolean) {
+        views.waitingView.root.isVisible = isLoading
     }
 }
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt
index 6419d02fc9..468b19c45a 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt
@@ -24,7 +24,7 @@ import im.vector.app.core.epoxy.noResultItem
 import im.vector.app.core.resources.ColorProvider
 import im.vector.app.core.resources.DrawableProvider
 import im.vector.app.core.resources.StringProvider
-import im.vector.app.features.settings.devices.DeviceFullInfo
+import im.vector.app.features.settings.devices.v2.DeviceFullInfo
 import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
 import javax.inject.Inject
 
@@ -60,7 +60,7 @@ class OtherSessionsController @Inject constructor(
                             SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS,
                             formattedLastActivityDate
                     )
-                } else if (device.trustLevelForShield == RoomEncryptionTrustLevel.Trusted) {
+                } else if (device.roomEncryptionTrustLevel == RoomEncryptionTrustLevel.Trusted) {
                     stringProvider.getString(R.string.device_manager_other_sessions_description_verified, formattedLastActivityDate)
                 } else {
                     stringProvider.getString(R.string.device_manager_other_sessions_description_unverified, formattedLastActivityDate)
@@ -71,7 +71,7 @@ class OtherSessionsController @Inject constructor(
                 otherSessionItem {
                     id(device.deviceInfo.deviceId)
                     deviceType(DeviceType.UNKNOWN) // TODO. We don't have this info yet. Update accordingly.
-                    roomEncryptionTrustLevel(device.trustLevelForShield)
+                    roomEncryptionTrustLevel(device.roomEncryptionTrustLevel)
                     sessionName(device.deviceInfo.displayName)
                     sessionDescription(description)
                     sessionDescriptionDrawable(descriptionDrawable)
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsView.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsView.kt
index 682a9c6e64..b552664fe9 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsView.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsView.kt
@@ -24,7 +24,7 @@ import im.vector.app.R
 import im.vector.app.core.extensions.cleanup
 import im.vector.app.core.extensions.configureWith
 import im.vector.app.databinding.ViewOtherSessionsBinding
-import im.vector.app.features.settings.devices.DeviceFullInfo
+import im.vector.app.features.settings.devices.v2.DeviceFullInfo
 import javax.inject.Inject
 
 @AndroidEntryPoint
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoView.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoView.kt
index 767f09482b..0cb621a502 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoView.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoView.kt
@@ -57,7 +57,7 @@ class SessionInfoView @JvmOverloads constructor(
     ) {
         renderDeviceInfo(sessionInfoViewState.deviceFullInfo.deviceInfo.displayName.orEmpty())
         renderVerificationStatus(
-                sessionInfoViewState.deviceFullInfo.trustLevelForShield,
+                sessionInfoViewState.deviceFullInfo.roomEncryptionTrustLevel,
                 sessionInfoViewState.isCurrentSession,
                 sessionInfoViewState.isLearnMoreLinkVisible,
         )
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoViewState.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoViewState.kt
index 22ad710676..60e1234820 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoViewState.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionInfoViewState.kt
@@ -16,7 +16,7 @@
 
 package im.vector.app.features.settings.devices.v2.list
 
-import im.vector.app.features.settings.devices.DeviceFullInfo
+import im.vector.app.features.settings.devices.v2.DeviceFullInfo
 
 data class SessionInfoViewState(
         val isCurrentSession: Boolean,
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt
index c3579b68c3..a8a97ab326 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt
@@ -18,7 +18,7 @@ package im.vector.app.features.settings.devices.v2.overview
 
 import androidx.lifecycle.asFlow
 import im.vector.app.core.di.ActiveSessionHolder
-import im.vector.app.features.settings.devices.DeviceFullInfo
+import im.vector.app.features.settings.devices.v2.DeviceFullInfo
 import im.vector.app.features.settings.devices.GetCurrentSessionCrossSigningInfoUseCase
 import im.vector.app.features.settings.devices.GetEncryptionTrustLevelForDeviceUseCase
 import im.vector.app.features.settings.devices.v2.list.CheckIfSessionIsInactiveUseCase
@@ -51,7 +51,7 @@ class GetDeviceFullInfoUseCase @Inject constructor(
                     DeviceFullInfo(
                             deviceInfo = info,
                             cryptoDeviceInfo = cryptoInfo,
-                            trustLevelForShield = roomEncryptionTrustLevel,
+                            roomEncryptionTrustLevel = roomEncryptionTrustLevel,
                             isInactive = isInactive
                     )
                 } else {
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt
index a6bac6087b..c5cd80bd3c 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt
@@ -34,7 +34,7 @@ import im.vector.app.core.platform.VectorBaseFragment
 import im.vector.app.core.resources.ColorProvider
 import im.vector.app.core.resources.DrawableProvider
 import im.vector.app.databinding.FragmentSessionOverviewBinding
-import im.vector.app.features.settings.devices.DeviceFullInfo
+import im.vector.app.features.settings.devices.v2.DeviceFullInfo
 import im.vector.app.features.settings.devices.v2.list.SessionInfoViewState
 import javax.inject.Inject
 
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewState.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewState.kt
index c9f5635cbd..a447336c23 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewState.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewState.kt
@@ -19,7 +19,7 @@ package im.vector.app.features.settings.devices.v2.overview
 import com.airbnb.mvrx.Async
 import com.airbnb.mvrx.MavericksState
 import com.airbnb.mvrx.Uninitialized
-import im.vector.app.features.settings.devices.DeviceFullInfo
+import im.vector.app.features.settings.devices.v2.DeviceFullInfo
 
 data class SessionOverviewViewState(
         val deviceId: String,
diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/GetCurrentSessionCrossSigningInfoUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/GetCurrentSessionCrossSigningInfoUseCaseTest.kt
index 7c8ee008eb..1a805f4c3e 100644
--- a/vector/src/test/java/im/vector/app/features/settings/devices/GetCurrentSessionCrossSigningInfoUseCaseTest.kt
+++ b/vector/src/test/java/im/vector/app/features/settings/devices/GetCurrentSessionCrossSigningInfoUseCaseTest.kt
@@ -16,6 +16,7 @@
 
 package im.vector.app.features.settings.devices
 
+import im.vector.app.features.settings.devices.v2.CurrentSessionCrossSigningInfo
 import im.vector.app.test.fakes.FakeActiveSessionHolder
 import io.mockk.every
 import io.mockk.mockk
diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForDeviceUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForDeviceUseCaseTest.kt
index 8d54b31ab4..e55f0969f7 100644
--- a/vector/src/test/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForDeviceUseCaseTest.kt
+++ b/vector/src/test/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForDeviceUseCaseTest.kt
@@ -16,6 +16,7 @@
 
 package im.vector.app.features.settings.devices
 
+import im.vector.app.features.settings.devices.v2.CurrentSessionCrossSigningInfo
 import io.mockk.every
 import io.mockk.mockk
 import io.mockk.verify
diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCaseTest.kt
index e3d62961a7..70af681c6f 100644
--- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCaseTest.kt
+++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCaseTest.kt
@@ -18,7 +18,7 @@ package im.vector.app.features.settings.devices.v2.overview
 
 import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.asFlow
-import im.vector.app.features.settings.devices.CurrentSessionCrossSigningInfo
+import im.vector.app.features.settings.devices.v2.CurrentSessionCrossSigningInfo
 import im.vector.app.features.settings.devices.DeviceFullInfo
 import im.vector.app.features.settings.devices.GetCurrentSessionCrossSigningInfoUseCase
 import im.vector.app.features.settings.devices.GetEncryptionTrustLevelForDeviceUseCase

From fa1ef0695228c8813991451c4b2704ddc5d0013c Mon Sep 17 00:00:00 2001
From: Maxime NATUREL <maxime.naturel@niji.fr>
Date: Mon, 5 Sep 2022 16:01:22 +0200
Subject: [PATCH 039/108] Moving recently created use cases inside v2 package

---
 .../app/features/settings/devices/DevicesViewModel.kt    | 1 +
 .../settings/devices/v2/GetDeviceFullInfoListUseCase.kt  | 1 -
 .../GetEncryptionTrustLevelForCurrentDeviceUseCase.kt    | 2 +-
 .../{ => v2}/GetEncryptionTrustLevelForDeviceUseCase.kt  | 2 +-
 .../GetEncryptionTrustLevelForOtherDeviceUseCase.kt      | 2 +-
 .../devices/v2/overview/GetDeviceFullInfoUseCase.kt      | 9 +++++----
 ...GetEncryptionTrustLevelForCurrentDeviceUseCaseTest.kt | 2 +-
 .../GetEncryptionTrustLevelForDeviceUseCaseTest.kt       | 5 ++---
 .../GetEncryptionTrustLevelForOtherDeviceUseCaseTest.kt  | 2 +-
 9 files changed, 13 insertions(+), 13 deletions(-)
 rename vector/src/main/java/im/vector/app/features/settings/devices/{ => v2}/GetEncryptionTrustLevelForCurrentDeviceUseCase.kt (96%)
 rename vector/src/main/java/im/vector/app/features/settings/devices/{ => v2}/GetEncryptionTrustLevelForDeviceUseCase.kt (97%)
 rename vector/src/main/java/im/vector/app/features/settings/devices/{ => v2}/GetEncryptionTrustLevelForOtherDeviceUseCase.kt (97%)
 rename vector/src/test/java/im/vector/app/features/settings/devices/{ => v2}/GetEncryptionTrustLevelForCurrentDeviceUseCaseTest.kt (97%)
 rename vector/src/test/java/im/vector/app/features/settings/devices/{ => v2}/GetEncryptionTrustLevelForDeviceUseCaseTest.kt (96%)
 rename vector/src/test/java/im/vector/app/features/settings/devices/{ => v2}/GetEncryptionTrustLevelForOtherDeviceUseCaseTest.kt (98%)

diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewModel.kt
index 82c346b09c..30e7727860 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/DevicesViewModel.kt
@@ -34,6 +34,7 @@ import im.vector.app.core.resources.StringProvider
 import im.vector.app.core.utils.PublishDataSource
 import im.vector.app.features.auth.ReAuthActivity
 import im.vector.app.features.login.ReAuthHelper
+import im.vector.app.features.settings.devices.v2.GetEncryptionTrustLevelForDeviceUseCase
 import im.vector.app.features.settings.devices.v2.list.CheckIfSessionIsInactiveUseCase
 import im.vector.lib.core.utils.flow.throttleFirst
 import kotlinx.coroutines.Dispatchers
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/GetDeviceFullInfoListUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/GetDeviceFullInfoListUseCase.kt
index fbe6609ef4..948d23de39 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/GetDeviceFullInfoListUseCase.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/GetDeviceFullInfoListUseCase.kt
@@ -17,7 +17,6 @@
 package im.vector.app.features.settings.devices.v2
 
 import im.vector.app.core.di.ActiveSessionHolder
-import im.vector.app.features.settings.devices.GetEncryptionTrustLevelForDeviceUseCase
 import im.vector.app.features.settings.devices.v2.list.CheckIfSessionIsInactiveUseCase
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForCurrentDeviceUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/GetEncryptionTrustLevelForCurrentDeviceUseCase.kt
similarity index 96%
rename from vector/src/main/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForCurrentDeviceUseCase.kt
rename to vector/src/main/java/im/vector/app/features/settings/devices/v2/GetEncryptionTrustLevelForCurrentDeviceUseCase.kt
index 0d30aba318..7e56d35570 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForCurrentDeviceUseCase.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/GetEncryptionTrustLevelForCurrentDeviceUseCase.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package im.vector.app.features.settings.devices
+package im.vector.app.features.settings.devices.v2
 
 import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
 import javax.inject.Inject
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForDeviceUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/GetEncryptionTrustLevelForDeviceUseCase.kt
similarity index 97%
rename from vector/src/main/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForDeviceUseCase.kt
rename to vector/src/main/java/im/vector/app/features/settings/devices/v2/GetEncryptionTrustLevelForDeviceUseCase.kt
index 433c4da233..7f330b71d5 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForDeviceUseCase.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/GetEncryptionTrustLevelForDeviceUseCase.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package im.vector.app.features.settings.devices
+package im.vector.app.features.settings.devices.v2
 
 import im.vector.app.features.settings.devices.v2.CurrentSessionCrossSigningInfo
 import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForOtherDeviceUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/GetEncryptionTrustLevelForOtherDeviceUseCase.kt
similarity index 97%
rename from vector/src/main/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForOtherDeviceUseCase.kt
rename to vector/src/main/java/im/vector/app/features/settings/devices/v2/GetEncryptionTrustLevelForOtherDeviceUseCase.kt
index 11bc3a8ede..7541b9b1d5 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForOtherDeviceUseCase.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/GetEncryptionTrustLevelForOtherDeviceUseCase.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package im.vector.app.features.settings.devices
+package im.vector.app.features.settings.devices.v2
 
 import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
 import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt
index a8a97ab326..60c6266901 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt
@@ -19,8 +19,8 @@ package im.vector.app.features.settings.devices.v2.overview
 import androidx.lifecycle.asFlow
 import im.vector.app.core.di.ActiveSessionHolder
 import im.vector.app.features.settings.devices.v2.DeviceFullInfo
-import im.vector.app.features.settings.devices.GetCurrentSessionCrossSigningInfoUseCase
-import im.vector.app.features.settings.devices.GetEncryptionTrustLevelForDeviceUseCase
+import im.vector.app.features.settings.devices.v2.GetCurrentSessionCrossSigningInfoUseCase
+import im.vector.app.features.settings.devices.v2.GetEncryptionTrustLevelForDeviceUseCase
 import im.vector.app.features.settings.devices.v2.list.CheckIfSessionIsInactiveUseCase
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
@@ -29,6 +29,7 @@ import org.matrix.android.sdk.api.util.Optional
 import org.matrix.android.sdk.api.util.toOptional
 import javax.inject.Inject
 
+// TODO update unit tests
 class GetDeviceFullInfoUseCase @Inject constructor(
         private val activeSessionHolder: ActiveSessionHolder,
         private val getCurrentSessionCrossSigningInfoUseCase: GetCurrentSessionCrossSigningInfoUseCase,
@@ -38,11 +39,11 @@ class GetDeviceFullInfoUseCase @Inject constructor(
 
     fun execute(deviceId: String): Flow<Optional<DeviceFullInfo>> {
         return activeSessionHolder.getSafeActiveSession()?.let { session ->
-            val currentSessionCrossSigningInfo = getCurrentSessionCrossSigningInfoUseCase.execute()
             combine(
+                    getCurrentSessionCrossSigningInfoUseCase.execute(),
                     session.cryptoService().getMyDevicesInfoLive(deviceId).asFlow(),
                     session.cryptoService().getLiveCryptoDeviceInfoWithId(deviceId).asFlow()
-            ) { deviceInfo, cryptoDeviceInfo ->
+            ) { currentSessionCrossSigningInfo, deviceInfo, cryptoDeviceInfo ->
                 val info = deviceInfo.getOrNull()
                 val cryptoInfo = cryptoDeviceInfo.getOrNull()
                 val fullInfo = if (info != null && cryptoInfo != null) {
diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForCurrentDeviceUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/GetEncryptionTrustLevelForCurrentDeviceUseCaseTest.kt
similarity index 97%
rename from vector/src/test/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForCurrentDeviceUseCaseTest.kt
rename to vector/src/test/java/im/vector/app/features/settings/devices/v2/GetEncryptionTrustLevelForCurrentDeviceUseCaseTest.kt
index 830eab5dcb..b2ce89df33 100644
--- a/vector/src/test/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForCurrentDeviceUseCaseTest.kt
+++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/GetEncryptionTrustLevelForCurrentDeviceUseCaseTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package im.vector.app.features.settings.devices
+package im.vector.app.features.settings.devices.v2
 
 import org.amshove.kluent.shouldBeEqualTo
 import org.junit.Test
diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForDeviceUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/GetEncryptionTrustLevelForDeviceUseCaseTest.kt
similarity index 96%
rename from vector/src/test/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForDeviceUseCaseTest.kt
rename to vector/src/test/java/im/vector/app/features/settings/devices/v2/GetEncryptionTrustLevelForDeviceUseCaseTest.kt
index e55f0969f7..e43fd49ffc 100644
--- a/vector/src/test/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForDeviceUseCaseTest.kt
+++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/GetEncryptionTrustLevelForDeviceUseCaseTest.kt
@@ -14,9 +14,8 @@
  * limitations under the License.
  */
 
-package im.vector.app.features.settings.devices
+package im.vector.app.features.settings.devices.v2
 
-import im.vector.app.features.settings.devices.v2.CurrentSessionCrossSigningInfo
 import io.mockk.every
 import io.mockk.mockk
 import io.mockk.verify
@@ -91,7 +90,7 @@ class GetEncryptionTrustLevelForDeviceUseCaseTest {
     }
 
     private fun givenCurrentSessionCrossSigningInfo(
-            deviceId: String?,
+            deviceId: String,
             isCrossSigningInitialized: Boolean,
             isCrossSigningVerified: Boolean
     ): CurrentSessionCrossSigningInfo {
diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForOtherDeviceUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/GetEncryptionTrustLevelForOtherDeviceUseCaseTest.kt
similarity index 98%
rename from vector/src/test/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForOtherDeviceUseCaseTest.kt
rename to vector/src/test/java/im/vector/app/features/settings/devices/v2/GetEncryptionTrustLevelForOtherDeviceUseCaseTest.kt
index 9dc87c2a16..2aeffbbb0d 100644
--- a/vector/src/test/java/im/vector/app/features/settings/devices/GetEncryptionTrustLevelForOtherDeviceUseCaseTest.kt
+++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/GetEncryptionTrustLevelForOtherDeviceUseCaseTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package im.vector.app.features.settings.devices
+package im.vector.app.features.settings.devices.v2
 
 import org.amshove.kluent.shouldBeEqualTo
 import org.junit.Test

From 69cb5738a46d8363eb521ba6156a9f9a42a5b633 Mon Sep 17 00:00:00 2001
From: Maxime NATUREL <maxime.naturel@niji.fr>
Date: Mon, 5 Sep 2022 16:44:10 +0200
Subject: [PATCH 040/108] Listen verification + refresh devices use cases

---
 .../settings/devices/v2/DevicesViewModel.kt   | 67 ++++++++++++++++++-
 ...reshDevicesOnCryptoDevicesChangeUseCase.kt | 52 ++++++++++++++
 .../devices/v2/RefreshDevicesUseCase.kt       | 33 +++++++++
 3 files changed, 151 insertions(+), 1 deletion(-)
 create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/RefreshDevicesOnCryptoDevicesChangeUseCase.kt
 create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/RefreshDevicesUseCase.kt

diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt
index 09de9ca8e7..20e3baa61d 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt
@@ -21,19 +21,30 @@ import com.airbnb.mvrx.Success
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
+import im.vector.app.core.di.ActiveSessionHolder
 import im.vector.app.core.di.MavericksAssistedViewModelFactory
 import im.vector.app.core.di.hiltMavericksViewModelFactory
 import im.vector.app.core.platform.VectorViewModel
+import im.vector.app.core.utils.PublishDataSource
+import im.vector.lib.core.utils.flow.throttleFirst
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
 import org.matrix.android.sdk.api.extensions.orFalse
+import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
+import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction
+import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
+import kotlin.time.Duration.Companion.seconds
 
 // TODO add unit tests
 class DevicesViewModel @AssistedInject constructor(
         @Assisted initialState: DevicesViewState,
+        private val activeSessionHolder: ActiveSessionHolder,
         private val getCurrentSessionCrossSigningInfoUseCase: GetCurrentSessionCrossSigningInfoUseCase,
         private val getDeviceFullInfoListUseCase: GetDeviceFullInfoListUseCase,
-) : VectorViewModel<DevicesViewState, DevicesAction, DevicesViewEvent>(initialState) {
+        private val refreshDevicesUseCase: RefreshDevicesUseCase,
+        private val refreshDevicesOnCryptoDevicesChangeUseCase: RefreshDevicesOnCryptoDevicesChangeUseCase,
+) : VectorViewModel<DevicesViewState, DevicesAction, DevicesViewEvent>(initialState), VerificationService.Listener {
 
     @AssistedFactory
     interface Factory : MavericksAssistedViewModelFactory<DevicesViewModel, DevicesViewState> {
@@ -42,9 +53,35 @@ class DevicesViewModel @AssistedInject constructor(
 
     companion object : MavericksViewModelFactory<DevicesViewModel, DevicesViewState> by hiltMavericksViewModelFactory()
 
+    private val refreshSource = PublishDataSource<Unit>()
+    private val refreshThrottleDelayMs = 4.seconds.inWholeMilliseconds
+
     init {
+        addVerificationListener()
         observeCurrentSessionCrossSigningInfo()
         observeDevices()
+        observeRefreshSource()
+        refreshDevicesOnCryptoDevicesChange()
+        queryRefreshDevicesList()
+    }
+
+    override fun onCleared() {
+        removeVerificationListener()
+        super.onCleared()
+    }
+
+    private fun addVerificationListener() {
+        activeSessionHolder.getSafeActiveSession()
+                ?.cryptoService()
+                ?.verificationService()
+                ?.addListener(this)
+    }
+
+    private fun removeVerificationListener() {
+        activeSessionHolder.getSafeActiveSession()
+                ?.cryptoService()
+                ?.verificationService()
+                ?.removeListener(this)
     }
 
     private fun observeCurrentSessionCrossSigningInfo() {
@@ -77,6 +114,34 @@ class DevicesViewModel @AssistedInject constructor(
                 }
     }
 
+    private fun refreshDevicesOnCryptoDevicesChange() {
+        viewModelScope.launch {
+            refreshDevicesOnCryptoDevicesChangeUseCase.execute()
+        }
+    }
+
+    private fun observeRefreshSource() {
+        refreshSource.stream()
+                .throttleFirst(refreshThrottleDelayMs)
+                .onEach { refreshDevicesUseCase.execute() }
+                .launchIn(viewModelScope)
+    }
+
+    override fun transactionUpdated(tx: VerificationTransaction) {
+        if (tx.state == VerificationTxState.Verified) {
+            queryRefreshDevicesList()
+        }
+    }
+
+    /**
+     * Force the refresh of the devices list.
+     * The devices list is the list of the devices where the user is logged in.
+     * It can be any mobile devices, and any browsers.
+     */
+    private fun queryRefreshDevicesList() {
+        refreshSource.post(Unit)
+    }
+
     override fun handle(action: DevicesAction) {
         when (action) {
             is DevicesAction.MarkAsManuallyVerified -> handleMarkAsManuallyVerifiedAction()
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/RefreshDevicesOnCryptoDevicesChangeUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/RefreshDevicesOnCryptoDevicesChangeUseCase.kt
new file mode 100644
index 0000000000..26f866aabe
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/RefreshDevicesOnCryptoDevicesChangeUseCase.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.settings.devices.v2
+
+import im.vector.app.core.di.ActiveSessionHolder
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.sample
+import org.matrix.android.sdk.api.NoOpMatrixCallback
+import org.matrix.android.sdk.flow.flow
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.seconds
+
+// TODO add unit tests
+class RefreshDevicesOnCryptoDevicesChangeUseCase @Inject constructor(
+        private val activeSessionHolder: ActiveSessionHolder,
+) {
+    private val samplingPeriodMs = 5.seconds.inWholeMilliseconds
+
+    suspend fun execute() {
+        activeSessionHolder.getSafeActiveSession()
+                ?.let { session ->
+                    session.flow().liveUserCryptoDevices(session.myUserId)
+                            .map { it.size }
+                            .distinctUntilChanged()
+                            .sample(samplingPeriodMs)
+                            .onEach {
+                                // If we have a new crypto device change, we might want to trigger refresh of device info
+                                activeSessionHolder.getSafeActiveSession()
+                                        ?.cryptoService()
+                                        ?.fetchDevicesList(NoOpMatrixCallback())
+                            }
+                            .collect()
+                }
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/RefreshDevicesUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/RefreshDevicesUseCase.kt
new file mode 100644
index 0000000000..e8f3aa7455
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/RefreshDevicesUseCase.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.features.settings.devices.v2
+
+import im.vector.app.core.di.ActiveSessionHolder
+import org.matrix.android.sdk.api.NoOpMatrixCallback
+import javax.inject.Inject
+
+// TODO add unit tests
+class RefreshDevicesUseCase @Inject constructor(
+        private val activeSessionHolder: ActiveSessionHolder,
+) {
+    fun execute() {
+        activeSessionHolder.getSafeActiveSession()?.let { session ->
+            session.cryptoService().fetchDevicesList(NoOpMatrixCallback())
+            session.cryptoService().downloadKeys(listOf(session.myUserId), true, NoOpMatrixCallback())
+        }
+    }
+}

From 07df58f4dff26933c73c5fd39ba163e297efc10e Mon Sep 17 00:00:00 2001
From: Maxime NATUREL <maxime.naturel@niji.fr>
Date: Mon, 5 Sep 2022 17:26:43 +0200
Subject: [PATCH 041/108] Updating existing unit tests

---
 .../devices/v2/overview/GetDeviceFullInfoUseCase.kt   |  1 -
 .../v2/overview/GetDeviceFullInfoUseCaseTest.kt       | 11 ++++++-----
 .../v2/overview/SessionOverviewViewModelTest.kt       |  2 +-
 3 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt
index 60c6266901..fff81b6dc5 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCase.kt
@@ -29,7 +29,6 @@ import org.matrix.android.sdk.api.util.Optional
 import org.matrix.android.sdk.api.util.toOptional
 import javax.inject.Inject
 
-// TODO update unit tests
 class GetDeviceFullInfoUseCase @Inject constructor(
         private val activeSessionHolder: ActiveSessionHolder,
         private val getCurrentSessionCrossSigningInfoUseCase: GetCurrentSessionCrossSigningInfoUseCase,
diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCaseTest.kt
index 70af681c6f..7dc8e08a4e 100644
--- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCaseTest.kt
+++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/GetDeviceFullInfoUseCaseTest.kt
@@ -19,9 +19,9 @@ package im.vector.app.features.settings.devices.v2.overview
 import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.asFlow
 import im.vector.app.features.settings.devices.v2.CurrentSessionCrossSigningInfo
-import im.vector.app.features.settings.devices.DeviceFullInfo
-import im.vector.app.features.settings.devices.GetCurrentSessionCrossSigningInfoUseCase
-import im.vector.app.features.settings.devices.GetEncryptionTrustLevelForDeviceUseCase
+import im.vector.app.features.settings.devices.v2.DeviceFullInfo
+import im.vector.app.features.settings.devices.v2.GetCurrentSessionCrossSigningInfoUseCase
+import im.vector.app.features.settings.devices.v2.GetEncryptionTrustLevelForDeviceUseCase
 import im.vector.app.features.settings.devices.v2.list.CheckIfSessionIsInactiveUseCase
 import im.vector.app.test.fakes.FakeActiveSessionHolder
 import im.vector.app.test.fakes.FakeFlowLiveDataConversions
@@ -31,6 +31,7 @@ import io.mockk.mockk
 import io.mockk.unmockkAll
 import io.mockk.verify
 import kotlinx.coroutines.flow.firstOrNull
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.runTest
 import org.amshove.kluent.shouldBeEqualTo
 import org.junit.After
@@ -90,7 +91,7 @@ class GetDeviceFullInfoUseCaseTest {
                 DeviceFullInfo(
                         deviceInfo = deviceInfo,
                         cryptoDeviceInfo = cryptoDeviceInfo,
-                        trustLevelForShield = trustLevel,
+                        roomEncryptionTrustLevel = trustLevel,
                         isInactive = isInactive,
                 )
         )
@@ -134,7 +135,7 @@ class GetDeviceFullInfoUseCaseTest {
                 isCrossSigningInitialized = true,
                 isCrossSigningVerified = false
         )
-        every { getCurrentSessionCrossSigningInfoUseCase.execute() } returns currentSessionCrossSigningInfo
+        every { getCurrentSessionCrossSigningInfoUseCase.execute() } returns flowOf(currentSessionCrossSigningInfo)
         return currentSessionCrossSigningInfo
     }
 
diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModelTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModelTest.kt
index 735c553808..4a26fc4adc 100644
--- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModelTest.kt
+++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModelTest.kt
@@ -18,7 +18,7 @@ package im.vector.app.features.settings.devices.v2.overview
 
 import com.airbnb.mvrx.Success
 import com.airbnb.mvrx.test.MvRxTestRule
-import im.vector.app.features.settings.devices.DeviceFullInfo
+import im.vector.app.features.settings.devices.v2.DeviceFullInfo
 import im.vector.app.test.fakes.FakeSession
 import im.vector.app.test.test
 import io.mockk.every

From 32f7767aa592021909e1c98ed130830b1c78c587 Mon Sep 17 00:00:00 2001
From: Maxime NATUREL <maxime.naturel@niji.fr>
Date: Mon, 5 Sep 2022 17:40:02 +0200
Subject: [PATCH 042/108] RefreshDevicesUseCase unit tests

---
 .../devices/v2/RefreshDevicesUseCase.kt       |  1 -
 .../devices/v2/RefreshDevicesUseCaseTest.kt   | 48 +++++++++++++++++++
 2 files changed, 48 insertions(+), 1 deletion(-)
 create mode 100644 vector/src/test/java/im/vector/app/features/settings/devices/v2/RefreshDevicesUseCaseTest.kt

diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/RefreshDevicesUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/RefreshDevicesUseCase.kt
index e8f3aa7455..a53ab1d2b3 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/RefreshDevicesUseCase.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/RefreshDevicesUseCase.kt
@@ -20,7 +20,6 @@ import im.vector.app.core.di.ActiveSessionHolder
 import org.matrix.android.sdk.api.NoOpMatrixCallback
 import javax.inject.Inject
 
-// TODO add unit tests
 class RefreshDevicesUseCase @Inject constructor(
         private val activeSessionHolder: ActiveSessionHolder,
 ) {
diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/RefreshDevicesUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/RefreshDevicesUseCaseTest.kt
new file mode 100644
index 0000000000..4cd7afaf08
--- /dev/null
+++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/RefreshDevicesUseCaseTest.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.settings.devices.v2
+
+import im.vector.app.test.fakes.FakeActiveSessionHolder
+import io.mockk.every
+import io.mockk.just
+import io.mockk.runs
+import io.mockk.verifyAll
+import org.junit.Test
+import org.matrix.android.sdk.api.NoOpMatrixCallback
+
+class RefreshDevicesUseCaseTest {
+
+    private val fakeActiveSessionHolder = FakeActiveSessionHolder()
+
+    private val refreshDevicesUseCase = RefreshDevicesUseCase(
+            activeSessionHolder = fakeActiveSessionHolder.instance
+    )
+
+    @Test
+    fun `given current session when refreshing then devices list and keys are fetched`() {
+        val session = fakeActiveSessionHolder.fakeSession
+        every { session.cryptoService().fetchDevicesList(any()) } just runs
+        every { session.cryptoService().downloadKeys(any(), any(), any()) } just runs
+
+        refreshDevicesUseCase.execute()
+
+        verifyAll {
+            session.cryptoService().fetchDevicesList(match { it is NoOpMatrixCallback })
+            session.cryptoService().downloadKeys(listOf(session.myUserId), true, match { it is NoOpMatrixCallback })
+        }
+    }
+}

From 7511d21a6f5b446470e1fabec683dc9909163b99 Mon Sep 17 00:00:00 2001
From: Maxime NATUREL <maxime.naturel@niji.fr>
Date: Tue, 6 Sep 2022 13:58:21 +0200
Subject: [PATCH 043/108] GetCurrentSessionCrossSigningInfoUseCase unit tests

---
 ...etCurrentSessionCrossSigningInfoUseCase.kt |   1 -
 ...rrentSessionCrossSigningInfoUseCaseTest.kt | 131 ++++++++++++++++++
 2 files changed, 131 insertions(+), 1 deletion(-)
 create mode 100644 vector/src/test/java/im/vector/app/features/settings/devices/v2/GetCurrentSessionCrossSigningInfoUseCaseTest.kt

diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/GetCurrentSessionCrossSigningInfoUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/GetCurrentSessionCrossSigningInfoUseCase.kt
index 9f7a3d8208..63e647e7c2 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/GetCurrentSessionCrossSigningInfoUseCase.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/GetCurrentSessionCrossSigningInfoUseCase.kt
@@ -26,7 +26,6 @@ import org.matrix.android.sdk.api.util.Optional
 import org.matrix.android.sdk.flow.flow
 import javax.inject.Inject
 
-// TODO add unit tests
 class GetCurrentSessionCrossSigningInfoUseCase @Inject constructor(
         private val activeSessionHolder: ActiveSessionHolder,
 ) {
diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/GetCurrentSessionCrossSigningInfoUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/GetCurrentSessionCrossSigningInfoUseCaseTest.kt
new file mode 100644
index 0000000000..178f388c7f
--- /dev/null
+++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/GetCurrentSessionCrossSigningInfoUseCaseTest.kt
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.settings.devices.v2
+
+import im.vector.app.test.fakes.FakeActiveSessionHolder
+import im.vector.app.test.test
+import im.vector.app.test.testDispatcher
+import io.mockk.every
+import io.mockk.mockk
+import io.mockk.mockkStatic
+import io.mockk.unmockkAll
+import io.mockk.verify
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runTest
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.matrix.android.sdk.api.auth.data.SessionParams
+import org.matrix.android.sdk.api.session.Session
+import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
+import org.matrix.android.sdk.api.util.toOptional
+import org.matrix.android.sdk.flow.FlowSession
+import org.matrix.android.sdk.flow.flow
+
+private const val A_DEVICE_ID = "device-id"
+
+class GetCurrentSessionCrossSigningInfoUseCaseTest {
+
+    private val fakeActiveSessionHolder = FakeActiveSessionHolder()
+
+    private val getCurrentSessionCrossSigningInfoUseCase = GetCurrentSessionCrossSigningInfoUseCase(
+            activeSessionHolder = fakeActiveSessionHolder.instance
+    )
+
+    @Before
+    fun setUp() {
+        mockkStatic("org.matrix.android.sdk.flow.FlowSessionKt")
+    }
+
+    @After
+    fun tearDown() {
+        unmockkAll()
+    }
+
+    @Test
+    fun `given the active session and existing cross signing info when getting these info then the result is correct`() = runTest(testDispatcher) {
+        val fakeSession = givenSession(A_DEVICE_ID)
+        val fakeFlowSession = givenFlowSession(fakeSession)
+        val isCrossSigningVerified = true
+        val mxCrossSigningInfo = givenMxCrossSigningInfo(isCrossSigningVerified)
+        every { fakeFlowSession.liveCrossSigningInfo(any()) } returns flowOf(mxCrossSigningInfo.toOptional())
+        val expectedResult = CurrentSessionCrossSigningInfo(
+                deviceId = A_DEVICE_ID,
+                isCrossSigningInitialized = true,
+                isCrossSigningVerified = isCrossSigningVerified
+        )
+
+        val result = getCurrentSessionCrossSigningInfoUseCase.execute()
+                .test(this)
+
+        result.assertValues(listOf(expectedResult))
+                .finish()
+        verify { fakeFlowSession.liveCrossSigningInfo(fakeSession.myUserId) }
+    }
+
+    @Test
+    fun `given the active session and no existing cross signing info when getting these info then the result is correct`() = runTest(testDispatcher) {
+        val fakeSession = givenSession(A_DEVICE_ID)
+        val fakeFlowSession = givenFlowSession(fakeSession)
+        val mxCrossSigningInfo = null
+        every { fakeFlowSession.liveCrossSigningInfo(any()) } returns flowOf(mxCrossSigningInfo.toOptional())
+        val expectedResult = CurrentSessionCrossSigningInfo(
+                deviceId = A_DEVICE_ID,
+                isCrossSigningInitialized = false,
+                isCrossSigningVerified = false
+        )
+
+        val result = getCurrentSessionCrossSigningInfoUseCase.execute()
+                .test(this)
+
+        result.assertValues(listOf(expectedResult))
+                .finish()
+        verify { fakeFlowSession.liveCrossSigningInfo(fakeSession.myUserId) }
+    }
+
+    @Test
+    fun `given no active session when getting cross signing info then the result is empty`() = runTest(testDispatcher) {
+        fakeActiveSessionHolder.givenGetSafeActiveSessionReturns(null)
+
+        val result = getCurrentSessionCrossSigningInfoUseCase.execute()
+                .test(this)
+
+        result.assertNoValues()
+                .finish()
+    }
+
+    private fun givenSession(deviceId: String): Session {
+        val sessionParams = mockk<SessionParams>()
+        every { sessionParams.deviceId } returns deviceId
+
+        val fakeSession = fakeActiveSessionHolder.fakeSession
+        fakeSession.givenSessionParams(sessionParams)
+
+        return fakeSession
+    }
+
+    private fun givenFlowSession(session: Session): FlowSession {
+        val fakeFlowSession = mockk<FlowSession>()
+        every { session.flow() } returns fakeFlowSession
+        return fakeFlowSession
+    }
+
+    private fun givenMxCrossSigningInfo(isTrusted: Boolean) = mockk<MXCrossSigningInfo>()
+            .also {
+                every { it.isTrusted() } returns isTrusted
+            }
+}

From 6394c7efde0adc837f85a844f7018dd597e4c85b Mon Sep 17 00:00:00 2001
From: Maxime NATUREL <maxime.naturel@niji.fr>
Date: Tue, 6 Sep 2022 14:47:18 +0200
Subject: [PATCH 044/108] GetDeviceFullInfoListUseCase unit tests

---
 .../v2/GetDeviceFullInfoListUseCase.kt        |   5 +-
 ...rrentSessionCrossSigningInfoUseCaseTest.kt |  16 +-
 .../v2/GetDeviceFullInfoListUseCaseTest.kt    | 182 ++++++++++++++++++
 .../im/vector/app/test/fakes/FakeSession.kt   |  11 ++
 4 files changed, 199 insertions(+), 15 deletions(-)
 create mode 100644 vector/src/test/java/im/vector/app/features/settings/devices/v2/GetDeviceFullInfoListUseCaseTest.kt

diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/GetDeviceFullInfoListUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/GetDeviceFullInfoListUseCase.kt
index 948d23de39..da2cf25f39 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/GetDeviceFullInfoListUseCase.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/GetDeviceFullInfoListUseCase.kt
@@ -27,7 +27,6 @@ import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
 import org.matrix.android.sdk.flow.flow
 import javax.inject.Inject
 
-// TODO add unit tests
 class GetDeviceFullInfoListUseCase @Inject constructor(
         private val activeSessionHolder: ActiveSessionHolder,
         private val checkIfSessionIsInactiveUseCase: CheckIfSessionIsInactiveUseCase,
@@ -58,9 +57,9 @@ class GetDeviceFullInfoListUseCase @Inject constructor(
                 .sortedByDescending { it.lastSeenTs }
                 .map { deviceInfo ->
                     val cryptoDeviceInfo = cryptoList.firstOrNull { it.deviceId == deviceInfo.deviceId }
-                    val trustLevelForShield = getEncryptionTrustLevelForDeviceUseCase.execute(currentSessionCrossSigningInfo, cryptoDeviceInfo)
+                    val roomEncryptionTrustLevel = getEncryptionTrustLevelForDeviceUseCase.execute(currentSessionCrossSigningInfo, cryptoDeviceInfo)
                     val isInactive = checkIfSessionIsInactiveUseCase.execute(deviceInfo.lastSeenTs ?: 0)
-                    DeviceFullInfo(deviceInfo, cryptoDeviceInfo, trustLevelForShield, isInactive)
+                    DeviceFullInfo(deviceInfo, cryptoDeviceInfo, roomEncryptionTrustLevel, isInactive)
                 }
     }
 }
diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/GetCurrentSessionCrossSigningInfoUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/GetCurrentSessionCrossSigningInfoUseCaseTest.kt
index 178f388c7f..f8ee1231ae 100644
--- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/GetCurrentSessionCrossSigningInfoUseCaseTest.kt
+++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/GetCurrentSessionCrossSigningInfoUseCaseTest.kt
@@ -17,6 +17,7 @@
 package im.vector.app.features.settings.devices.v2
 
 import im.vector.app.test.fakes.FakeActiveSessionHolder
+import im.vector.app.test.fakes.FakeSession
 import im.vector.app.test.test
 import im.vector.app.test.testDispatcher
 import io.mockk.every
@@ -30,11 +31,8 @@ import org.junit.After
 import org.junit.Before
 import org.junit.Test
 import org.matrix.android.sdk.api.auth.data.SessionParams
-import org.matrix.android.sdk.api.session.Session
 import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
 import org.matrix.android.sdk.api.util.toOptional
-import org.matrix.android.sdk.flow.FlowSession
-import org.matrix.android.sdk.flow.flow
 
 private const val A_DEVICE_ID = "device-id"
 
@@ -59,7 +57,7 @@ class GetCurrentSessionCrossSigningInfoUseCaseTest {
     @Test
     fun `given the active session and existing cross signing info when getting these info then the result is correct`() = runTest(testDispatcher) {
         val fakeSession = givenSession(A_DEVICE_ID)
-        val fakeFlowSession = givenFlowSession(fakeSession)
+        val fakeFlowSession = fakeSession.givenFlowSession()
         val isCrossSigningVerified = true
         val mxCrossSigningInfo = givenMxCrossSigningInfo(isCrossSigningVerified)
         every { fakeFlowSession.liveCrossSigningInfo(any()) } returns flowOf(mxCrossSigningInfo.toOptional())
@@ -80,7 +78,7 @@ class GetCurrentSessionCrossSigningInfoUseCaseTest {
     @Test
     fun `given the active session and no existing cross signing info when getting these info then the result is correct`() = runTest(testDispatcher) {
         val fakeSession = givenSession(A_DEVICE_ID)
-        val fakeFlowSession = givenFlowSession(fakeSession)
+        val fakeFlowSession = fakeSession.givenFlowSession()
         val mxCrossSigningInfo = null
         every { fakeFlowSession.liveCrossSigningInfo(any()) } returns flowOf(mxCrossSigningInfo.toOptional())
         val expectedResult = CurrentSessionCrossSigningInfo(
@@ -108,7 +106,7 @@ class GetCurrentSessionCrossSigningInfoUseCaseTest {
                 .finish()
     }
 
-    private fun givenSession(deviceId: String): Session {
+    private fun givenSession(deviceId: String): FakeSession {
         val sessionParams = mockk<SessionParams>()
         every { sessionParams.deviceId } returns deviceId
 
@@ -118,12 +116,6 @@ class GetCurrentSessionCrossSigningInfoUseCaseTest {
         return fakeSession
     }
 
-    private fun givenFlowSession(session: Session): FlowSession {
-        val fakeFlowSession = mockk<FlowSession>()
-        every { session.flow() } returns fakeFlowSession
-        return fakeFlowSession
-    }
-
     private fun givenMxCrossSigningInfo(isTrusted: Boolean) = mockk<MXCrossSigningInfo>()
             .also {
                 every { it.isTrusted() } returns isTrusted
diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/GetDeviceFullInfoListUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/GetDeviceFullInfoListUseCaseTest.kt
new file mode 100644
index 0000000000..739d5c6668
--- /dev/null
+++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/GetDeviceFullInfoListUseCaseTest.kt
@@ -0,0 +1,182 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.settings.devices.v2
+
+import im.vector.app.features.settings.devices.v2.list.CheckIfSessionIsInactiveUseCase
+import im.vector.app.test.fakes.FakeActiveSessionHolder
+import im.vector.app.test.test
+import im.vector.app.test.testDispatcher
+import io.mockk.every
+import io.mockk.mockk
+import io.mockk.mockkStatic
+import io.mockk.unmockkAll
+import io.mockk.verify
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runTest
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
+import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
+import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
+
+private const val A_DEVICE_ID_1 = "device-id-1"
+private const val A_DEVICE_ID_2 = "device-id-2"
+private const val A_DEVICE_ID_3 = "device-id-3"
+private const val A_TIMESTAMP_1 = 100L
+private const val A_TIMESTAMP_2 = 200L
+private const val A_TIMESTAMP_3 = 300L
+
+class GetDeviceFullInfoListUseCaseTest {
+
+    private val fakeActiveSessionHolder = FakeActiveSessionHolder()
+    private val checkIfSessionIsInactiveUseCase = mockk<CheckIfSessionIsInactiveUseCase>()
+    private val getEncryptionTrustLevelForDeviceUseCase = mockk<GetEncryptionTrustLevelForDeviceUseCase>()
+    private val getCurrentSessionCrossSigningInfoUseCase = mockk<GetCurrentSessionCrossSigningInfoUseCase>()
+
+    private val getDeviceFullInfoListUseCase = GetDeviceFullInfoListUseCase(
+            activeSessionHolder = fakeActiveSessionHolder.instance,
+            checkIfSessionIsInactiveUseCase = checkIfSessionIsInactiveUseCase,
+            getEncryptionTrustLevelForDeviceUseCase = getEncryptionTrustLevelForDeviceUseCase,
+            getCurrentSessionCrossSigningInfoUseCase = getCurrentSessionCrossSigningInfoUseCase,
+    )
+
+    @Before
+    fun setUp() {
+        mockkStatic("org.matrix.android.sdk.flow.FlowSessionKt")
+    }
+
+    @After
+    fun tearDown() {
+        unmockkAll()
+    }
+
+    @Test
+    fun `given active session when getting list of device full info then the result list is correct and sorted in descending order`() = runTest(testDispatcher) {
+        // Given
+        val currentSessionCrossSigningInfo = givenCurrentSessionCrossSigningInfo()
+        val fakeFlowSession = fakeActiveSessionHolder.fakeSession.givenFlowSession()
+        val cryptoDeviceInfo1 = givenACryptoDeviceInfo(A_DEVICE_ID_1)
+        val cryptoDeviceInfo2 = givenACryptoDeviceInfo(A_DEVICE_ID_2)
+        val cryptoDeviceInfo3 = givenACryptoDeviceInfo(A_DEVICE_ID_3)
+        val cryptoDeviceInfoList = listOf(cryptoDeviceInfo1, cryptoDeviceInfo2, cryptoDeviceInfo3)
+        every { fakeFlowSession.liveUserCryptoDevices(any()) } returns flowOf(cryptoDeviceInfoList)
+        val deviceInfo1 = givenADevicesInfo(
+                deviceId = A_DEVICE_ID_1,
+                lastSeenTs = A_TIMESTAMP_1,
+                isInactive = true,
+                roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Trusted,
+                cryptoDeviceInfo = cryptoDeviceInfo1
+        )
+        val deviceInfo2 = givenADevicesInfo(
+                deviceId = A_DEVICE_ID_2,
+                lastSeenTs = A_TIMESTAMP_2,
+                isInactive = false,
+                roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Trusted,
+                cryptoDeviceInfo = cryptoDeviceInfo2
+        )
+        val deviceInfo3 = givenADevicesInfo(
+                deviceId = A_DEVICE_ID_3,
+                lastSeenTs = A_TIMESTAMP_3,
+                isInactive = false,
+                roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Warning,
+                cryptoDeviceInfo = cryptoDeviceInfo3
+        )
+        val deviceInfoList = listOf(deviceInfo1, deviceInfo2, deviceInfo3)
+        every { fakeFlowSession.liveMyDevicesInfo() } returns flowOf(deviceInfoList)
+        val expectedResult1 = DeviceFullInfo(
+                deviceInfo = deviceInfo1,
+                cryptoDeviceInfo = cryptoDeviceInfo1,
+                roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Trusted,
+                isInactive = true
+        )
+        val expectedResult2 = DeviceFullInfo(
+                deviceInfo = deviceInfo2,
+                cryptoDeviceInfo = cryptoDeviceInfo2,
+                roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Trusted,
+                isInactive = false
+        )
+        val expectedResult3 = DeviceFullInfo(
+                deviceInfo = deviceInfo3,
+                cryptoDeviceInfo = cryptoDeviceInfo3,
+                roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Warning,
+                isInactive = false
+        )
+        val expectedResult = listOf(expectedResult3, expectedResult2, expectedResult1)
+
+        // When
+        val result = getDeviceFullInfoListUseCase.execute()
+                .test(this)
+
+        // Then
+        result.assertValues(expectedResult)
+                .finish()
+        verify {
+            getCurrentSessionCrossSigningInfoUseCase.execute()
+            fakeFlowSession.liveUserCryptoDevices(fakeActiveSessionHolder.fakeSession.myUserId)
+            fakeFlowSession.liveMyDevicesInfo()
+            getEncryptionTrustLevelForDeviceUseCase.execute(currentSessionCrossSigningInfo, cryptoDeviceInfo1)
+            getEncryptionTrustLevelForDeviceUseCase.execute(currentSessionCrossSigningInfo, cryptoDeviceInfo2)
+            getEncryptionTrustLevelForDeviceUseCase.execute(currentSessionCrossSigningInfo, cryptoDeviceInfo3)
+            checkIfSessionIsInactiveUseCase.execute(A_TIMESTAMP_1)
+            checkIfSessionIsInactiveUseCase.execute(A_TIMESTAMP_2)
+            checkIfSessionIsInactiveUseCase.execute(A_TIMESTAMP_3)
+        }
+    }
+
+    @Test
+    fun `given no active session when getting list then the result is empty`() = runTest(testDispatcher) {
+        // Given
+        fakeActiveSessionHolder.givenGetSafeActiveSessionReturns(null)
+
+        // When
+        val result = getDeviceFullInfoListUseCase.execute()
+                .test(this)
+
+        // Then
+        result.assertNoValues()
+                .finish()
+    }
+
+    private fun givenCurrentSessionCrossSigningInfo(): CurrentSessionCrossSigningInfo {
+        val currentSessionCrossSigningInfo = mockk<CurrentSessionCrossSigningInfo>()
+        every { getCurrentSessionCrossSigningInfoUseCase.execute() } returns flowOf(currentSessionCrossSigningInfo)
+        return currentSessionCrossSigningInfo
+    }
+
+    private fun givenACryptoDeviceInfo(deviceId: String): CryptoDeviceInfo {
+        val cryptoDeviceInfo = mockk<CryptoDeviceInfo>()
+        every { cryptoDeviceInfo.deviceId } returns deviceId
+        return cryptoDeviceInfo
+    }
+
+    private fun givenADevicesInfo(
+            deviceId: String,
+            lastSeenTs: Long,
+            isInactive: Boolean,
+            roomEncryptionTrustLevel: RoomEncryptionTrustLevel,
+            cryptoDeviceInfo: CryptoDeviceInfo,
+    ): DeviceInfo {
+        val deviceInfo = mockk<DeviceInfo>()
+        every { deviceInfo.deviceId } returns deviceId
+        every { deviceInfo.lastSeenTs } returns lastSeenTs
+        every { getEncryptionTrustLevelForDeviceUseCase.execute(any(), cryptoDeviceInfo) } returns roomEncryptionTrustLevel
+        every { checkIfSessionIsInactiveUseCase.execute(lastSeenTs) } returns isInactive
+
+        return deviceInfo
+    }
+}
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 71bcde5807..35d23e35e8 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
@@ -32,6 +32,8 @@ import org.matrix.android.sdk.api.session.getRoomSummary
 import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
 import org.matrix.android.sdk.api.session.profile.ProfileService
 import org.matrix.android.sdk.api.session.room.model.RoomSummary
+import org.matrix.android.sdk.flow.FlowSession
+import org.matrix.android.sdk.flow.flow
 
 class FakeSession(
         val fakeCryptoService: FakeCryptoService = FakeCryptoService(),
@@ -76,6 +78,15 @@ class FakeSession(
         every { this@FakeSession.sessionParams } returns sessionParams
     }
 
+    /**
+     * Do not forget to call mockkStatic("org.matrix.android.sdk.flow.FlowSessionKt") in the setup method of the tests.
+     */
+    fun givenFlowSession(): FlowSession {
+        val fakeFlowSession = mockk<FlowSession>()
+        every { flow() } returns fakeFlowSession
+        return fakeFlowSession
+    }
+
     companion object {
 
         fun withRoomSummary(roomSummary: RoomSummary) = FakeSession().apply {

From 88a5c42a4ac73a79ffcf66bd4063708d6ebbf0a0 Mon Sep 17 00:00:00 2001
From: Maxime NATUREL <maxime.naturel@niji.fr>
Date: Tue, 6 Sep 2022 16:35:23 +0200
Subject: [PATCH 045/108] DevicesViewModel unit tests

---
 .../settings/devices/v2/DevicesViewModel.kt   |   3 +-
 .../devices/v2/DevicesViewModelTest.kt        | 193 ++++++++++++++++++
 .../app/test/fakes/FakeCryptoService.kt       |   5 +-
 .../app/test/fakes/FakeVerificationService.kt |  22 ++
 4 files changed, 220 insertions(+), 3 deletions(-)
 create mode 100644 vector/src/test/java/im/vector/app/features/settings/devices/v2/DevicesViewModelTest.kt
 create mode 100644 vector/src/test/java/im/vector/app/test/fakes/FakeVerificationService.kt

diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt
index 20e3baa61d..a50cef6665 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt
@@ -36,7 +36,6 @@ import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransa
 import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
 import kotlin.time.Duration.Companion.seconds
 
-// TODO add unit tests
 class DevicesViewModel @AssistedInject constructor(
         @Assisted initialState: DevicesViewState,
         private val activeSessionHolder: ActiveSessionHolder,
@@ -99,7 +98,7 @@ class DevicesViewModel @AssistedInject constructor(
                 .execute { async ->
                     if (async is Success) {
                         val deviceFullInfoList = async.invoke()
-                        val unverifiedSessionsCount = deviceFullInfoList.count { !it.cryptoDeviceInfo?.trustLevel?.isVerified().orFalse() }
+                        val unverifiedSessionsCount = deviceFullInfoList.count { !it.cryptoDeviceInfo?.isVerified.orFalse() }
                         val inactiveSessionsCount = deviceFullInfoList.count { it.isInactive }
                         copy(
                                 devices = async,
diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/DevicesViewModelTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/DevicesViewModelTest.kt
new file mode 100644
index 0000000000..72f8920bb0
--- /dev/null
+++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/DevicesViewModelTest.kt
@@ -0,0 +1,193 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.settings.devices.v2
+
+import com.airbnb.mvrx.Success
+import com.airbnb.mvrx.test.MvRxTestRule
+import im.vector.app.test.fakes.FakeActiveSessionHolder
+import im.vector.app.test.fakes.FakeVerificationService
+import im.vector.app.test.test
+import im.vector.app.test.testDispatcher
+import io.mockk.coEvery
+import io.mockk.coVerify
+import io.mockk.every
+import io.mockk.just
+import io.mockk.mockk
+import io.mockk.runs
+import io.mockk.verify
+import kotlinx.coroutines.flow.flowOf
+import org.junit.Rule
+import org.junit.Test
+import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
+import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
+
+class DevicesViewModelTest {
+
+    @get:Rule
+    val mvRxTestRule = MvRxTestRule(testDispatcher = testDispatcher)
+
+    private val fakeActiveSessionHolder = FakeActiveSessionHolder()
+    private val getCurrentSessionCrossSigningInfoUseCase = mockk<GetCurrentSessionCrossSigningInfoUseCase>()
+    private val getDeviceFullInfoListUseCase = mockk<GetDeviceFullInfoListUseCase>()
+    private val refreshDevicesUseCase = mockk<RefreshDevicesUseCase>()
+    private val refreshDevicesOnCryptoDevicesChangeUseCase = mockk<RefreshDevicesOnCryptoDevicesChangeUseCase>()
+
+    private fun createViewModel(): DevicesViewModel {
+        return DevicesViewModel(
+                DevicesViewState(),
+                fakeActiveSessionHolder.instance,
+                getCurrentSessionCrossSigningInfoUseCase,
+                getDeviceFullInfoListUseCase,
+                refreshDevicesUseCase,
+                refreshDevicesOnCryptoDevicesChangeUseCase,
+        )
+    }
+
+    @Test
+    fun `given the viewModel when initializing it then verification listener is added`() {
+        // Given
+        val fakeVerificationService = givenVerificationService()
+        givenCurrentSessionCrossSigningInfo()
+        givenDeviceFullInfoList()
+        givenRefreshDevicesOnCryptoDevicesChange()
+
+        // When
+        val viewModel = createViewModel()
+
+        // Then
+        verify {
+            fakeVerificationService.addListener(viewModel)
+        }
+    }
+
+    @Test
+    fun `given the viewModel when clearing it then verification listener is removed`() {
+        // Given
+        val fakeVerificationService = givenVerificationService()
+        givenCurrentSessionCrossSigningInfo()
+        givenDeviceFullInfoList()
+        givenRefreshDevicesOnCryptoDevicesChange()
+
+        // When
+        val viewModel = createViewModel()
+        viewModel.onCleared()
+
+        // Then
+        verify {
+            fakeVerificationService.removeListener(viewModel)
+        }
+    }
+
+    @Test
+    fun `given the viewModel when initializing it then view state is updated with current session cross signing info`() {
+        // Given
+        givenVerificationService()
+        val currentSessionCrossSigningInfo = givenCurrentSessionCrossSigningInfo()
+        givenDeviceFullInfoList()
+        givenRefreshDevicesOnCryptoDevicesChange()
+
+        // When
+        val viewModelTest = createViewModel().test()
+
+        // Then
+        viewModelTest.assertLatestState { it.currentSessionCrossSigningInfo == currentSessionCrossSigningInfo }
+        viewModelTest.finish()
+    }
+
+    @Test
+    fun `given the viewModel when initializing it then view state is updated with current device full info list`() {
+        // Given
+        givenVerificationService()
+        givenCurrentSessionCrossSigningInfo()
+        val deviceFullInfoList = givenDeviceFullInfoList()
+        givenRefreshDevicesOnCryptoDevicesChange()
+
+        // When
+        val viewModelTest = createViewModel().test()
+
+        // Then
+        viewModelTest.assertLatestState {
+            it.devices is Success
+                    && it.devices.invoke() == deviceFullInfoList
+                    && it.inactiveSessionsCount == 1
+                    && it.unverifiedSessionsCount == 1
+        }
+        viewModelTest.finish()
+    }
+
+    @Test
+    fun `given the viewModel when initializing it then devices are refreshed on crypto devices change`() {
+        // Given
+        givenVerificationService()
+        givenCurrentSessionCrossSigningInfo()
+        givenDeviceFullInfoList()
+        givenRefreshDevicesOnCryptoDevicesChange()
+
+        // When
+        createViewModel()
+
+        // Then
+        coVerify { refreshDevicesOnCryptoDevicesChangeUseCase.execute() }
+    }
+
+    private fun givenVerificationService(): FakeVerificationService {
+        val fakeVerificationService = fakeActiveSessionHolder
+                .fakeSession
+                .fakeCryptoService
+                .fakeVerificationService
+        every { fakeVerificationService.addListener(any()) } just runs
+        every { fakeVerificationService.removeListener(any()) } just runs
+        return fakeVerificationService
+    }
+
+    private fun givenCurrentSessionCrossSigningInfo(): CurrentSessionCrossSigningInfo {
+        val currentSessionCrossSigningInfo = mockk<CurrentSessionCrossSigningInfo>()
+        every { getCurrentSessionCrossSigningInfoUseCase.execute() } returns flowOf(currentSessionCrossSigningInfo)
+        return currentSessionCrossSigningInfo
+    }
+
+    /**
+     * Generate mocked deviceFullInfo list with 1 unverified and inactive + 1 verified and active.
+     */
+    private fun givenDeviceFullInfoList(): List<DeviceFullInfo> {
+        val verifiedCryptoDeviceInfo = mockk<CryptoDeviceInfo>()
+        every { verifiedCryptoDeviceInfo.isVerified } returns true
+        val unverifiedCryptoDeviceInfo = mockk<CryptoDeviceInfo>()
+        every { unverifiedCryptoDeviceInfo.isVerified } returns false
+
+        val deviceFullInfo1 = DeviceFullInfo(
+                deviceInfo = mockk(),
+                cryptoDeviceInfo = verifiedCryptoDeviceInfo,
+                roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Trusted,
+                isInactive = false
+        )
+        val deviceFullInfo2 = DeviceFullInfo(
+                deviceInfo = mockk(),
+                cryptoDeviceInfo = unverifiedCryptoDeviceInfo,
+                roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Warning,
+                isInactive = true
+        )
+        val deviceFullInfoList = listOf(deviceFullInfo1, deviceFullInfo2)
+        val deviceFullInfoListFlow = flowOf(deviceFullInfoList)
+        every { getDeviceFullInfoListUseCase.execute() } returns deviceFullInfoListFlow
+        return deviceFullInfoList
+    }
+
+    private fun givenRefreshDevicesOnCryptoDevicesChange() {
+        coEvery { refreshDevicesOnCryptoDevicesChangeUseCase.execute() } just runs
+    }
+}
diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeCryptoService.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeCryptoService.kt
index 197ccf4cd2..538ce671d2 100644
--- a/vector/src/test/java/im/vector/app/test/fakes/FakeCryptoService.kt
+++ b/vector/src/test/java/im/vector/app/test/fakes/FakeCryptoService.kt
@@ -24,7 +24,8 @@ import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
 import org.matrix.android.sdk.api.util.Optional
 
 class FakeCryptoService(
-        val fakeCrossSigningService: FakeCrossSigningService = FakeCrossSigningService()
+        val fakeCrossSigningService: FakeCrossSigningService = FakeCrossSigningService(),
+        val fakeVerificationService: FakeVerificationService = FakeVerificationService(),
 ) : CryptoService by mockk() {
 
     var roomKeysExport = ByteArray(size = 1)
@@ -34,6 +35,8 @@ class FakeCryptoService(
 
     override fun crossSigningService() = fakeCrossSigningService
 
+    override fun verificationService() = fakeVerificationService
+
     override suspend fun exportRoomKeys(password: String) = roomKeysExport
 
     override fun getLiveCryptoDeviceInfo() = MutableLiveData(cryptoDeviceInfos.values.toList())
diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeVerificationService.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeVerificationService.kt
new file mode 100644
index 0000000000..984a48b2c1
--- /dev/null
+++ b/vector/src/test/java/im/vector/app/test/fakes/FakeVerificationService.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.test.fakes
+
+import io.mockk.mockk
+import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
+
+class FakeVerificationService : VerificationService by mockk()

From c65bbd91d95f37979297afe970380dc045b0d06e Mon Sep 17 00:00:00 2001
From: Maxime NATUREL <maxime.naturel@niji.fr>
Date: Tue, 6 Sep 2022 16:43:38 +0200
Subject: [PATCH 046/108] Fix some coding style issues

---
 .../app/features/settings/devices/v2/DevicesViewModel.kt    | 1 -
 .../devices/v2/GetCurrentSessionCrossSigningInfoUseCase.kt  | 1 -
 .../devices/v2/GetEncryptionTrustLevelForDeviceUseCase.kt   | 1 -
 .../features/settings/devices/v2/DevicesViewModelTest.kt    | 6 ++----
 .../settings/devices/v2/GetDeviceFullInfoListUseCaseTest.kt | 2 +-
 5 files changed, 3 insertions(+), 8 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt
index a50cef6665..e0b6368fc1 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt
@@ -151,4 +151,3 @@ class DevicesViewModel @AssistedInject constructor(
         // TODO implement when needed
     }
 }
-
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/GetCurrentSessionCrossSigningInfoUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/GetCurrentSessionCrossSigningInfoUseCase.kt
index 63e647e7c2..f41b3d4cf8 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/GetCurrentSessionCrossSigningInfoUseCase.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/GetCurrentSessionCrossSigningInfoUseCase.kt
@@ -17,7 +17,6 @@
 package im.vector.app.features.settings.devices.v2
 
 import im.vector.app.core.di.ActiveSessionHolder
-import im.vector.app.features.settings.devices.v2.CurrentSessionCrossSigningInfo
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.flow.map
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/GetEncryptionTrustLevelForDeviceUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/GetEncryptionTrustLevelForDeviceUseCase.kt
index 7f330b71d5..6f0dcbface 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/GetEncryptionTrustLevelForDeviceUseCase.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/GetEncryptionTrustLevelForDeviceUseCase.kt
@@ -16,7 +16,6 @@
 
 package im.vector.app.features.settings.devices.v2
 
-import im.vector.app.features.settings.devices.v2.CurrentSessionCrossSigningInfo
 import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
 import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
 import javax.inject.Inject
diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/DevicesViewModelTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/DevicesViewModelTest.kt
index 72f8920bb0..cc5cdf6e39 100644
--- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/DevicesViewModelTest.kt
+++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/DevicesViewModelTest.kt
@@ -121,10 +121,8 @@ class DevicesViewModelTest {
 
         // Then
         viewModelTest.assertLatestState {
-            it.devices is Success
-                    && it.devices.invoke() == deviceFullInfoList
-                    && it.inactiveSessionsCount == 1
-                    && it.unverifiedSessionsCount == 1
+            it.devices is Success && it.devices.invoke() == deviceFullInfoList &&
+                    it.inactiveSessionsCount == 1 && it.unverifiedSessionsCount == 1
         }
         viewModelTest.finish()
     }
diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/GetDeviceFullInfoListUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/GetDeviceFullInfoListUseCaseTest.kt
index 739d5c6668..fa9f742976 100644
--- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/GetDeviceFullInfoListUseCaseTest.kt
+++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/GetDeviceFullInfoListUseCaseTest.kt
@@ -66,7 +66,7 @@ class GetDeviceFullInfoListUseCaseTest {
     }
 
     @Test
-    fun `given active session when getting list of device full info then the result list is correct and sorted in descending order`() = runTest(testDispatcher) {
+    fun `given active session when getting list of device full info then the list is correct and sorted in descending order`() = runTest(testDispatcher) {
         // Given
         val currentSessionCrossSigningInfo = givenCurrentSessionCrossSigningInfo()
         val fakeFlowSession = fakeActiveSessionHolder.fakeSession.givenFlowSession()

From 7d549a311f6083cdd5fc6352b6312dbe5a29e508 Mon Sep 17 00:00:00 2001
From: Maxime NATUREL <maxime.naturel@niji.fr>
Date: Tue, 6 Sep 2022 16:47:39 +0200
Subject: [PATCH 047/108] Adding changelog entry

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

diff --git a/changelog.d/7043.wip b/changelog.d/7043.wip
new file mode 100644
index 0000000000..3c9b7731bf
--- /dev/null
+++ b/changelog.d/7043.wip
@@ -0,0 +1 @@
+[Devices Management] Refactor some code to improve testability

From 2592bc377220dc3cbacc1d963b18b7c5031ce244 Mon Sep 17 00:00:00 2001
From: Maxime NATUREL <maxime.naturel@niji.fr>
Date: Tue, 6 Sep 2022 17:30:36 +0200
Subject: [PATCH 048/108] RefreshDevicesOnCryptoDevicesChangeUseCase unit tests

---
 ...reshDevicesOnCryptoDevicesChangeUseCase.kt |  5 +-
 ...DevicesOnCryptoDevicesChangeUseCaseTest.kt | 77 +++++++++++++++++++
 2 files changed, 78 insertions(+), 4 deletions(-)
 create mode 100644 vector/src/test/java/im/vector/app/features/settings/devices/v2/RefreshDevicesOnCryptoDevicesChangeUseCaseTest.kt

diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/RefreshDevicesOnCryptoDevicesChangeUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/RefreshDevicesOnCryptoDevicesChangeUseCase.kt
index 26f866aabe..7d0a96eb0d 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/RefreshDevicesOnCryptoDevicesChangeUseCase.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/RefreshDevicesOnCryptoDevicesChangeUseCase.kt
@@ -27,7 +27,6 @@ import org.matrix.android.sdk.flow.flow
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.seconds
 
-// TODO add unit tests
 class RefreshDevicesOnCryptoDevicesChangeUseCase @Inject constructor(
         private val activeSessionHolder: ActiveSessionHolder,
 ) {
@@ -42,9 +41,7 @@ class RefreshDevicesOnCryptoDevicesChangeUseCase @Inject constructor(
                             .sample(samplingPeriodMs)
                             .onEach {
                                 // If we have a new crypto device change, we might want to trigger refresh of device info
-                                activeSessionHolder.getSafeActiveSession()
-                                        ?.cryptoService()
-                                        ?.fetchDevicesList(NoOpMatrixCallback())
+                                session.cryptoService().fetchDevicesList(NoOpMatrixCallback())
                             }
                             .collect()
                 }
diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/RefreshDevicesOnCryptoDevicesChangeUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/RefreshDevicesOnCryptoDevicesChangeUseCaseTest.kt
new file mode 100644
index 0000000000..97958d04ed
--- /dev/null
+++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/RefreshDevicesOnCryptoDevicesChangeUseCaseTest.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.settings.devices.v2
+
+import im.vector.app.test.fakes.FakeActiveSessionHolder
+import io.mockk.every
+import io.mockk.just
+import io.mockk.mockk
+import io.mockk.mockkStatic
+import io.mockk.runs
+import io.mockk.unmockkAll
+import io.mockk.verify
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runTest
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
+import org.matrix.android.sdk.flow.FlowSession
+import org.matrix.android.sdk.flow.flow
+
+class RefreshDevicesOnCryptoDevicesChangeUseCaseTest {
+
+    private val fakeActiveSessionHolder = FakeActiveSessionHolder()
+
+    private val refreshDevicesOnCryptoDevicesChangeUseCase = RefreshDevicesOnCryptoDevicesChangeUseCase(
+            activeSessionHolder = fakeActiveSessionHolder.instance
+    )
+
+    @Before
+    fun setUp() {
+        mockkStatic("org.matrix.android.sdk.flow.FlowSessionKt")
+    }
+
+    @After
+    fun tearDown() {
+        unmockkAll()
+    }
+
+    @Test
+    fun `given the current session when crypto devices list changes then the devices list is refreshed`() = runTest {
+        // Given
+        val device1 = givenACryptoDevice()
+        val devices = listOf(device1)
+        val fakeSession = fakeActiveSessionHolder.fakeSession
+        val flowSession = mockk<FlowSession>()
+        every { fakeSession.flow() } returns flowSession
+        every { flowSession.liveUserCryptoDevices(any()) } returns flowOf(devices)
+        every { fakeSession.cryptoService().fetchDevicesList(any()) } just runs
+
+        // When
+        refreshDevicesOnCryptoDevicesChangeUseCase.execute()
+
+        // Then
+        verify {
+            flowSession.liveUserCryptoDevices(fakeSession.myUserId)
+            // FIXME the following verification does not work due to the usage of Flow.sample() inside the use case implementation
+            // fakeSession.cryptoService().fetchDevicesList(match { it is NoOpMatrixCallback })
+        }
+    }
+
+    private fun givenACryptoDevice(): CryptoDeviceInfo = mockk()
+}

From ab4ebc7f11320794d64ef59600c72092fb061963 Mon Sep 17 00:00:00 2001
From: Onuray Sahin <onuray.sahin@gmail.com>
Date: Thu, 8 Sep 2022 13:47:07 +0300
Subject: [PATCH 049/108] List devices.

---
 .../settings/devices/v2/DevicesViewState.kt   | 17 ++++++++++-
 .../v2/VectorSettingsDevicesFragment.kt       |  5 ++--
 .../v2/list/OtherSessionsController.kt        |  2 +-
 .../devices/v2/list/OtherSessionsView.kt      |  4 +--
 .../devices/v2/list/SessionsListHeaderView.kt |  7 ++++-
 .../v2/othersessions/OtherSessionsFragment.kt | 30 +++++++++++++++++++
 .../res/layout/fragment_other_sessions.xml    | 21 +++++++++++++
 .../res/layout/view_sessions_list_header.xml  |  4 +--
 8 files changed, 81 insertions(+), 9 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewState.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewState.kt
index 3fc061daa4..5ca3e71a06 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewState.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewState.kt
@@ -19,6 +19,8 @@ package im.vector.app.features.settings.devices.v2
 import com.airbnb.mvrx.Async
 import com.airbnb.mvrx.MavericksState
 import com.airbnb.mvrx.Uninitialized
+import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
+import org.matrix.android.sdk.api.extensions.orFalse
 
 data class DevicesViewState(
         val currentSessionCrossSigningInfo: CurrentSessionCrossSigningInfo = CurrentSessionCrossSigningInfo(),
@@ -26,4 +28,17 @@ data class DevicesViewState(
         val unverifiedSessionsCount: Int = 0,
         val inactiveSessionsCount: Int = 0,
         val isLoading: Boolean = false,
-) : MavericksState
+        val currentFilter: DeviceManagerFilterType = DeviceManagerFilterType.ALL_SESSIONS,
+) : MavericksState {
+
+    fun List<DeviceFullInfo>?.filteredDevices(): List<DeviceFullInfo>? {
+        return this?.filter {
+            when (currentFilter) {
+                DeviceManagerFilterType.ALL_SESSIONS -> true
+                DeviceManagerFilterType.VERIFIED -> it.cryptoDeviceInfo?.isVerified.orFalse()
+                DeviceManagerFilterType.UNVERIFIED -> !it.cryptoDeviceInfo?.isVerified.orFalse()
+                DeviceManagerFilterType.INACTIVE -> it.isInactive
+            }
+        }
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt
index 684f9cdae9..33c0a7ca74 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt
@@ -37,10 +37,11 @@ import im.vector.app.core.resources.DrawableProvider
 import im.vector.app.databinding.FragmentSettingsDevicesBinding
 import im.vector.app.features.crypto.recover.SetupMode
 import im.vector.app.features.crypto.verification.VerificationBottomSheet
+import im.vector.app.features.settings.devices.v2.list.NUMBER_OF_OTHER_DEVICES_TO_RENDER
+import im.vector.app.features.settings.devices.v2.list.OtherSessionsView
 import im.vector.app.features.settings.devices.v2.list.SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS
 import im.vector.app.features.settings.devices.v2.list.SecurityRecommendationViewState
 import im.vector.app.features.settings.devices.v2.list.SessionInfoViewState
-import im.vector.app.features.settings.devices.v2.list.OtherSessionsView
 import javax.inject.Inject
 
 /**
@@ -198,7 +199,7 @@ class VectorSettingsDevicesFragment :
         } else {
             views.deviceListHeaderOtherSessions.isVisible = true
             views.deviceListOtherSessions.isVisible = true
-            views.deviceListOtherSessions.render(otherDevices)
+            views.deviceListOtherSessions.render(otherDevices.take(NUMBER_OF_OTHER_DEVICES_TO_RENDER), otherDevices.size)
         }
     }
 
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt
index 468b19c45a..06f3373f61 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsController.kt
@@ -50,7 +50,7 @@ class OtherSessionsController @Inject constructor(
                 text(host.stringProvider.getString(R.string.no_result_placeholder))
             }
         } else {
-            data.take(NUMBER_OF_OTHER_DEVICES_TO_RENDER).forEach { device ->
+            data.forEach { device ->
                 val dateFormatKind = if (device.isInactive) DateFormatKind.TIMELINE_DAY_DIVIDER else DateFormatKind.DEFAULT_DATE_AND_TIME
                 val formattedLastActivityDate = host.dateFormatter.format(device.deviceInfo.lastSeenTs, dateFormatKind)
                 val description = if (device.isInactive) {
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsView.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsView.kt
index 37a2db2a96..c6eccc94b6 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsView.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsView.kt
@@ -55,9 +55,9 @@ class OtherSessionsView @JvmOverloads constructor(
         }
     }
 
-    fun render(devices: List<DeviceFullInfo>) {
+    fun render(devices: List<DeviceFullInfo>, totalNumberOfDevices: Int) {
         views.otherSessionsRecyclerView.configureWith(otherSessionsController, hasFixedSize = true)
-        views.otherSessionsViewAllButton.text = context.getString(R.string.device_manager_other_sessions_view_all, devices.size)
+        views.otherSessionsViewAllButton.text = context.getString(R.string.device_manager_other_sessions_view_all, totalNumberOfDevices)
         otherSessionsController.setData(devices)
     }
 
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionsListHeaderView.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionsListHeaderView.kt
index 547ed93f24..2b93c3447e 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionsListHeaderView.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionsListHeaderView.kt
@@ -54,7 +54,12 @@ class SessionsListHeaderView @JvmOverloads constructor(
 
     private fun setTitle(typedArray: TypedArray) {
         val title = typedArray.getString(R.styleable.SessionsListHeaderView_devicesListHeaderTitle)
-        binding.sessionsListHeaderTitle.text = title
+        if (title.isNullOrEmpty()) {
+            binding.sessionsListHeaderTitle.isVisible = false
+        } else {
+            binding.sessionsListHeaderTitle.isVisible = true
+            binding.sessionsListHeaderTitle.text = title
+        }
     }
 
     private fun setDescription(typedArray: TypedArray) {
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt
index 43d3005f16..8b014c4ba8 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt
@@ -21,16 +21,25 @@ import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
 import android.widget.Toast
+import androidx.core.view.isVisible
+import com.airbnb.mvrx.Success
+import com.airbnb.mvrx.fragmentViewModel
+import com.airbnb.mvrx.withState
 import dagger.hilt.android.AndroidEntryPoint
 import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
 import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment.ResultListener.Companion.RESULT_OK
 import im.vector.app.core.platform.VectorBaseFragment
 import im.vector.app.databinding.FragmentOtherSessionsBinding
+import im.vector.app.features.settings.devices.v2.DeviceFullInfo
+import im.vector.app.features.settings.devices.v2.DevicesViewModel
 import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterBottomSheet
+import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
 
 @AndroidEntryPoint
 class OtherSessionsFragment : VectorBaseFragment<FragmentOtherSessionsBinding>(), VectorBaseBottomSheetDialogFragment.ResultListener {
 
+    private val viewModel: DevicesViewModel by fragmentViewModel()
+
     override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentOtherSessionsBinding {
         return FragmentOtherSessionsBinding.inflate(layoutInflater, container, false)
     }
@@ -54,4 +63,25 @@ class OtherSessionsFragment : VectorBaseFragment<FragmentOtherSessionsBinding>()
             Toast.makeText(requireContext(), data.toString(), Toast.LENGTH_LONG).show()
         }
     }
+
+    override fun invalidate() = withState(viewModel) { state ->
+        if (state.devices is Success) {
+            with(state) {
+                val devices = state.devices()
+                        ?.filter { it.deviceInfo.deviceId != state.currentSessionCrossSigningInfo.deviceId }
+                        ?.filteredDevices()
+                renderDevices(devices, state.currentFilter)
+            }
+        }
+    }
+
+    private fun renderDevices(devices: List<DeviceFullInfo>?, currentFilter: DeviceManagerFilterType) {
+        views.otherSessionsFilterBadgeImageView.isVisible = currentFilter != DeviceManagerFilterType.ALL_SESSIONS
+
+        if (devices.isNullOrEmpty()) {
+            // TODO. Render empty state
+        } else {
+            views.deviceListOtherSessions.render(devices, devices.size)
+        }
+    }
 }
diff --git a/vector/src/main/res/layout/fragment_other_sessions.xml b/vector/src/main/res/layout/fragment_other_sessions.xml
index e0450fb5e5..8b504ca903 100644
--- a/vector/src/main/res/layout/fragment_other_sessions.xml
+++ b/vector/src/main/res/layout/fragment_other_sessions.xml
@@ -46,4 +46,25 @@
 
     </com.google.android.material.appbar.AppBarLayout>
 
+
+
+    <im.vector.app.features.settings.devices.v2.list.SessionsListHeaderView
+        android:id="@+id/deviceListHeaderOtherSessions"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        app:devicesListHeaderDescription="@string/settings_sessions_other_description"
+        app:devicesListHeaderTitle=""
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/appBarLayout"/>
+
+    <im.vector.app.features.settings.devices.v2.list.OtherSessionsView
+        android:id="@+id/deviceListOtherSessions"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="16dp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/deviceListHeaderOtherSessions" />
+
 </androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/vector/src/main/res/layout/view_sessions_list_header.xml b/vector/src/main/res/layout/view_sessions_list_header.xml
index d690ee4c87..6139ff4815 100644
--- a/vector/src/main/res/layout/view_sessions_list_header.xml
+++ b/vector/src/main/res/layout/view_sessions_list_header.xml
@@ -23,10 +23,10 @@
         style="@style/TextAppearance.Vector.Body.DevicesManagement"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
+        android:layout_marginHorizontal="@dimen/layout_horizontal_margin"
         android:layout_marginTop="18.5dp"
-        android:layout_marginEnd="40dp"
         app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintStart_toStartOf="@id/sessions_list_header_title"
+        app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toBottomOf="@id/sessions_list_header_title"
         tools:text="For best security, verify your sessions and sign out from any session that you don’t recognize or use anymore. Learn More." />
 </merge>

From 41ca662dccc189dc9451e285954c1928fcf36bee Mon Sep 17 00:00:00 2001
From: Onuray Sahin <onuray.sahin@gmail.com>
Date: Thu, 8 Sep 2022 18:28:17 +0300
Subject: [PATCH 050/108] Update device list according to the filter type.

---
 .../src/main/res/values/strings.xml           |   9 ++
 .../ui-styles/src/main/res/values/colors.xml  |   1 +
 ..._sessions_security_recommendation_view.xml |  11 ++
 .../settings/devices/v2/DevicesAction.kt      |   2 +
 .../settings/devices/v2/DevicesViewModel.kt   |  10 ++
 .../v2/othersessions/OtherSessionsFragment.kt |  55 ++++++++-
 ...OtherSessionsSecurityRecommendationView.kt | 107 ++++++++++++++++++
 ...SessionsSecurityRecommendationViewState.kt |  24 ++++
 .../res/layout/fragment_other_sessions.xml    |  27 ++++-
 ..._other_session_security_recommendation.xml |  46 ++++++++
 10 files changed, 283 insertions(+), 9 deletions(-)
 create mode 100644 library/ui-styles/src/main/res/values/stylable_other_sessions_security_recommendation_view.xml
 create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsSecurityRecommendationView.kt
 create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsSecurityRecommendationViewState.kt
 create mode 100644 vector/src/main/res/layout/view_other_session_security_recommendation.xml

diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml
index fd18ee8992..5eaeae4d86 100644
--- a/library/ui-strings/src/main/res/values/strings.xml
+++ b/library/ui-strings/src/main/res/values/strings.xml
@@ -3295,5 +3295,14 @@
     </plurals>
     <string name="device_manager_other_sessions_title">Other sessions</string>
     <string name="a11y_device_manager_filter">Filter</string>
+    <string name="device_manager_other_sessions_recommendation_title_verified">Verified</string>
+    <string name="device_manager_other_sessions_recommendation_description_verified">For best security, sign out from any session that you don’t recognize or use anymore.</string>
+    <string name="device_manager_other_sessions_recommendation_title_unverified">Unverified</string>
+    <string name="device_manager_other_sessions_recommendation_description_unverified">Verify your sessions for enhanced secure messaging or sign out from those you don’t recognize or use anymore.</string>
+    <string name="device_manager_other_sessions_recommendation_title_inactive">Inactive</string>
+    <plurals name="device_manager_other_sessions_recommendation_description_inactive">
+        <item quantity="one">Consider signing out from old sessions (%1$d day or more) you don’t use anymore.</item>
+        <item quantity="other">Consider signing out from old sessions (%1$d days or more) you don’t use anymore.</item>
+    </plurals>
 
 </resources>
diff --git a/library/ui-styles/src/main/res/values/colors.xml b/library/ui-styles/src/main/res/values/colors.xml
index 01af740d43..3d6bc91f2e 100644
--- a/library/ui-styles/src/main/res/values/colors.xml
+++ b/library/ui-styles/src/main/res/values/colors.xml
@@ -141,6 +141,7 @@
 
     <!-- Shield colors -->
     <color name="shield_color_trust">#0DBD8B</color>
+    <color name="shield_color_trust_background">#0F0DBD8B</color>
     <color name="shield_color_black">#17191C</color>
     <color name="shield_color_warning">#FF4B55</color>
     <color name="shield_color_warning_background">#0FFF4B55</color>
diff --git a/library/ui-styles/src/main/res/values/stylable_other_sessions_security_recommendation_view.xml b/library/ui-styles/src/main/res/values/stylable_other_sessions_security_recommendation_view.xml
new file mode 100644
index 0000000000..6a46132b13
--- /dev/null
+++ b/library/ui-styles/src/main/res/values/stylable_other_sessions_security_recommendation_view.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+    <declare-styleable name="OtherSessionsSecurityRecommendationView">
+        <attr name="otherSessionsRecommendationTitle" format="string" />
+        <attr name="otherSessionsRecommendationDescription" format="string" />
+        <attr name="otherSessionsRecommendationImageResource" format="reference" />
+        <attr name="otherSessionsRecommendationImageBackgroundTint" format="color" />
+    </declare-styleable>
+
+</resources>
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesAction.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesAction.kt
index 8c7718bfcf..3c459ca992 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesAction.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesAction.kt
@@ -17,8 +17,10 @@
 package im.vector.app.features.settings.devices.v2
 
 import im.vector.app.core.platform.VectorViewModelAction
+import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
 import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
 
 sealed class DevicesAction : VectorViewModelAction {
     data class MarkAsManuallyVerified(val cryptoDeviceInfo: CryptoDeviceInfo) : DevicesAction()
+    data class FilterDevices(val filterType: DeviceManagerFilterType) : DevicesAction()
 }
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt
index e0b6368fc1..4bdadda815 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt
@@ -144,9 +144,19 @@ class DevicesViewModel @AssistedInject constructor(
     override fun handle(action: DevicesAction) {
         when (action) {
             is DevicesAction.MarkAsManuallyVerified -> handleMarkAsManuallyVerifiedAction()
+            is DevicesAction.FilterDevices -> handleFilterDevices(action)
         }
     }
 
+    private fun handleFilterDevices(action: DevicesAction.FilterDevices) {
+        setState {
+            copy(
+                    currentFilter = action.filterType
+            )
+        }
+        queryRefreshDevicesList()
+    }
+
     private fun handleMarkAsManuallyVerifiedAction() {
         // TODO implement when needed
     }
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt
index 8b014c4ba8..2996de5658 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt
@@ -20,25 +20,31 @@ 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 com.airbnb.mvrx.Success
 import com.airbnb.mvrx.fragmentViewModel
 import com.airbnb.mvrx.withState
 import dagger.hilt.android.AndroidEntryPoint
+import im.vector.app.R
 import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
 import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment.ResultListener.Companion.RESULT_OK
 import im.vector.app.core.platform.VectorBaseFragment
+import im.vector.app.core.resources.ColorProvider
 import im.vector.app.databinding.FragmentOtherSessionsBinding
 import im.vector.app.features.settings.devices.v2.DeviceFullInfo
+import im.vector.app.features.settings.devices.v2.DevicesAction
 import im.vector.app.features.settings.devices.v2.DevicesViewModel
 import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterBottomSheet
 import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
+import im.vector.app.features.settings.devices.v2.list.SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS
+import im.vector.app.features.themes.ThemeUtils
+import javax.inject.Inject
 
 @AndroidEntryPoint
 class OtherSessionsFragment : VectorBaseFragment<FragmentOtherSessionsBinding>(), VectorBaseBottomSheetDialogFragment.ResultListener {
 
     private val viewModel: DevicesViewModel by fragmentViewModel()
+    @Inject lateinit var colorProvider: ColorProvider
 
     override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentOtherSessionsBinding {
         return FragmentOtherSessionsBinding.inflate(layoutInflater, container, false)
@@ -59,8 +65,8 @@ class OtherSessionsFragment : VectorBaseFragment<FragmentOtherSessionsBinding>()
     }
 
     override fun onBottomSheetResult(resultCode: Int, data: Any?) {
-        if (resultCode == RESULT_OK && data != null) {
-            Toast.makeText(requireContext(), data.toString(), Toast.LENGTH_LONG).show()
+        if (resultCode == RESULT_OK && data != null && data is DeviceManagerFilterType) {
+            viewModel.handle(DevicesAction.FilterDevices(data))
         }
     }
 
@@ -77,10 +83,51 @@ class OtherSessionsFragment : VectorBaseFragment<FragmentOtherSessionsBinding>()
 
     private fun renderDevices(devices: List<DeviceFullInfo>?, currentFilter: DeviceManagerFilterType) {
         views.otherSessionsFilterBadgeImageView.isVisible = currentFilter != DeviceManagerFilterType.ALL_SESSIONS
+        views.otherSessionsSecurityRecommendationView.isVisible = currentFilter != DeviceManagerFilterType.ALL_SESSIONS
+        views.deviceListHeaderOtherSessions.isVisible = currentFilter == DeviceManagerFilterType.ALL_SESSIONS
+
+        when (currentFilter) {
+            DeviceManagerFilterType.VERIFIED -> {
+                views.otherSessionsSecurityRecommendationView.render(
+                        OtherSessionsSecurityRecommendationViewState(
+                                title = getString(R.string.device_manager_other_sessions_recommendation_title_verified),
+                                description = getString(R.string.device_manager_other_sessions_recommendation_description_verified),
+                                imageResourceId = R.drawable.ic_shield_trusted_no_border,
+                                imageTintColorResourceId = colorProvider.getColor(R.color.shield_color_trust_background)
+                        )
+                )
+            }
+            DeviceManagerFilterType.UNVERIFIED -> {
+                views.otherSessionsSecurityRecommendationView.render(
+                        OtherSessionsSecurityRecommendationViewState(
+                                title = getString(R.string.device_manager_other_sessions_recommendation_title_unverified),
+                                description = getString(R.string.device_manager_other_sessions_recommendation_description_unverified),
+                                imageResourceId = R.drawable.ic_shield_warning_no_border,
+                                imageTintColorResourceId = colorProvider.getColor(R.color.shield_color_warning_background)
+                        )
+                )
+            }
+            DeviceManagerFilterType.INACTIVE -> {
+                views.otherSessionsSecurityRecommendationView.render(
+                        OtherSessionsSecurityRecommendationViewState(
+                                title = getString(R.string.device_manager_other_sessions_recommendation_title_inactive),
+                                description = resources.getQuantityString(
+                                        R.plurals.device_manager_other_sessions_recommendation_description_inactive,
+                                        SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS,
+                                        SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS
+                                ),
+                                imageResourceId = R.drawable.ic_inactive_sessions,
+                                imageTintColorResourceId = ThemeUtils.getColor(requireContext(), R.attr.vctr_system)
+                        )
+                )
+            }
+            DeviceManagerFilterType.ALL_SESSIONS -> { /* NOOP. View is not visible */ }
+        }
 
         if (devices.isNullOrEmpty()) {
-            // TODO. Render empty state
+            views.deviceListOtherSessions.isVisible = false
         } else {
+            views.deviceListOtherSessions.isVisible = true
             views.deviceListOtherSessions.render(devices, devices.size)
         }
     }
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsSecurityRecommendationView.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsSecurityRecommendationView.kt
new file mode 100644
index 0000000000..c72dc30a93
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsSecurityRecommendationView.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 im.vector.app.features.settings.devices.v2.othersessions
+
+import android.content.Context
+import android.content.res.ColorStateList
+import android.content.res.TypedArray
+import android.util.AttributeSet
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.content.res.use
+import dagger.hilt.android.AndroidEntryPoint
+import im.vector.app.R
+import im.vector.app.core.extensions.setTextWithColoredPart
+import im.vector.app.databinding.ViewOtherSessionSecurityRecommendationBinding
+
+@AndroidEntryPoint
+class OtherSessionsSecurityRecommendationView  @JvmOverloads constructor(
+        context: Context,
+        attrs: AttributeSet? = null,
+        defStyleAttr: Int = 0
+) : ConstraintLayout(context, attrs, defStyleAttr) {
+
+    private val views: ViewOtherSessionSecurityRecommendationBinding
+    var onLearnMoreClickListener: (() -> Unit)? = null
+
+    init {
+        inflate(context, R.layout.view_other_session_security_recommendation, this)
+        views = ViewOtherSessionSecurityRecommendationBinding.bind(this)
+
+        context.obtainStyledAttributes(
+                attrs,
+                R.styleable.OtherSessionsSecurityRecommendationView,
+                0,
+                0
+        ).use {
+            setTitle(it)
+            setDescription(it)
+            setImage(it)
+        }
+    }
+
+    private fun setTitle(typedArray: TypedArray) {
+        val title = typedArray.getString(R.styleable.OtherSessionsSecurityRecommendationView_otherSessionsRecommendationTitle)
+        setTitle(title)
+    }
+
+    private fun setTitle(title: String?) {
+        views.recommendationTitleTextView.text = title
+    }
+
+    private fun setDescription(typedArray: TypedArray) {
+        val description = typedArray.getString(R.styleable.OtherSessionsSecurityRecommendationView_otherSessionsRecommendationDescription)
+        setDescription(description)
+    }
+
+    private fun setImage(typedArray: TypedArray) {
+        val imageResource = typedArray.getResourceId(R.styleable.OtherSessionsSecurityRecommendationView_otherSessionsRecommendationImageResource, 0)
+        val backgroundTint = typedArray.getColor(R.styleable.OtherSessionsSecurityRecommendationView_otherSessionsRecommendationImageBackgroundTint, 0)
+        setImageResource(imageResource)
+        setImageBackgroundTint(backgroundTint)
+    }
+
+    private fun setImageResource(resourceId: Int) {
+        views.recommendationShieldImageView.setImageResource(resourceId)
+    }
+
+    private fun setImageBackgroundTint(backgroundTintColor: Int) {
+        views.recommendationShieldImageView.backgroundTintList = ColorStateList.valueOf(backgroundTintColor)
+    }
+
+    private fun setDescription(description: String?) {
+        val learnMore = context.getString(R.string.action_learn_more)
+        val stringBuilder = StringBuilder()
+        stringBuilder.append(description)
+        stringBuilder.append(" ")
+        stringBuilder.append(learnMore)
+
+        views.recommendationDescriptionTextView.setTextWithColoredPart(
+                fullText = stringBuilder.toString(),
+                coloredPart = learnMore,
+                underline = false
+        ) {
+            onLearnMoreClickListener?.invoke()
+        }
+    }
+
+    fun render(viewState: OtherSessionsSecurityRecommendationViewState) {
+        setTitle(viewState.title)
+        setDescription(viewState.description)
+        setImageResource(viewState.imageResourceId)
+        setImageBackgroundTint(viewState.imageTintColorResourceId)
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsSecurityRecommendationViewState.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsSecurityRecommendationViewState.kt
new file mode 100644
index 0000000000..2b17cb26b3
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsSecurityRecommendationViewState.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.settings.devices.v2.othersessions
+
+data class OtherSessionsSecurityRecommendationViewState(
+        val title: String,
+        val description: String,
+        val imageResourceId: Int,
+        val imageTintColorResourceId: Int,
+)
diff --git a/vector/src/main/res/layout/fragment_other_sessions.xml b/vector/src/main/res/layout/fragment_other_sessions.xml
index 8b504ca903..ae9ca5ae50 100644
--- a/vector/src/main/res/layout/fragment_other_sessions.xml
+++ b/vector/src/main/res/layout/fragment_other_sessions.xml
@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
 
@@ -24,7 +25,8 @@
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_gravity="end"
-                android:layout_marginEnd="16dp">
+                android:padding="8dp"
+                android:layout_marginEnd="8dp">
 
                 <ImageView
                     android:layout_width="wrap_content"
@@ -47,7 +49,6 @@
     </com.google.android.material.appbar.AppBarLayout>
 
 
-
     <im.vector.app.features.settings.devices.v2.list.SessionsListHeaderView
         android:id="@+id/deviceListHeaderOtherSessions"
         android:layout_width="0dp"
@@ -56,15 +57,31 @@
         app:devicesListHeaderTitle=""
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/appBarLayout"/>
+        app:layout_constraintTop_toBottomOf="@id/appBarLayout" />
+
+    <im.vector.app.features.settings.devices.v2.othersessions.OtherSessionsSecurityRecommendationView
+        android:id="@+id/otherSessionsSecurityRecommendationView"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="16dp"
+        android:layout_marginTop="20dp"
+        android:visibility="gone"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/deviceListHeaderOtherSessions"
+        app:otherSessionsRecommendationDescription="@string/device_manager_other_sessions_recommendation_description_unverified"
+        app:otherSessionsRecommendationImageBackgroundTint="@color/shield_color_warning_background"
+        app:otherSessionsRecommendationImageResource="@drawable/ic_shield_warning_no_border"
+        app:otherSessionsRecommendationTitle="@string/device_manager_other_sessions_recommendation_title_unverified"
+        tools:visibility="visible" />
 
     <im.vector.app.features.settings.devices.v2.list.OtherSessionsView
         android:id="@+id/deviceListOtherSessions"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
-        android:layout_marginTop="16dp"
+        android:layout_marginTop="32dp"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/deviceListHeaderOtherSessions" />
+        app:layout_constraintTop_toBottomOf="@id/otherSessionsSecurityRecommendationView" />
 
 </androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/vector/src/main/res/layout/view_other_session_security_recommendation.xml b/vector/src/main/res/layout/view_other_session_security_recommendation.xml
new file mode 100644
index 0000000000..d7597aea35
--- /dev/null
+++ b/vector/src/main/res/layout/view_other_session_security_recommendation.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
+
+    <ImageView
+        android:id="@+id/recommendationShieldImageView"
+        android:layout_width="40dp"
+        android:layout_height="40dp"
+        android:background="@drawable/bg_security_recommendation_shield"
+        android:importantForAccessibility="no"
+        android:padding="8dp"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:backgroundTint="@color/shield_color_warning_background"
+        tools:src="@drawable/ic_shield_warning_no_border" />
+
+    <TextView
+        android:id="@+id/recommendationTitleTextView"
+        style="@style/TextAppearance.Vector.Subtitle.Medium.DevicesManagement"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="16dp"
+        android:layout_marginEnd="16dp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@id/recommendationShieldImageView"
+        app:layout_constraintTop_toTopOf="@id/recommendationShieldImageView"
+        app:layout_constraintBottom_toBottomOf="@id/recommendationShieldImageView"
+        tools:text="@string/device_manager_other_sessions_recommendation_title_unverified" />
+
+    <TextView
+        android:id="@+id/recommendationDescriptionTextView"
+        style="@style/TextAppearance.Vector.Body.DevicesManagement"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="8dp"
+        android:layout_marginEnd="40dp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="@id/recommendationTitleTextView"
+        app:layout_constraintTop_toBottomOf="@id/recommendationTitleTextView"
+        tools:text="@string/device_manager_other_sessions_recommendation_description_unverified" />
+
+</merge>

From 11079afa6b9717a09fdf9e6087f41cc38ebc0334 Mon Sep 17 00:00:00 2001
From: Onuray Sahin <onuray.sahin@gmail.com>
Date: Thu, 8 Sep 2022 19:25:11 +0300
Subject: [PATCH 051/108] Keep initial filter type on bottom sheet.

---
 .../filter/DeviceManagerFilterBottomSheet.kt  | 27 ++++++++++++++++---
 .../v2/othersessions/OtherSessionsFragment.kt |  8 +++---
 2 files changed, 28 insertions(+), 7 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/filter/DeviceManagerFilterBottomSheet.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/filter/DeviceManagerFilterBottomSheet.kt
index 4eee482348..4ab5acd496 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/filter/DeviceManagerFilterBottomSheet.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/filter/DeviceManagerFilterBottomSheet.kt
@@ -17,19 +17,29 @@
 package im.vector.app.features.settings.devices.v2.filter
 
 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 dagger.hilt.android.AndroidEntryPoint
 import im.vector.app.R
 import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
 import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment.ResultListener.Companion.RESULT_OK
 import im.vector.app.databinding.BottomSheetDeviceManagerFilterBinding
 import im.vector.app.features.settings.devices.v2.list.SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS
+import kotlinx.parcelize.Parcelize
+
+@Parcelize
+data class DeviceManagerFilterBottomSheetArgs(
+        val initialFilterType: DeviceManagerFilterType,
+) : Parcelable
 
 @AndroidEntryPoint
 class DeviceManagerFilterBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetDeviceManagerFilterBinding>() {
 
+    private val args: DeviceManagerFilterBottomSheetArgs by args()
+
     override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): BottomSheetDeviceManagerFilterBinding {
         return BottomSheetDeviceManagerFilterBinding.inflate(inflater, container, false)
     }
@@ -46,6 +56,14 @@ class DeviceManagerFilterBottomSheet : VectorBaseBottomSheetDialogFragment<Botto
                 SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS
         )
 
+        val radioButtonId = when (args.initialFilterType) {
+            DeviceManagerFilterType.ALL_SESSIONS -> R.id.filterOptionAllSessionsRadioButton
+            DeviceManagerFilterType.VERIFIED -> R.id.filterOptionVerifiedRadioButton
+            DeviceManagerFilterType.UNVERIFIED -> R.id.filterOptionUnverifiedRadioButton
+            DeviceManagerFilterType.INACTIVE -> R.id.filterOptionInactiveRadioButton
+        }
+        views.filterOptionsRadioGroup.check(radioButtonId)
+
         views.filterOptionsRadioGroup.setOnCheckedChangeListener { _, checkedId ->
             onFilterTypeChanged(checkedId)
         }
@@ -64,10 +82,11 @@ class DeviceManagerFilterBottomSheet : VectorBaseBottomSheetDialogFragment<Botto
     }
 
     companion object {
-        fun newInstance(resultListener: ResultListener): DeviceManagerFilterBottomSheet {
-            val bottomSheet = DeviceManagerFilterBottomSheet()
-            bottomSheet.resultListener = resultListener
-            return bottomSheet
+        fun newInstance(initialFilterType: DeviceManagerFilterType, resultListener: ResultListener): DeviceManagerFilterBottomSheet {
+            return DeviceManagerFilterBottomSheet().apply {
+                this.resultListener = resultListener
+                setArguments(DeviceManagerFilterBottomSheetArgs(initialFilterType))
+            }
         }
     }
 }
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt
index 2996de5658..8d2de8d756 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt
@@ -58,9 +58,11 @@ class OtherSessionsFragment : VectorBaseFragment<FragmentOtherSessionsBinding>()
 
     private fun initFilterView() {
         views.otherSessionsFilterFrameLayout.debouncedClicks {
-            DeviceManagerFilterBottomSheet
-                    .newInstance(this)
-                    .show(requireActivity().supportFragmentManager, "SHOW_DEVICE_MANAGER_FILTER_BOTTOM_SHEET")
+            withState(viewModel) { state ->
+                DeviceManagerFilterBottomSheet
+                        .newInstance(state.currentFilter, this)
+                        .show(requireActivity().supportFragmentManager, "SHOW_DEVICE_MANAGER_FILTER_BOTTOM_SHEET")
+            }
         }
     }
 

From 0ec67c1ab82ecfd8ffdb0f2e975f4dbb7cc073d0 Mon Sep 17 00:00:00 2001
From: Onuray Sahin <onurays@element.io>
Date: Tue, 13 Sep 2022 13:10:03 +0300
Subject: [PATCH 052/108]  Implement clear filter.

---
 .../src/main/res/values/strings.xml           |  4 +++
 .../v2/othersessions/OtherSessionsFragment.kt |  9 ++++++
 .../res/layout/fragment_other_sessions.xml    | 31 +++++++++++++++++++
 3 files changed, 44 insertions(+)

diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml
index 5eaeae4d86..b28c7c638c 100644
--- a/library/ui-strings/src/main/res/values/strings.xml
+++ b/library/ui-strings/src/main/res/values/strings.xml
@@ -3304,5 +3304,9 @@
         <item quantity="one">Consider signing out from old sessions (%1$d day or more) you don’t use anymore.</item>
         <item quantity="other">Consider signing out from old sessions (%1$d days or more) you don’t use anymore.</item>
     </plurals>
+    <string name="device_manager_other_sessions_no_verified_sessions_found">No verified sessions found.</string>
+    <string name="device_manager_other_sessions_no_unverified_sessions_found">No unverified sessions found.</string>
+    <string name="device_manager_other_sessions_no_inactive_sessions_found">No inactive sessions found.</string>
+    <string name="device_manager_other_sessions_clear_filter">Clear Filter</string>
 
 </resources>
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt
index 8d2de8d756..7fd0b755c9 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt
@@ -64,6 +64,10 @@ class OtherSessionsFragment : VectorBaseFragment<FragmentOtherSessionsBinding>()
                         .show(requireActivity().supportFragmentManager, "SHOW_DEVICE_MANAGER_FILTER_BOTTOM_SHEET")
             }
         }
+
+        views.otherSessionsClearFilterButton.debouncedClicks {
+            viewModel.handle(DevicesAction.FilterDevices(DeviceManagerFilterType.ALL_SESSIONS))
+        }
     }
 
     override fun onBottomSheetResult(resultCode: Int, data: Any?) {
@@ -98,6 +102,7 @@ class OtherSessionsFragment : VectorBaseFragment<FragmentOtherSessionsBinding>()
                                 imageTintColorResourceId = colorProvider.getColor(R.color.shield_color_trust_background)
                         )
                 )
+                views.otherSessionsNotFoundTextView.text = getString(R.string.device_manager_other_sessions_no_verified_sessions_found)
             }
             DeviceManagerFilterType.UNVERIFIED -> {
                 views.otherSessionsSecurityRecommendationView.render(
@@ -108,6 +113,7 @@ class OtherSessionsFragment : VectorBaseFragment<FragmentOtherSessionsBinding>()
                                 imageTintColorResourceId = colorProvider.getColor(R.color.shield_color_warning_background)
                         )
                 )
+                views.otherSessionsNotFoundTextView.text = getString(R.string.device_manager_other_sessions_no_unverified_sessions_found)
             }
             DeviceManagerFilterType.INACTIVE -> {
                 views.otherSessionsSecurityRecommendationView.render(
@@ -122,14 +128,17 @@ class OtherSessionsFragment : VectorBaseFragment<FragmentOtherSessionsBinding>()
                                 imageTintColorResourceId = ThemeUtils.getColor(requireContext(), R.attr.vctr_system)
                         )
                 )
+                views.otherSessionsNotFoundTextView.text = getString(R.string.device_manager_other_sessions_no_inactive_sessions_found)
             }
             DeviceManagerFilterType.ALL_SESSIONS -> { /* NOOP. View is not visible */ }
         }
 
         if (devices.isNullOrEmpty()) {
             views.deviceListOtherSessions.isVisible = false
+            views.otherSessionsNotFoundLayout.isVisible = true
         } else {
             views.deviceListOtherSessions.isVisible = true
+            views.otherSessionsNotFoundLayout.isVisible = false
             views.deviceListOtherSessions.render(devices, devices.size)
         }
     }
diff --git a/vector/src/main/res/layout/fragment_other_sessions.xml b/vector/src/main/res/layout/fragment_other_sessions.xml
index ae9ca5ae50..a6181d05f5 100644
--- a/vector/src/main/res/layout/fragment_other_sessions.xml
+++ b/vector/src/main/res/layout/fragment_other_sessions.xml
@@ -75,6 +75,37 @@
         app:otherSessionsRecommendationTitle="@string/device_manager_other_sessions_recommendation_title_unverified"
         tools:visibility="visible" />
 
+    <LinearLayout
+        android:id="@+id/otherSessionsNotFoundLayout"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="72dp"
+        android:layout_marginTop="32dp"
+        android:layout_marginEnd="16dp"
+        android:orientation="vertical"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/otherSessionsSecurityRecommendationView">
+
+        <TextView
+            android:id="@+id/otherSessionsNotFoundTextView"
+            style="@style/TextAppearance.Vector.Body"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            tools:text="@string/device_manager_other_sessions_no_verified_sessions_found" />
+
+        <Button
+            android:id="@+id/otherSessionsClearFilterButton"
+            style="@style/Widget.Vector.Button.Text"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="20dp"
+            android:gravity="start"
+            android:padding="0dp"
+            android:text="@string/device_manager_other_sessions_clear_filter" />
+
+    </LinearLayout>
+
     <im.vector.app.features.settings.devices.v2.list.OtherSessionsView
         android:id="@+id/deviceListOtherSessions"
         android:layout_width="0dp"

From 42ade670daed686713fb573007d9e345688c41f1 Mon Sep 17 00:00:00 2001
From: Onuray Sahin <onurays@element.io>
Date: Tue, 13 Sep 2022 13:47:38 +0300
Subject: [PATCH 053/108] Navigate to session details on click.

---
 .../v2/othersessions/OtherSessionsFragment.kt | 21 ++++++++++++++++++-
 1 file changed, 20 insertions(+), 1 deletion(-)

diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt
index 7fd0b755c9..3c24c466ca 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt
@@ -34,17 +34,23 @@ import im.vector.app.databinding.FragmentOtherSessionsBinding
 import im.vector.app.features.settings.devices.v2.DeviceFullInfo
 import im.vector.app.features.settings.devices.v2.DevicesAction
 import im.vector.app.features.settings.devices.v2.DevicesViewModel
+import im.vector.app.features.settings.devices.v2.VectorSettingsDevicesViewNavigator
 import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterBottomSheet
 import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
+import im.vector.app.features.settings.devices.v2.list.OtherSessionsView
 import im.vector.app.features.settings.devices.v2.list.SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS
 import im.vector.app.features.themes.ThemeUtils
 import javax.inject.Inject
 
 @AndroidEntryPoint
-class OtherSessionsFragment : VectorBaseFragment<FragmentOtherSessionsBinding>(), VectorBaseBottomSheetDialogFragment.ResultListener {
+class OtherSessionsFragment :
+        VectorBaseFragment<FragmentOtherSessionsBinding>(),
+        VectorBaseBottomSheetDialogFragment.ResultListener,
+        OtherSessionsView.Callback {
 
     private val viewModel: DevicesViewModel by fragmentViewModel()
     @Inject lateinit var colorProvider: ColorProvider
+    @Inject lateinit var viewNavigator: VectorSettingsDevicesViewNavigator
 
     override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentOtherSessionsBinding {
         return FragmentOtherSessionsBinding.inflate(layoutInflater, container, false)
@@ -68,6 +74,8 @@ class OtherSessionsFragment : VectorBaseFragment<FragmentOtherSessionsBinding>()
         views.otherSessionsClearFilterButton.debouncedClicks {
             viewModel.handle(DevicesAction.FilterDevices(DeviceManagerFilterType.ALL_SESSIONS))
         }
+
+        views.deviceListOtherSessions.callback = this
     }
 
     override fun onBottomSheetResult(resultCode: Int, data: Any?) {
@@ -142,4 +150,15 @@ class OtherSessionsFragment : VectorBaseFragment<FragmentOtherSessionsBinding>()
             views.deviceListOtherSessions.render(devices, devices.size)
         }
     }
+
+    override fun onOtherSessionClicked(deviceId: String) {
+        viewNavigator.navigateToSessionOverview(
+                context = requireActivity(),
+                deviceId = deviceId
+        )
+    }
+
+    override fun onViewAllOtherSessionsClicked() {
+        // NOOP. We don't have this button in this screen
+    }
 }

From 9a651b223b6c6b9e6ccfe643c49e7ae084bf957e Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoit@matrix.org>
Date: Tue, 13 Sep 2022 13:18:18 +0200
Subject: [PATCH 054/108] Use `buildjet-4vcpu-ubuntu-2204` runner instead of
 `macos-latest` to build and run the integration tests.

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

diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index cd7e26f3cf..8a0d320bbf 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -13,7 +13,7 @@ env:
 jobs:
   tests:
     name: Runs all tests
-    runs-on: macos-latest # for the emulator
+    runs-on: buildjet-4vcpu-ubuntu-2204 # for the emulator
     # Allow all jobs on main and develop. Just one per PR.
     concurrency:
       group: ${{ github.ref == 'refs/heads/main' && format('unit-tests-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('unit-tests-develop-{0}', github.sha) || format('unit-tests-{0}', github.ref) }}

From 2e8b6e4eb9a7576ce51058b593033b288f2053d8 Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoit@matrix.org>
Date: Tue, 13 Sep 2022 14:40:26 +0200
Subject: [PATCH 055/108] typo

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

diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 8a0d320bbf..148a026b84 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -53,7 +53,7 @@ jobs:
             ./gradlew unitTestsWithCoverage $CI_GRADLE_ARG_PROPERTIES
             ./gradlew instrumentationTestsWithCoverage $CI_GRADLE_ARG_PROPERTIES
             ./gradlew generateCoverageReport $CI_GRADLE_ARG_PROPERTIES
-      # NB: continue-on-error marks steps.tests.conclusion = 'success' but leaves stes.tests.outcome = 'failure'
+      # NB: continue-on-error marks steps.tests.conclusion = 'success' but leaves steps.tests.outcome = 'failure'
       - name: Run all the codecoverage tests at once (retry if emulator failed)
         uses: reactivecircus/android-emulator-runner@v2
         if: always() && steps.tests.outcome == 'failure' # don't run if previous step succeeded.

From 6ac9a7627b451c84760b1ebf463f0ace34b693ff Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoit@matrix.org>
Date: Tue, 13 Sep 2022 14:42:39 +0200
Subject: [PATCH 056/108] Disable 2nd attempt to run the tests.

---
 .github/workflows/tests.yml | 32 ++++++++++++++++----------------
 1 file changed, 16 insertions(+), 16 deletions(-)

diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 148a026b84..ffffb2b760 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -54,22 +54,22 @@ jobs:
             ./gradlew instrumentationTestsWithCoverage $CI_GRADLE_ARG_PROPERTIES
             ./gradlew generateCoverageReport $CI_GRADLE_ARG_PROPERTIES
       # NB: continue-on-error marks steps.tests.conclusion = 'success' but leaves steps.tests.outcome = 'failure'
-      - name: Run all the codecoverage tests at once (retry if emulator failed)
-        uses: reactivecircus/android-emulator-runner@v2
-        if: always() && steps.tests.outcome == 'failure' # don't run if previous step succeeded.
-        with:
-          api-level: 28
-          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
-          disable-animations: true
-          emulator-build: 7425822
-          script: |
-            ./gradlew gatherGplayDebugStringTemplates $CI_GRADLE_ARG_PROPERTIES
-            ./gradlew unitTestsWithCoverage $CI_GRADLE_ARG_PROPERTIES
-            ./gradlew instrumentationTestsWithCoverage $CI_GRADLE_ARG_PROPERTIES
-            ./gradlew generateCoverageReport $CI_GRADLE_ARG_PROPERTIES
+      ### - name: Run all the codecoverage tests at once (retry if emulator failed)
+      ###   uses: reactivecircus/android-emulator-runner@v2
+      ###   if: always() && steps.tests.outcome == 'failure' # don't run if previous step succeeded.
+      ###   with:
+      ###     api-level: 28
+      ###     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
+      ###     disable-animations: true
+      ###     emulator-build: 7425822
+      ###     script: |
+      ###       ./gradlew gatherGplayDebugStringTemplates $CI_GRADLE_ARG_PROPERTIES
+      ###       ./gradlew unitTestsWithCoverage $CI_GRADLE_ARG_PROPERTIES
+      ###       ./gradlew instrumentationTestsWithCoverage $CI_GRADLE_ARG_PROPERTIES
+      ###       ./gradlew generateCoverageReport $CI_GRADLE_ARG_PROPERTIES
 
       # we may have failed a previous step and retried, that's OK
       - name: Publish results to Sonar

From 1afe0981a68ba472ccad834791dde9281a6750d4 Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoit@matrix.org>
Date: Tue, 13 Sep 2022 14:47:35 +0200
Subject: [PATCH 057/108] Use `buildjet-4vcpu-ubuntu-2204` runner instead of
 `macos-latest` to build and run the integration tests for the post merge
 task.

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

diff --git a/.github/workflows/post-pr.yml b/.github/workflows/post-pr.yml
index 5cde95e625..bf948064ed 100644
--- a/.github/workflows/post-pr.yml
+++ b/.github/workflows/post-pr.yml
@@ -31,7 +31,7 @@ jobs:
   ui-tests:
     name: UI Tests (Synapse)
     needs: should-i-run
-    runs-on: macos-latest
+    runs-on: buildjet-4vcpu-ubuntu-2204
     strategy:
       fail-fast: false
       matrix:

From b5c6f60ee61c5c38bdd63d47583539d3c330f534 Mon Sep 17 00:00:00 2001
From: Onuray Sahin <onurays@element.io>
Date: Tue, 13 Sep 2022 16:35:30 +0300
Subject: [PATCH 058/108] Scroll to top on filter type changed.

---
 .../v2/VectorSettingsDevicesFragment.kt       |  6 ++++-
 .../devices/v2/list/OtherSessionsView.kt      | 22 +++++++++++++++++--
 .../v2/othersessions/OtherSessionsFragment.kt |  2 +-
 .../res/layout/fragment_other_sessions.xml    |  7 +++---
 4 files changed, 30 insertions(+), 7 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt
index 33c0a7ca74..ab918312a5 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesFragment.kt
@@ -199,7 +199,11 @@ class VectorSettingsDevicesFragment :
         } else {
             views.deviceListHeaderOtherSessions.isVisible = true
             views.deviceListOtherSessions.isVisible = true
-            views.deviceListOtherSessions.render(otherDevices.take(NUMBER_OF_OTHER_DEVICES_TO_RENDER), otherDevices.size)
+            views.deviceListOtherSessions.render(
+                    devices = otherDevices.take(NUMBER_OF_OTHER_DEVICES_TO_RENDER),
+                    totalNumberOfDevices = otherDevices.size,
+                    showViewAll = otherDevices.size > NUMBER_OF_OTHER_DEVICES_TO_RENDER
+            )
         }
     }
 
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsView.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsView.kt
index c6eccc94b6..865bc5d209 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsView.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsView.kt
@@ -19,6 +19,8 @@ package im.vector.app.features.settings.devices.v2.list
 import android.content.Context
 import android.util.AttributeSet
 import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.view.isVisible
+import androidx.recyclerview.widget.RecyclerView
 import dagger.hilt.android.AndroidEntryPoint
 import im.vector.app.R
 import im.vector.app.core.extensions.cleanup
@@ -42,8 +44,10 @@ class OtherSessionsView @JvmOverloads constructor(
     @Inject lateinit var otherSessionsController: OtherSessionsController
 
     private val views: ViewOtherSessionsBinding
+    private val recyclerViewDataObserver: RecyclerView.AdapterDataObserver
     var callback: Callback? = null
 
+
     init {
         inflate(context, R.layout.view_other_sessions, this)
         views = ViewOtherSessionsBinding.bind(this)
@@ -53,16 +57,30 @@ class OtherSessionsView @JvmOverloads constructor(
         views.otherSessionsViewAllButton.setOnClickListener {
             callback?.onViewAllOtherSessionsClicked()
         }
+
+        recyclerViewDataObserver = object : RecyclerView.AdapterDataObserver() {
+            override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
+                super.onItemRangeInserted(positionStart, itemCount)
+                views.otherSessionsRecyclerView.scrollToPosition(0)
+            }
+        }
+        otherSessionsController.adapter.registerAdapterDataObserver(recyclerViewDataObserver)
     }
 
-    fun render(devices: List<DeviceFullInfo>, totalNumberOfDevices: Int) {
+    fun render(devices: List<DeviceFullInfo>, totalNumberOfDevices: Int, showViewAll: Boolean) {
         views.otherSessionsRecyclerView.configureWith(otherSessionsController, hasFixedSize = true)
-        views.otherSessionsViewAllButton.text = context.getString(R.string.device_manager_other_sessions_view_all, totalNumberOfDevices)
+        if (showViewAll) {
+            views.otherSessionsViewAllButton.isVisible = true
+            views.otherSessionsViewAllButton.text = context.getString(R.string.device_manager_other_sessions_view_all, totalNumberOfDevices)
+        } else {
+            views.otherSessionsViewAllButton.isVisible = false
+        }
         otherSessionsController.setData(devices)
     }
 
     override fun onDetachedFromWindow() {
         otherSessionsController.callback = null
+        otherSessionsController.adapter.unregisterAdapterDataObserver(recyclerViewDataObserver)
         views.otherSessionsRecyclerView.cleanup()
         super.onDetachedFromWindow()
     }
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt
index 3c24c466ca..b582d7952c 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt
@@ -147,7 +147,7 @@ class OtherSessionsFragment :
         } else {
             views.deviceListOtherSessions.isVisible = true
             views.otherSessionsNotFoundLayout.isVisible = false
-            views.deviceListOtherSessions.render(devices, devices.size)
+            views.deviceListOtherSessions.render(devices = devices, totalNumberOfDevices = devices.size, showViewAll = false)
         }
     }
 
diff --git a/vector/src/main/res/layout/fragment_other_sessions.xml b/vector/src/main/res/layout/fragment_other_sessions.xml
index a6181d05f5..df2bf0cce4 100644
--- a/vector/src/main/res/layout/fragment_other_sessions.xml
+++ b/vector/src/main/res/layout/fragment_other_sessions.xml
@@ -25,8 +25,8 @@
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_gravity="end"
-                android:padding="8dp"
-                android:layout_marginEnd="8dp">
+                android:layout_marginEnd="8dp"
+                android:padding="8dp">
 
                 <ImageView
                     android:layout_width="wrap_content"
@@ -109,8 +109,9 @@
     <im.vector.app.features.settings.devices.v2.list.OtherSessionsView
         android:id="@+id/deviceListOtherSessions"
         android:layout_width="0dp"
-        android:layout_height="wrap_content"
+        android:layout_height="0dp"
         android:layout_marginTop="32dp"
+        app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toBottomOf="@id/otherSessionsSecurityRecommendationView" />

From 81cc8ab98bf5d301068586a15bc6b9eda5bc7b56 Mon Sep 17 00:00:00 2001
From: Onuray Sahin <onurays@element.io>
Date: Thu, 15 Sep 2022 15:14:46 +0300
Subject: [PATCH 059/108] Code review fixes.

---
 changelog.d/7043.wip                          |   1 -
 .../src/main/res/values/strings.xml           |  53 ++++---
 .../app/core/di/MavericksViewModelModule.kt   |   6 +
 .../settings/devices/v2/DevicesAction.kt      |   2 -
 .../settings/devices/v2/DevicesViewModel.kt   |  16 +--
 .../settings/devices/v2/DevicesViewState.kt   |  17 +--
 .../v2/GetDeviceFullInfoListUseCase.kt        |  13 +-
 .../filter/DeviceManagerFilterBottomSheet.kt  |  12 +-
 .../devices/v2/filter/FilterDevicesUseCase.kt |  41 ++++++
 .../devices/v2/list/OtherSessionsView.kt      |  26 +++-
 .../v2/othersessions/OtherSessionsAction.kt   |  24 ++++
 .../v2/othersessions/OtherSessionsFragment.kt |  30 ++--
 ...OtherSessionsSecurityRecommendationView.kt |  13 +-
 .../othersessions/OtherSessionsViewEvents.kt  |  24 ++++
 .../othersessions/OtherSessionsViewModel.kt   | 135 ++++++++++++++++++
 .../OtherSessionsViewNavigator.kt             |  28 ++++
 .../othersessions/OtherSessionsViewState.kt   |  28 ++++
 .../bottom_sheet_device_manager_filter.xml    |   4 +-
 .../res/layout/fragment_other_sessions.xml    |   2 +-
 19 files changed, 391 insertions(+), 84 deletions(-)
 delete mode 100644 changelog.d/7043.wip
 create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/filter/FilterDevicesUseCase.kt
 create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsAction.kt
 create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewEvents.kt
 create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt
 create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewNavigator.kt
 create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewState.kt

diff --git a/changelog.d/7043.wip b/changelog.d/7043.wip
deleted file mode 100644
index 3c9b7731bf..0000000000
--- a/changelog.d/7043.wip
+++ /dev/null
@@ -1 +0,0 @@
-[Devices Management] Refactor some code to improve testability
diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml
index cff1aaea1b..2731ba8837 100644
--- a/library/ui-strings/src/main/res/values/strings.xml
+++ b/library/ui-strings/src/main/res/values/strings.xml
@@ -3265,6 +3265,32 @@
     <string name="device_manager_session_title">Session</string>
     <!--  Examples: Last activity Yesterday at 6PM, Last activity Aug 31 at 5:47PM -->
     <string name="device_manager_session_last_activity">Last activity %1$s</string>
+    <string name="device_manager_filter_bottom_sheet_title">Filter</string>
+    <string name="device_manager_filter_option_all_sessions">All sessions</string>
+    <string name="device_manager_filter_option_verified">Verified</string>
+    <string name="device_manager_filter_option_verified_description">Ready for secure messaging</string>
+    <string name="device_manager_filter_option_unverified">Unverified</string>
+    <string name="device_manager_filter_option_unverified_description">Not ready for secure messaging</string>
+    <string name="device_manager_filter_option_inactive">Inactive</string>
+    <plurals name="device_manager_filter_option_inactive_description">
+        <item quantity="one">Inactive for %1$d day or longer</item>
+        <item quantity="other">Inactive for %1$d days or longer</item>
+    </plurals>
+    <string name="a11y_device_manager_filter">Filter</string>
+    <string name="device_manager_other_sessions_recommendation_title_verified">Verified</string>
+    <string name="device_manager_other_sessions_recommendation_description_verified">For best security, sign out from any session that you don’t recognize or use anymore.</string>
+    <string name="device_manager_other_sessions_recommendation_title_unverified">Unverified</string>
+    <string name="device_manager_other_sessions_recommendation_description_unverified">Verify your sessions for enhanced secure messaging or sign out from those you don’t recognize or use anymore.</string>
+    <string name="device_manager_other_sessions_recommendation_title_inactive">Inactive</string>
+    <plurals name="device_manager_other_sessions_recommendation_description_inactive">
+        <item quantity="one">Consider signing out from old sessions (%1$d day or more) you don’t use anymore.</item>
+        <item quantity="other">Consider signing out from old sessions (%1$d days or more) you don’t use anymore.</item>
+    </plurals>
+    <string name="device_manager_other_sessions_no_verified_sessions_found">No verified sessions found.</string>
+    <string name="device_manager_other_sessions_no_unverified_sessions_found">No unverified sessions found.</string>
+    <string name="device_manager_other_sessions_no_inactive_sessions_found">No inactive sessions found.</string>
+    <string name="device_manager_other_sessions_clear_filter">Clear Filter</string>
+
     <!-- Note to translators: %s will be replaces with selected space name -->
     <string name="home_empty_space_no_rooms_title">%s\nis looking a little empty.</string>
     <!-- Note to translators: for RTL languages, Spaces will be at the bottom left. Please translate "bottom-left" instead of "bottom-right". Thanks!-->
@@ -3286,31 +3312,4 @@
     <string name="onboarding_new_app_layout_feedback_message">Tap top right to see the option to feedback.</string>
     <string name="onboarding_new_app_layout_button_try">Try it out</string>
 
-    <string name="device_manager_filter_bottom_sheet_title">Filter</string>
-    <string name="device_manager_filter_option_all_sessions">All session</string>
-    <string name="device_manager_filter_option_verified">Verified</string>
-    <string name="device_manager_filter_option_verified_description">Ready for secure messaging</string>
-    <string name="device_manager_filter_option_unverified">Unverified</string>
-    <string name="device_manager_filter_option_unverified_description">Not ready for secure messaging</string>
-    <string name="device_manager_filter_option_inactive">Inactive</string>
-    <plurals name="device_manager_filter_option_inactive_description">
-        <item quantity="one">Inactive for %1$d day or longer</item>
-        <item quantity="other">Inactive for %1$d days or longer</item>
-    </plurals>
-    <string name="device_manager_other_sessions_title">Other sessions</string>
-    <string name="a11y_device_manager_filter">Filter</string>
-    <string name="device_manager_other_sessions_recommendation_title_verified">Verified</string>
-    <string name="device_manager_other_sessions_recommendation_description_verified">For best security, sign out from any session that you don’t recognize or use anymore.</string>
-    <string name="device_manager_other_sessions_recommendation_title_unverified">Unverified</string>
-    <string name="device_manager_other_sessions_recommendation_description_unverified">Verify your sessions for enhanced secure messaging or sign out from those you don’t recognize or use anymore.</string>
-    <string name="device_manager_other_sessions_recommendation_title_inactive">Inactive</string>
-    <plurals name="device_manager_other_sessions_recommendation_description_inactive">
-        <item quantity="one">Consider signing out from old sessions (%1$d day or more) you don’t use anymore.</item>
-        <item quantity="other">Consider signing out from old sessions (%1$d days or more) you don’t use anymore.</item>
-    </plurals>
-    <string name="device_manager_other_sessions_no_verified_sessions_found">No verified sessions found.</string>
-    <string name="device_manager_other_sessions_no_unverified_sessions_found">No unverified sessions found.</string>
-    <string name="device_manager_other_sessions_no_inactive_sessions_found">No inactive sessions found.</string>
-    <string name="device_manager_other_sessions_clear_filter">Clear Filter</string>
-
 </resources>
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 8bcfd4e422..21016077a1 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
@@ -88,6 +88,7 @@ import im.vector.app.features.settings.account.deactivation.DeactivateAccountVie
 import im.vector.app.features.settings.crosssigning.CrossSigningSettingsViewModel
 import im.vector.app.features.settings.devices.DeviceVerificationInfoBottomSheetViewModel
 import im.vector.app.features.settings.devices.DevicesViewModel
+import im.vector.app.features.settings.devices.v2.othersessions.OtherSessionsViewModel
 import im.vector.app.features.settings.devices.v2.overview.SessionOverviewViewModel
 import im.vector.app.features.settings.devtools.AccountDataViewModel
 import im.vector.app.features.settings.devtools.GossipingEventsPaperTrailViewModel
@@ -641,4 +642,9 @@ interface MavericksViewModelModule {
     @IntoMap
     @MavericksViewModelKey(SessionOverviewViewModel::class)
     fun sessionOverviewViewModelFactory(factory: SessionOverviewViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
+
+    @Binds
+    @IntoMap
+    @MavericksViewModelKey(OtherSessionsViewModel::class)
+    fun otherSessionsViewModelFactory(factory: OtherSessionsViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
 }
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesAction.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesAction.kt
index 3c459ca992..8c7718bfcf 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesAction.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesAction.kt
@@ -17,10 +17,8 @@
 package im.vector.app.features.settings.devices.v2
 
 import im.vector.app.core.platform.VectorViewModelAction
-import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
 import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
 
 sealed class DevicesAction : VectorViewModelAction {
     data class MarkAsManuallyVerified(val cryptoDeviceInfo: CryptoDeviceInfo) : DevicesAction()
-    data class FilterDevices(val filterType: DeviceManagerFilterType) : DevicesAction()
 }
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt
index 4bdadda815..99afc33a8a 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt
@@ -26,6 +26,7 @@ import im.vector.app.core.di.MavericksAssistedViewModelFactory
 import im.vector.app.core.di.hiltMavericksViewModelFactory
 import im.vector.app.core.platform.VectorViewModel
 import im.vector.app.core.utils.PublishDataSource
+import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
 import im.vector.lib.core.utils.flow.throttleFirst
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
@@ -94,7 +95,10 @@ class DevicesViewModel @AssistedInject constructor(
     }
 
     private fun observeDevices() {
-        getDeviceFullInfoListUseCase.execute()
+        getDeviceFullInfoListUseCase.execute(
+                filterType = DeviceManagerFilterType.ALL_SESSIONS,
+                excludeCurrentDevice = false
+        )
                 .execute { async ->
                     if (async is Success) {
                         val deviceFullInfoList = async.invoke()
@@ -144,19 +148,9 @@ class DevicesViewModel @AssistedInject constructor(
     override fun handle(action: DevicesAction) {
         when (action) {
             is DevicesAction.MarkAsManuallyVerified -> handleMarkAsManuallyVerifiedAction()
-            is DevicesAction.FilterDevices -> handleFilterDevices(action)
         }
     }
 
-    private fun handleFilterDevices(action: DevicesAction.FilterDevices) {
-        setState {
-            copy(
-                    currentFilter = action.filterType
-            )
-        }
-        queryRefreshDevicesList()
-    }
-
     private fun handleMarkAsManuallyVerifiedAction() {
         // TODO implement when needed
     }
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewState.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewState.kt
index 5ca3e71a06..3fc061daa4 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewState.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewState.kt
@@ -19,8 +19,6 @@ package im.vector.app.features.settings.devices.v2
 import com.airbnb.mvrx.Async
 import com.airbnb.mvrx.MavericksState
 import com.airbnb.mvrx.Uninitialized
-import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
-import org.matrix.android.sdk.api.extensions.orFalse
 
 data class DevicesViewState(
         val currentSessionCrossSigningInfo: CurrentSessionCrossSigningInfo = CurrentSessionCrossSigningInfo(),
@@ -28,17 +26,4 @@ data class DevicesViewState(
         val unverifiedSessionsCount: Int = 0,
         val inactiveSessionsCount: Int = 0,
         val isLoading: Boolean = false,
-        val currentFilter: DeviceManagerFilterType = DeviceManagerFilterType.ALL_SESSIONS,
-) : MavericksState {
-
-    fun List<DeviceFullInfo>?.filteredDevices(): List<DeviceFullInfo>? {
-        return this?.filter {
-            when (currentFilter) {
-                DeviceManagerFilterType.ALL_SESSIONS -> true
-                DeviceManagerFilterType.VERIFIED -> it.cryptoDeviceInfo?.isVerified.orFalse()
-                DeviceManagerFilterType.UNVERIFIED -> !it.cryptoDeviceInfo?.isVerified.orFalse()
-                DeviceManagerFilterType.INACTIVE -> it.isInactive
-            }
-        }
-    }
-}
+) : MavericksState
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/GetDeviceFullInfoListUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/GetDeviceFullInfoListUseCase.kt
index da2cf25f39..3c0d3a5e56 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/GetDeviceFullInfoListUseCase.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/GetDeviceFullInfoListUseCase.kt
@@ -17,6 +17,8 @@
 package im.vector.app.features.settings.devices.v2
 
 import im.vector.app.core.di.ActiveSessionHolder
+import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
+import im.vector.app.features.settings.devices.v2.filter.FilterDevicesUseCase
 import im.vector.app.features.settings.devices.v2.list.CheckIfSessionIsInactiveUseCase
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
@@ -32,16 +34,23 @@ class GetDeviceFullInfoListUseCase @Inject constructor(
         private val checkIfSessionIsInactiveUseCase: CheckIfSessionIsInactiveUseCase,
         private val getEncryptionTrustLevelForDeviceUseCase: GetEncryptionTrustLevelForDeviceUseCase,
         private val getCurrentSessionCrossSigningInfoUseCase: GetCurrentSessionCrossSigningInfoUseCase,
+        private val filterDevicesUseCase: FilterDevicesUseCase,
 ) {
 
-    fun execute(): Flow<List<DeviceFullInfo>> {
+    fun execute(filterType: DeviceManagerFilterType, excludeCurrentDevice: Boolean = false): Flow<List<DeviceFullInfo>> {
         return activeSessionHolder.getSafeActiveSession()?.let { session ->
             val deviceFullInfoFlow = combine(
                     getCurrentSessionCrossSigningInfoUseCase.execute(),
                     session.flow().liveUserCryptoDevices(session.myUserId),
                     session.flow().liveMyDevicesInfo()
             ) { currentSessionCrossSigningInfo, cryptoList, infoList ->
-                convertToDeviceFullInfoList(currentSessionCrossSigningInfo, cryptoList, infoList)
+                val deviceFullInfoList = convertToDeviceFullInfoList(currentSessionCrossSigningInfo, cryptoList, infoList)
+                val excludedDeviceIds = if (excludeCurrentDevice) {
+                    listOf(currentSessionCrossSigningInfo.deviceId)
+                } else {
+                    emptyList()
+                }
+                filterDevicesUseCase.execute(deviceFullInfoList, filterType, excludedDeviceIds)
             }
 
             deviceFullInfoFlow.distinctUntilChanged()
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/filter/DeviceManagerFilterBottomSheet.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/filter/DeviceManagerFilterBottomSheet.kt
index 4ab5acd496..28c7045a82 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/filter/DeviceManagerFilterBottomSheet.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/filter/DeviceManagerFilterBottomSheet.kt
@@ -50,7 +50,7 @@ class DeviceManagerFilterBottomSheet : VectorBaseBottomSheetDialogFragment<Botto
     }
 
     private fun initFilterRadioGroup() {
-        views.filterOptionInactiveRadioButtonDescription.text = resources.getQuantityString(
+        views.filterOptionInactiveTextView.text = resources.getQuantityString(
                 R.plurals.device_manager_filter_option_inactive_description,
                 SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS,
                 SESSION_IS_MARKED_AS_INACTIVE_AFTER_DAYS
@@ -64,6 +64,16 @@ class DeviceManagerFilterBottomSheet : VectorBaseBottomSheetDialogFragment<Botto
         }
         views.filterOptionsRadioGroup.check(radioButtonId)
 
+        views.filterOptionVerifiedTextView.debouncedClicks {
+            views.filterOptionsRadioGroup.check(R.id.filterOptionVerifiedRadioButton)
+        }
+        views.filterOptionUnverifiedTextView.debouncedClicks {
+            views.filterOptionsRadioGroup.check(R.id.filterOptionUnverifiedRadioButton)
+        }
+        views.filterOptionInactiveTextView.debouncedClicks {
+            views.filterOptionsRadioGroup.check(R.id.filterOptionInactiveRadioButton)
+        }
+
         views.filterOptionsRadioGroup.setOnCheckedChangeListener { _, checkedId ->
             onFilterTypeChanged(checkedId)
         }
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/filter/FilterDevicesUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/filter/FilterDevicesUseCase.kt
new file mode 100644
index 0000000000..e0bb567dc6
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/filter/FilterDevicesUseCase.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.settings.devices.v2.filter
+
+import im.vector.app.features.settings.devices.v2.DeviceFullInfo
+import org.matrix.android.sdk.api.extensions.orFalse
+import javax.inject.Inject
+
+class FilterDevicesUseCase @Inject constructor() {
+
+    fun execute(
+            devices: List<DeviceFullInfo>,
+            filterType: DeviceManagerFilterType,
+            excludedDeviceIds: List<String> = emptyList(),
+    ): List<DeviceFullInfo> {
+        return devices
+                .filter {
+                    when (filterType) {
+                        DeviceManagerFilterType.ALL_SESSIONS -> true
+                        DeviceManagerFilterType.VERIFIED -> it.cryptoDeviceInfo?.isVerified.orFalse()
+                        DeviceManagerFilterType.UNVERIFIED -> !it.cryptoDeviceInfo?.isVerified.orFalse()
+                        DeviceManagerFilterType.INACTIVE -> it.isInactive
+                    }
+                }
+                .filter { it.deviceInfo.deviceId !in excludedDeviceIds }
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsView.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsView.kt
index 8e8de69fac..6f6956c885 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsView.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/OtherSessionsView.kt
@@ -20,9 +20,12 @@ import android.content.Context
 import android.util.AttributeSet
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.core.view.isVisible
+import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
+import com.airbnb.epoxy.OnModelBuildFinishedListener
 import dagger.hilt.android.AndroidEntryPoint
 import im.vector.app.R
+import im.vector.app.core.epoxy.LayoutManagerStateRestorer
 import im.vector.app.core.extensions.cleanup
 import im.vector.app.core.extensions.configureWith
 import im.vector.app.databinding.ViewOtherSessionsBinding
@@ -44,18 +47,32 @@ class OtherSessionsView @JvmOverloads constructor(
     @Inject lateinit var otherSessionsController: OtherSessionsController
 
     private val views: ViewOtherSessionsBinding
-    private val recyclerViewDataObserver: RecyclerView.AdapterDataObserver
+    private lateinit var recyclerViewDataObserver: RecyclerView.AdapterDataObserver
+    private lateinit var stateRestorer: LayoutManagerStateRestorer
+    private var modelBuildListener: OnModelBuildFinishedListener? = null
+
     var callback: Callback? = null
 
     init {
         inflate(context, R.layout.view_other_sessions, this)
         views = ViewOtherSessionsBinding.bind(this)
 
-        otherSessionsController.callback = this
+        configureOtherSessionsRecyclerView()
 
         views.otherSessionsViewAllButton.setOnClickListener {
             callback?.onViewAllOtherSessionsClicked()
         }
+    }
+
+    private fun configureOtherSessionsRecyclerView() {
+        views.otherSessionsRecyclerView.configureWith(otherSessionsController, hasFixedSize = false)
+
+        val layoutManager = LinearLayoutManager(context)
+        stateRestorer = LayoutManagerStateRestorer(layoutManager)
+        views.otherSessionsRecyclerView.layoutManager = layoutManager
+        layoutManager.recycleChildrenOnDetach = true
+        modelBuildListener = OnModelBuildFinishedListener { it.dispatchTo(stateRestorer) }
+        otherSessionsController.addModelBuildListener(modelBuildListener)
 
         recyclerViewDataObserver = object : RecyclerView.AdapterDataObserver() {
             override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
@@ -64,10 +81,11 @@ class OtherSessionsView @JvmOverloads constructor(
             }
         }
         otherSessionsController.adapter.registerAdapterDataObserver(recyclerViewDataObserver)
+
+        otherSessionsController.callback = this
     }
 
     fun render(devices: List<DeviceFullInfo>, totalNumberOfDevices: Int, showViewAll: Boolean) {
-        views.otherSessionsRecyclerView.configureWith(otherSessionsController, hasFixedSize = true)
         if (showViewAll) {
             views.otherSessionsViewAllButton.isVisible = true
             views.otherSessionsViewAllButton.text = context.getString(R.string.device_manager_other_sessions_view_all, totalNumberOfDevices)
@@ -78,6 +96,8 @@ class OtherSessionsView @JvmOverloads constructor(
     }
 
     override fun onDetachedFromWindow() {
+        otherSessionsController.removeModelBuildListener(modelBuildListener)
+        modelBuildListener = null
         otherSessionsController.callback = null
         otherSessionsController.adapter.unregisterAdapterDataObserver(recyclerViewDataObserver)
         views.otherSessionsRecyclerView.cleanup()
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsAction.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsAction.kt
new file mode 100644
index 0000000000..7164ecc866
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsAction.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.settings.devices.v2.othersessions
+
+import im.vector.app.core.platform.VectorViewModelAction
+import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
+
+sealed class OtherSessionsAction : VectorViewModelAction {
+    data class FilterDevices(val filterType: DeviceManagerFilterType) : OtherSessionsAction()
+}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt
index b582d7952c..81ea5f4b89 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsFragment.kt
@@ -32,9 +32,6 @@ import im.vector.app.core.platform.VectorBaseFragment
 import im.vector.app.core.resources.ColorProvider
 import im.vector.app.databinding.FragmentOtherSessionsBinding
 import im.vector.app.features.settings.devices.v2.DeviceFullInfo
-import im.vector.app.features.settings.devices.v2.DevicesAction
-import im.vector.app.features.settings.devices.v2.DevicesViewModel
-import im.vector.app.features.settings.devices.v2.VectorSettingsDevicesViewNavigator
 import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterBottomSheet
 import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
 import im.vector.app.features.settings.devices.v2.list.OtherSessionsView
@@ -48,9 +45,11 @@ class OtherSessionsFragment :
         VectorBaseBottomSheetDialogFragment.ResultListener,
         OtherSessionsView.Callback {
 
-    private val viewModel: DevicesViewModel by fragmentViewModel()
+    private val viewModel: OtherSessionsViewModel by fragmentViewModel()
+
     @Inject lateinit var colorProvider: ColorProvider
-    @Inject lateinit var viewNavigator: VectorSettingsDevicesViewNavigator
+
+    @Inject lateinit var viewNavigator: OtherSessionsViewNavigator
 
     override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentOtherSessionsBinding {
         return FragmentOtherSessionsBinding.inflate(layoutInflater, container, false)
@@ -59,9 +58,19 @@ class OtherSessionsFragment :
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
         setupToolbar(views.otherSessionsToolbar).allowBack()
+        observeViewEvents()
         initFilterView()
     }
 
+    private fun observeViewEvents() {
+        viewModel.observeViewEvents {
+            when (it) {
+                is OtherSessionsViewEvents.Loading -> showLoading(it.message)
+                is OtherSessionsViewEvents.Failure -> showFailure(it.throwable)
+            }
+        }
+    }
+
     private fun initFilterView() {
         views.otherSessionsFilterFrameLayout.debouncedClicks {
             withState(viewModel) { state ->
@@ -72,7 +81,7 @@ class OtherSessionsFragment :
         }
 
         views.otherSessionsClearFilterButton.debouncedClicks {
-            viewModel.handle(DevicesAction.FilterDevices(DeviceManagerFilterType.ALL_SESSIONS))
+            viewModel.handle(OtherSessionsAction.FilterDevices(DeviceManagerFilterType.ALL_SESSIONS))
         }
 
         views.deviceListOtherSessions.callback = this
@@ -80,18 +89,13 @@ class OtherSessionsFragment :
 
     override fun onBottomSheetResult(resultCode: Int, data: Any?) {
         if (resultCode == RESULT_OK && data != null && data is DeviceManagerFilterType) {
-            viewModel.handle(DevicesAction.FilterDevices(data))
+            viewModel.handle(OtherSessionsAction.FilterDevices(data))
         }
     }
 
     override fun invalidate() = withState(viewModel) { state ->
         if (state.devices is Success) {
-            with(state) {
-                val devices = state.devices()
-                        ?.filter { it.deviceInfo.deviceId != state.currentSessionCrossSigningInfo.deviceId }
-                        ?.filteredDevices()
-                renderDevices(devices, state.currentFilter)
-            }
+            renderDevices(state.devices(), state.currentFilter)
         }
     }
 
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsSecurityRecommendationView.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsSecurityRecommendationView.kt
index c72dc30a93..5a7d1fa910 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsSecurityRecommendationView.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsSecurityRecommendationView.kt
@@ -28,7 +28,7 @@ import im.vector.app.core.extensions.setTextWithColoredPart
 import im.vector.app.databinding.ViewOtherSessionSecurityRecommendationBinding
 
 @AndroidEntryPoint
-class OtherSessionsSecurityRecommendationView  @JvmOverloads constructor(
+class OtherSessionsSecurityRecommendationView @JvmOverloads constructor(
         context: Context,
         attrs: AttributeSet? = null,
         defStyleAttr: Int = 0
@@ -84,13 +84,14 @@ class OtherSessionsSecurityRecommendationView  @JvmOverloads constructor(
 
     private fun setDescription(description: String?) {
         val learnMore = context.getString(R.string.action_learn_more)
-        val stringBuilder = StringBuilder()
-        stringBuilder.append(description)
-        stringBuilder.append(" ")
-        stringBuilder.append(learnMore)
+        val formattedDescription = buildString {
+            append(description)
+            append(" ")
+            append(learnMore)
+        }
 
         views.recommendationDescriptionTextView.setTextWithColoredPart(
-                fullText = stringBuilder.toString(),
+                fullText = formattedDescription,
                 coloredPart = learnMore,
                 underline = false
         ) {
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewEvents.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewEvents.kt
new file mode 100644
index 0000000000..95f9c72b33
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewEvents.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.settings.devices.v2.othersessions
+
+import im.vector.app.core.platform.VectorViewEvents
+
+sealed class OtherSessionsViewEvents : VectorViewEvents {
+    data class Loading(val message: CharSequence? = null) : OtherSessionsViewEvents()
+    data class Failure(val throwable: Throwable) : OtherSessionsViewEvents()
+}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt
new file mode 100644
index 0000000000..4a7f911ff4
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.settings.devices.v2.othersessions
+
+import com.airbnb.mvrx.MavericksViewModelFactory
+import com.airbnb.mvrx.Success
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import im.vector.app.core.di.ActiveSessionHolder
+import im.vector.app.core.di.MavericksAssistedViewModelFactory
+import im.vector.app.core.di.hiltMavericksViewModelFactory
+import im.vector.app.core.platform.VectorViewModel
+import im.vector.app.core.utils.PublishDataSource
+import im.vector.app.features.settings.devices.v2.GetDeviceFullInfoListUseCase
+import im.vector.app.features.settings.devices.v2.RefreshDevicesUseCase
+import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
+import im.vector.lib.core.utils.flow.throttleFirst
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
+import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction
+import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
+import kotlin.time.Duration.Companion.seconds
+
+class OtherSessionsViewModel @AssistedInject constructor(
+        @Assisted initialState: OtherSessionsViewState,
+        private val activeSessionHolder: ActiveSessionHolder,
+        private val getDeviceFullInfoListUseCase: GetDeviceFullInfoListUseCase,
+        private val refreshDevicesUseCase: RefreshDevicesUseCase,
+) : VectorViewModel<OtherSessionsViewState, OtherSessionsAction, OtherSessionsViewEvents>(initialState), VerificationService.Listener {
+
+    @AssistedFactory
+    interface Factory : MavericksAssistedViewModelFactory<OtherSessionsViewModel, OtherSessionsViewState> {
+        override fun create(initialState: OtherSessionsViewState): OtherSessionsViewModel
+    }
+
+    companion object : MavericksViewModelFactory<OtherSessionsViewModel, OtherSessionsViewState> by hiltMavericksViewModelFactory()
+
+    private var observeDevicesJob: Job? = null
+
+    private val refreshSource = PublishDataSource<Unit>()
+    private val refreshThrottleDelayMs = 4.seconds.inWholeMilliseconds
+
+    init {
+        observeDevices(initialState.currentFilter)
+        addVerificationListener()
+        observeRefreshSource()
+    }
+
+    override fun onCleared() {
+        removeVerificationListener()
+        super.onCleared()
+    }
+
+    private fun observeDevices(currentFilter: DeviceManagerFilterType) {
+        observeDevicesJob?.cancel()
+        observeDevicesJob = getDeviceFullInfoListUseCase.execute(
+                filterType = currentFilter,
+                excludeCurrentDevice = true
+        )
+                .execute { async ->
+                    if (async is Success) {
+                        copy(
+                                devices = async,
+                        )
+                    } else {
+                        copy(
+                                devices = async
+                        )
+                    }
+                }
+    }
+
+    private fun addVerificationListener() {
+        activeSessionHolder.getSafeActiveSession()
+                ?.cryptoService()
+                ?.verificationService()
+                ?.addListener(this)
+    }
+
+    private fun removeVerificationListener() {
+        activeSessionHolder.getSafeActiveSession()
+                ?.cryptoService()
+                ?.verificationService()
+                ?.removeListener(this)
+    }
+
+    private fun observeRefreshSource() {
+        refreshSource.stream()
+                .throttleFirst(refreshThrottleDelayMs)
+                .onEach { refreshDevicesUseCase.execute() }
+                .launchIn(viewModelScope)
+    }
+
+    override fun transactionUpdated(tx: VerificationTransaction) {
+        if (tx.state == VerificationTxState.Verified) {
+            queryRefreshDevicesList()
+        }
+    }
+
+    private fun queryRefreshDevicesList() {
+        refreshSource.post(Unit)
+    }
+
+    override fun handle(action: OtherSessionsAction) {
+        when (action) {
+            is OtherSessionsAction.FilterDevices -> handleFilterDevices(action)
+        }
+    }
+
+    private fun handleFilterDevices(action: OtherSessionsAction.FilterDevices) {
+        setState {
+            copy(
+                    currentFilter = action.filterType
+            )
+        }
+        observeDevices(action.filterType)
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewNavigator.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewNavigator.kt
new file mode 100644
index 0000000000..ef1895d0ae
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewNavigator.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.features.settings.devices.v2.othersessions
+
+import android.content.Context
+import im.vector.app.features.settings.devices.v2.overview.SessionOverviewActivity
+import javax.inject.Inject
+
+class OtherSessionsViewNavigator @Inject constructor() {
+
+    fun navigateToSessionOverview(context: Context, deviceId: String) {
+        context.startActivity(SessionOverviewActivity.newIntent(context, deviceId))
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewState.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewState.kt
new file mode 100644
index 0000000000..d03cba03f9
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewState.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.features.settings.devices.v2.othersessions
+
+import com.airbnb.mvrx.Async
+import com.airbnb.mvrx.MavericksState
+import com.airbnb.mvrx.Uninitialized
+import im.vector.app.features.settings.devices.v2.DeviceFullInfo
+import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
+
+data class OtherSessionsViewState(
+        val devices: Async<List<DeviceFullInfo>> = Uninitialized,
+        val currentFilter: DeviceManagerFilterType = DeviceManagerFilterType.ALL_SESSIONS,
+) : MavericksState
diff --git a/vector/src/main/res/layout/bottom_sheet_device_manager_filter.xml b/vector/src/main/res/layout/bottom_sheet_device_manager_filter.xml
index 73e1971820..a7987e70b5 100644
--- a/vector/src/main/res/layout/bottom_sheet_device_manager_filter.xml
+++ b/vector/src/main/res/layout/bottom_sheet_device_manager_filter.xml
@@ -47,6 +47,7 @@
             android:text="@string/device_manager_filter_option_verified" />
 
         <TextView
+            android:id="@+id/filterOptionVerifiedTextView"
             style="@style/TextAppearance.Vector.Body.DevicesManagement"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
@@ -63,6 +64,7 @@
             android:text="@string/device_manager_filter_option_unverified" />
 
         <TextView
+            android:id="@+id/filterOptionUnverifiedTextView"
             style="@style/TextAppearance.Vector.Body.DevicesManagement"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
@@ -79,7 +81,7 @@
             android:text="@string/device_manager_filter_option_inactive" />
 
         <TextView
-            android:id="@+id/filterOptionInactiveRadioButtonDescription"
+            android:id="@+id/filterOptionInactiveTextView"
             style="@style/TextAppearance.Vector.Body.DevicesManagement"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
diff --git a/vector/src/main/res/layout/fragment_other_sessions.xml b/vector/src/main/res/layout/fragment_other_sessions.xml
index df2bf0cce4..ea39e9d58d 100644
--- a/vector/src/main/res/layout/fragment_other_sessions.xml
+++ b/vector/src/main/res/layout/fragment_other_sessions.xml
@@ -18,7 +18,7 @@
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             app:navigationIcon="@drawable/ic_back_24dp"
-            app:title="@string/device_manager_other_sessions_title">
+            app:title="@string/settings_sessions_other_title">
 
             <FrameLayout
                 android:id="@+id/otherSessionsFilterFrameLayout"

From e3ee59f6c17ee3e10ae2aec8c8927d91ba7a50dd Mon Sep 17 00:00:00 2001
From: Onuray Sahin <onurays@element.io>
Date: Thu, 15 Sep 2022 15:34:59 +0300
Subject: [PATCH 060/108] Refactor naming of strings.

---
 library/ui-strings/src/main/res/values-ca/strings.xml     | 4 ++--
 library/ui-strings/src/main/res/values-cs/strings.xml     | 4 ++--
 library/ui-strings/src/main/res/values-de/strings.xml     | 6 +++---
 library/ui-strings/src/main/res/values-et/strings.xml     | 4 ++--
 library/ui-strings/src/main/res/values-fa/strings.xml     | 4 ++--
 library/ui-strings/src/main/res/values-fr/strings.xml     | 4 ++--
 library/ui-strings/src/main/res/values-hu/strings.xml     | 4 ++--
 library/ui-strings/src/main/res/values-in/strings.xml     | 4 ++--
 library/ui-strings/src/main/res/values-it/strings.xml     | 4 ++--
 library/ui-strings/src/main/res/values-nl/strings.xml     | 4 ++--
 library/ui-strings/src/main/res/values-pl/strings.xml     | 6 +++---
 library/ui-strings/src/main/res/values-pt-rBR/strings.xml | 4 ++--
 library/ui-strings/src/main/res/values-ru/strings.xml     | 6 +++---
 library/ui-strings/src/main/res/values-sk/strings.xml     | 4 ++--
 library/ui-strings/src/main/res/values-uk/strings.xml     | 4 ++--
 library/ui-strings/src/main/res/values-zh-rCN/strings.xml | 6 +++---
 library/ui-strings/src/main/res/values-zh-rTW/strings.xml | 4 ++--
 library/ui-strings/src/main/res/values/strings.xml        | 4 ++--
 vector/src/main/res/layout/fragment_other_sessions.xml    | 4 ++--
 vector/src/main/res/layout/fragment_settings_devices.xml  | 4 ++--
 20 files changed, 44 insertions(+), 44 deletions(-)

diff --git a/library/ui-strings/src/main/res/values-ca/strings.xml b/library/ui-strings/src/main/res/values-ca/strings.xml
index 13a5b6c119..e23c375084 100644
--- a/library/ui-strings/src/main/res/values-ca/strings.xml
+++ b/library/ui-strings/src/main/res/values-ca/strings.xml
@@ -2602,8 +2602,8 @@
     <string name="all_chats">Tots els xats</string>
     <string name="home_layout_preferences">Preferències de disseny</string>
     <string name="explore_rooms">Explora sales</string>
-    <string name="settings_sessions_other_description">Per estar més segur, verifica les teves sessions i tanca qualsevol sessió que no reconeguis o ja no utilitzis.</string>
-    <string name="settings_sessions_other_title">Altres sessions</string>
+    <string name="device_manager_sessions_other_description">Per estar més segur, verifica les teves sessions i tanca qualsevol sessió que no reconeguis o ja no utilitzis.</string>
+    <string name="device_manager_sessions_other_title">Altres sessions</string>
     <string name="settings_sessions_list">Sessions</string>
     <string name="a11y_open_spaces">Obre la llista d\'espais</string>
     <string name="a11y_create_message">Crea un nou xat o sala</string>
diff --git a/library/ui-strings/src/main/res/values-cs/strings.xml b/library/ui-strings/src/main/res/values-cs/strings.xml
index b7bfeac444..4ad9cf8e30 100644
--- a/library/ui-strings/src/main/res/values-cs/strings.xml
+++ b/library/ui-strings/src/main/res/values-cs/strings.xml
@@ -2651,8 +2651,8 @@
     <string name="a11y_open_settings">Otevřít nastavení</string>
     <string name="all_chats">Všechny konverzace</string>
     <string name="device_manager_settings_active_sessions_show_all">Zobrazit všechny relace (V2, WIP)</string>
-    <string name="settings_sessions_other_description">V zájmu co nejlepšího zabezpečení ověřujte své relace a odhlašujte se ze všech relací, které již nepoznáváte nebo nepoužíváte.</string>
-    <string name="settings_sessions_other_title">Ostatní relace</string>
+    <string name="device_manager_sessions_other_description">V zájmu co nejlepšího zabezpečení ověřujte své relace a odhlašujte se ze všech relací, které již nepoznáváte nebo nepoužíváte.</string>
+    <string name="device_manager_sessions_other_title">Ostatní relace</string>
     <string name="settings_sessions_list">Relace</string>
     <string name="a11y_open_spaces">Seznam otevřených prostorů</string>
     <string name="a11y_create_message">Vytvořit novou konverzaci nebo místnost</string>
diff --git a/library/ui-strings/src/main/res/values-de/strings.xml b/library/ui-strings/src/main/res/values-de/strings.xml
index 8e502a6392..e49bb8ac78 100644
--- a/library/ui-strings/src/main/res/values-de/strings.xml
+++ b/library/ui-strings/src/main/res/values-de/strings.xml
@@ -2587,8 +2587,8 @@
     <string name="room_list_filter_people">Personen</string>
     <string name="send_your_first_msg_to_invite">Schreibe deine erste Nachricht, um %s zur Konversation einzuladen</string>
     <string name="device_manager_settings_active_sessions_show_all">Alle Sitzungen anzeigen (V2, in Arbeit)</string>
-    <string name="settings_sessions_other_description">Für bestmögliche Sicherheit verifiziere deine Sitzungen und melde dich von allen ab, die du nicht erkennst oder nutzt.</string>
-    <string name="settings_sessions_other_title">Andere Sitzungen</string>
+    <string name="device_manager_sessions_other_description">Für bestmögliche Sicherheit verifiziere deine Sitzungen und melde dich von allen ab, die du nicht erkennst oder nutzt.</string>
+    <string name="device_manager_sessions_other_title">Andere Sitzungen</string>
     <string name="settings_sessions_list">Sitzungen</string>
     <string name="a11y_open_spaces">Space-Liste öffnen</string>
     <string name="a11y_create_message">Beginne ein Gespräch oder erstelle einen Raum</string>
@@ -2622,4 +2622,4 @@
     <string name="device_manager_other_sessions_description_unverified">Nicht verifiziert · Letzte Aktivität %1$s</string>
     <string name="device_manager_verification_status_detail_unverified">Verifiziere deine aktuelle Sitzung für besonders sichere Nachrichtenübertragung.</string>
     <string name="device_manager_verification_status_unverified">Nicht verifizierte Sitzung</string>
-</resources>
\ No newline at end of file
+</resources>
diff --git a/library/ui-strings/src/main/res/values-et/strings.xml b/library/ui-strings/src/main/res/values-et/strings.xml
index 55fb9dfef0..fd2cb44ecd 100644
--- a/library/ui-strings/src/main/res/values-et/strings.xml
+++ b/library/ui-strings/src/main/res/values-et/strings.xml
@@ -2592,8 +2592,8 @@
     <string name="a11y_open_settings">Ava seadistused</string>
     <string name="all_chats">Kõik vestlused</string>
     <string name="device_manager_settings_active_sessions_show_all">Näita kõiki sessioone (V2, WIP)</string>
-    <string name="settings_sessions_other_description">Parima turvalisuse nimel verifitseeri kõik oma sessioonid ning logi välja neist, mida sa enam ei kasuta.</string>
-    <string name="settings_sessions_other_title">Muud sessioonid</string>
+    <string name="device_manager_sessions_other_description">Parima turvalisuse nimel verifitseeri kõik oma sessioonid ning logi välja neist, mida sa enam ei kasuta.</string>
+    <string name="device_manager_sessions_other_title">Muud sessioonid</string>
     <string name="settings_sessions_list">Sessionid</string>
     <string name="a11y_open_spaces">Ava kogukondade loend</string>
     <string name="a11y_create_message">Alusta uut vestlust või loo uus jututuba</string>
diff --git a/library/ui-strings/src/main/res/values-fa/strings.xml b/library/ui-strings/src/main/res/values-fa/strings.xml
index e104225389..3aa03fc77b 100644
--- a/library/ui-strings/src/main/res/values-fa/strings.xml
+++ b/library/ui-strings/src/main/res/values-fa/strings.xml
@@ -2601,8 +2601,8 @@
     <string name="a11y_open_settings">گشودن تنظیمات</string>
     <string name="all_chats">تمامی گپ‌ها</string>
     <string name="device_manager_settings_active_sessions_show_all">نمایش تمامی نشست‌ها (ن۲، دح‌ت)</string>
-    <string name="settings_sessions_other_description">برای امنیت بیش‌تر، نشست‌هایتان را تأیید و از هر نشستی که تشخیصش نمی‌دهید یا دیگر استفاده نمی‌کنید خارج شوید.</string>
-    <string name="settings_sessions_other_title">دیگر نشست‌ها</string>
+    <string name="device_manager_sessions_other_description">برای امنیت بیش‌تر، نشست‌هایتان را تأیید و از هر نشستی که تشخیصش نمی‌دهید یا دیگر استفاده نمی‌کنید خارج شوید.</string>
+    <string name="device_manager_sessions_other_title">دیگر نشست‌ها</string>
     <string name="settings_sessions_list">نشست‌ها</string>
     <string name="a11y_open_spaces">گشودن سیاههٔ فضاها</string>
     <string name="a11y_create_message">ایجاد اتاق یا گفت‌وگویی جدید</string>
diff --git a/library/ui-strings/src/main/res/values-fr/strings.xml b/library/ui-strings/src/main/res/values-fr/strings.xml
index 55b5f88134..d73207eb3b 100644
--- a/library/ui-strings/src/main/res/values-fr/strings.xml
+++ b/library/ui-strings/src/main/res/values-fr/strings.xml
@@ -2601,8 +2601,8 @@
     <string name="a11y_open_settings">Ouvrir les paramètres</string>
     <string name="all_chats">Toutes les conversations</string>
     <string name="device_manager_settings_active_sessions_show_all">Afficher toutes les sessions (V2, en cours)</string>
-    <string name="settings_sessions_other_description">Pour une meilleure sécurité, vérifiez vos sessions et déconnectez toutes les sessions que vous ne connaissez pas ou que vous n’utilisez plus.</string>
-    <string name="settings_sessions_other_title">Autres sessions</string>
+    <string name="device_manager_sessions_other_description">Pour une meilleure sécurité, vérifiez vos sessions et déconnectez toutes les sessions que vous ne connaissez pas ou que vous n’utilisez plus.</string>
+    <string name="device_manager_sessions_other_title">Autres sessions</string>
     <string name="settings_sessions_list">Sessions</string>
     <string name="a11y_open_spaces">Ouvrir la liste des espaces</string>
     <string name="a11y_create_message">Créer une nouvelle conversation ou salon</string>
diff --git a/library/ui-strings/src/main/res/values-hu/strings.xml b/library/ui-strings/src/main/res/values-hu/strings.xml
index af8bf26b2e..84cae1c51d 100644
--- a/library/ui-strings/src/main/res/values-hu/strings.xml
+++ b/library/ui-strings/src/main/res/values-hu/strings.xml
@@ -2615,8 +2615,8 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze
     <string name="a11y_device_manager_device_type_web">Web</string>
     <string name="a11y_device_manager_device_type_mobile">Mobil</string>
     <string name="device_manager_settings_active_sessions_show_all">Minden munkamenet megjelenítése (V2, WIP)</string>
-    <string name="settings_sessions_other_description">A legjobb biztonság érdekében ellenőrizd a munkameneteket, és jelentkezz ki minden olyan munkamenetből, melyet már nem ismersz fel vagy nem használsz.</string>
-    <string name="settings_sessions_other_title">Más munkamenetek</string>
+    <string name="device_manager_sessions_other_description">A legjobb biztonság érdekében ellenőrizd a munkameneteket, és jelentkezz ki minden olyan munkamenetből, melyet már nem ismersz fel vagy nem használsz.</string>
+    <string name="device_manager_sessions_other_title">Más munkamenetek</string>
     <string name="settings_sessions_list">Munkamenetek</string>
     <string name="a11y_open_spaces">Nyitott területek listája</string>
     <string name="a11y_create_message">Új beszélgetés vagy szoba létrehozása</string>
diff --git a/library/ui-strings/src/main/res/values-in/strings.xml b/library/ui-strings/src/main/res/values-in/strings.xml
index d1e68b4529..830adf7ce9 100644
--- a/library/ui-strings/src/main/res/values-in/strings.xml
+++ b/library/ui-strings/src/main/res/values-in/strings.xml
@@ -2553,8 +2553,8 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.</string>
     <string name="auth_reset_password_error_unverified">Email belum diverifikasi, periksa kotak masuk Anda</string>
     <string name="all_chats">Semua Obrolan</string>
     <string name="device_manager_settings_active_sessions_show_all">Tampilkan Semua Sesi (V2, Dalam Pengembangan)</string>
-    <string name="settings_sessions_other_description">Untuk keamanan terbaik, verifikasi sesi Anda dan keluarkan sesi apa pun yang Anda tidak kenal atau Anda tidak gunakan lagi.</string>
-    <string name="settings_sessions_other_title">Sesi lainnya</string>
+    <string name="device_manager_sessions_other_description">Untuk keamanan terbaik, verifikasi sesi Anda dan keluarkan sesi apa pun yang Anda tidak kenal atau Anda tidak gunakan lagi.</string>
+    <string name="device_manager_sessions_other_title">Sesi lainnya</string>
     <string name="settings_sessions_list">Sesi</string>
     <string name="a11y_open_spaces">Buka daftar space</string>
     <string name="a11y_create_message">Buat percakapan atau ruangan baru</string>
diff --git a/library/ui-strings/src/main/res/values-it/strings.xml b/library/ui-strings/src/main/res/values-it/strings.xml
index ecb29d1586..b96693811a 100644
--- a/library/ui-strings/src/main/res/values-it/strings.xml
+++ b/library/ui-strings/src/main/res/values-it/strings.xml
@@ -2592,8 +2592,8 @@
     <string name="a11y_open_settings">Apri le impostazioni</string>
     <string name="all_chats">Tutte le chat</string>
     <string name="device_manager_settings_active_sessions_show_all">Mostra tutte le sessioni (V2, WIP)</string>
-    <string name="settings_sessions_other_description">Per una maggiore sicurezza, verifica le tue sessioni e disconnetti quelle che non riconosci o che non usi più.</string>
-    <string name="settings_sessions_other_title">Altre sessioni</string>
+    <string name="device_manager_sessions_other_description">Per una maggiore sicurezza, verifica le tue sessioni e disconnetti quelle che non riconosci o che non usi più.</string>
+    <string name="device_manager_sessions_other_title">Altre sessioni</string>
     <string name="settings_sessions_list">Sessioni</string>
     <string name="a11y_open_spaces">Apri elenco spazi</string>
     <string name="a11y_create_message">Crea una nuova conversazione o stanza</string>
diff --git a/library/ui-strings/src/main/res/values-nl/strings.xml b/library/ui-strings/src/main/res/values-nl/strings.xml
index b1d239963e..c4cacbad93 100644
--- a/library/ui-strings/src/main/res/values-nl/strings.xml
+++ b/library/ui-strings/src/main/res/values-nl/strings.xml
@@ -2600,8 +2600,8 @@
     <string name="location_share_loading_map_error">Kan kaart niet laden
 \nDeze server is mogelijk niet geconfigureerd om kaarten weer te geven.</string>
     <string name="a11y_open_settings">Open instellingen</string>
-    <string name="settings_sessions_other_description">Voor de beste beveiliging verifieert u uw sessies en meldt u zich af bij elke sessie die u niet meer herkent of gebruikt.</string>
-    <string name="settings_sessions_other_title">Andere sessies</string>
+    <string name="device_manager_sessions_other_description">Voor de beste beveiliging verifieert u uw sessies en meldt u zich af bij elke sessie die u niet meer herkent of gebruikt.</string>
+    <string name="device_manager_sessions_other_title">Andere sessies</string>
     <string name="settings_sessions_list">Sessies</string>
     <string name="a11y_open_spaces">Lijst met publieke spaces</string>
     <string name="a11y_create_message">Maak een nieuw gesprek of een nieuwe kamer</string>
diff --git a/library/ui-strings/src/main/res/values-pl/strings.xml b/library/ui-strings/src/main/res/values-pl/strings.xml
index 18b0de078c..dd10397900 100644
--- a/library/ui-strings/src/main/res/values-pl/strings.xml
+++ b/library/ui-strings/src/main/res/values-pl/strings.xml
@@ -2697,8 +2697,8 @@
     <string name="location_share_loading_map_error">Nie można wczytać mapy.
 \nTen serwer macierzysty może nie być skonfigurowany do wyświetlania map.</string>
     <string name="a11y_open_settings">Otwórz ustawienia</string>
-    <string name="settings_sessions_other_description">Aby zapewnić najlepsze bezpieczeństwo, zweryfikuj swoje sesje i wyloguj się z każdej sesji, której już nie rozpoznajesz lub której już nie używasz.</string>
-    <string name="settings_sessions_other_title">Inne sesje</string>
+    <string name="device_manager_sessions_other_description">Aby zapewnić najlepsze bezpieczeństwo, zweryfikuj swoje sesje i wyloguj się z każdej sesji, której już nie rozpoznajesz lub której już nie używasz.</string>
+    <string name="device_manager_sessions_other_title">Inne sesje</string>
     <string name="settings_sessions_list">Sesje</string>
     <string name="a11y_open_spaces">Lista otwartych przestrzeni</string>
     <string name="a11y_create_message">Utwórz nową rozmowę lub pokój</string>
@@ -2734,4 +2734,4 @@
     <string name="timeline_error_room_not_found">Niestety, ten pokój nie został znaleziony.
 \nSpróbuj ponownie później.%s</string>
     <string name="invites_title">Zaproszenia</string>
-</resources>
\ No newline at end of file
+</resources>
diff --git a/library/ui-strings/src/main/res/values-pt-rBR/strings.xml b/library/ui-strings/src/main/res/values-pt-rBR/strings.xml
index 08c41db365..7698d1ecf9 100644
--- a/library/ui-strings/src/main/res/values-pt-rBR/strings.xml
+++ b/library/ui-strings/src/main/res/values-pt-rBR/strings.xml
@@ -2601,8 +2601,8 @@
     <string name="a11y_open_settings">Abrir configurações</string>
     <string name="all_chats">Todos os Chats</string>
     <string name="device_manager_settings_active_sessions_show_all">Mostrar Todas Sessões (V2, WIP)</string>
-    <string name="settings_sessions_other_description">Para a melhor segurança, verifique suas sessões e faça signout de qualquer sessão que você não reconhece ou usa mais.</string>
-    <string name="settings_sessions_other_title">Outras sessões</string>
+    <string name="device_manager_sessions_other_description">Para a melhor segurança, verifique suas sessões e faça signout de qualquer sessão que você não reconhece ou usa mais.</string>
+    <string name="device_manager_sessions_other_title">Outras sessões</string>
     <string name="settings_sessions_list">Sessões</string>
     <string name="a11y_open_spaces">Abrir lista de espaços</string>
     <string name="a11y_create_message">Criar uma nova conversa ou sala</string>
diff --git a/library/ui-strings/src/main/res/values-ru/strings.xml b/library/ui-strings/src/main/res/values-ru/strings.xml
index 4852be1f82..d21e71c7fe 100644
--- a/library/ui-strings/src/main/res/values-ru/strings.xml
+++ b/library/ui-strings/src/main/res/values-ru/strings.xml
@@ -2660,8 +2660,8 @@
     <string name="location_share_loading_map_error">Не удалось загрузить карту
 \nВозможно, этот домашний сервер не настроен для отображения карт.</string>
     <string name="all_chats">Все беседы</string>
-    <string name="settings_sessions_other_description">Для лучшей безопасности заверьте свои сессии и выйдите из тех, которые более не признаёте или не используете.</string>
-    <string name="settings_sessions_other_title">Другие сессии</string>
+    <string name="device_manager_sessions_other_description">Для лучшей безопасности заверьте свои сессии и выйдите из тех, которые более не признаёте или не используете.</string>
+    <string name="device_manager_sessions_other_title">Другие сессии</string>
     <string name="settings_sessions_list">Сессии</string>
     <string name="a11y_create_message">Создать беседу или комнату</string>
     <string name="device_manager_settings_active_sessions_show_all">Показать все сессии (V2, в разработке)</string>
@@ -2678,4 +2678,4 @@
     <string name="explore_rooms">Обзор комнат</string>
     <string name="start_chat">Начать беседу</string>
     <string name="create_room">Создать комнату</string>
-</resources>
\ No newline at end of file
+</resources>
diff --git a/library/ui-strings/src/main/res/values-sk/strings.xml b/library/ui-strings/src/main/res/values-sk/strings.xml
index 2cc2d0280e..799f070eb2 100644
--- a/library/ui-strings/src/main/res/values-sk/strings.xml
+++ b/library/ui-strings/src/main/res/values-sk/strings.xml
@@ -2651,8 +2651,8 @@
     <string name="a11y_open_settings">Otvoriť nastavenia</string>
     <string name="all_chats">Všetky konverzácie</string>
     <string name="device_manager_settings_active_sessions_show_all">Zobraziť všetky relácie (V2, WIP)</string>
-    <string name="settings_sessions_other_description">V záujme čo najlepšieho zabezpečenia overte svoje relácie a odhláste sa z každej relácie, ktorú už nepoznáte alebo nepoužívate.</string>
-    <string name="settings_sessions_other_title">Iné relácie</string>
+    <string name="device_manager_sessions_other_description">V záujme čo najlepšieho zabezpečenia overte svoje relácie a odhláste sa z každej relácie, ktorú už nepoznáte alebo nepoužívate.</string>
+    <string name="device_manager_sessions_other_title">Iné relácie</string>
     <string name="settings_sessions_list">Relácie</string>
     <string name="a11y_open_spaces">Otvoriť zoznam priestorov</string>
     <string name="a11y_create_message">Vytvoriť novú konverzáciu alebo miestnosť</string>
diff --git a/library/ui-strings/src/main/res/values-uk/strings.xml b/library/ui-strings/src/main/res/values-uk/strings.xml
index 1c809fff3e..6baba39a2a 100644
--- a/library/ui-strings/src/main/res/values-uk/strings.xml
+++ b/library/ui-strings/src/main/res/values-uk/strings.xml
@@ -2701,8 +2701,8 @@
     <string name="a11y_open_settings">Відкрити налаштування</string>
     <string name="all_chats">Усі бесіди</string>
     <string name="device_manager_settings_active_sessions_show_all">Показати всі сеанси (V2, WIP)</string>
-    <string name="settings_sessions_other_description">Для найкращої безпеки перевірте свої сеанси та вийдіть з усіх сеансів, які ви більше не розпізнаєте або не використовуєте.</string>
-    <string name="settings_sessions_other_title">Інші сеанси</string>
+    <string name="device_manager_sessions_other_description">Для найкращої безпеки перевірте свої сеанси та вийдіть з усіх сеансів, які ви більше не розпізнаєте або не використовуєте.</string>
+    <string name="device_manager_sessions_other_title">Інші сеанси</string>
     <string name="settings_sessions_list">Сеанси</string>
     <string name="a11y_open_spaces">Відкрити список кімнат</string>
     <string name="a11y_create_message">Створити нову розмову або кімнату</string>
diff --git a/library/ui-strings/src/main/res/values-zh-rCN/strings.xml b/library/ui-strings/src/main/res/values-zh-rCN/strings.xml
index 4e1c8e61c8..16e00a0856 100644
--- a/library/ui-strings/src/main/res/values-zh-rCN/strings.xml
+++ b/library/ui-strings/src/main/res/values-zh-rCN/strings.xml
@@ -2551,8 +2551,8 @@
     <string name="a11y_open_settings">打开设置</string>
     <string name="all_chats">全部聊天</string>
     <string name="device_manager_settings_active_sessions_show_all">显示全部会话(V2, WIP)</string>
-    <string name="settings_sessions_other_description">为获得最佳安全性,请验证你的会话,并从任何你不认识或不再使用的会话登出。</string>
-    <string name="settings_sessions_other_title">其他会话</string>
+    <string name="device_manager_sessions_other_description">为获得最佳安全性,请验证你的会话,并从任何你不认识或不再使用的会话登出。</string>
+    <string name="device_manager_sessions_other_title">其他会话</string>
     <string name="settings_sessions_list">会话</string>
     <string name="a11y_open_spaces">打开空间列表</string>
     <string name="a11y_create_message">创建新对话或房间</string>
@@ -2583,4 +2583,4 @@
     <string name="device_manager_verification_status_verified">已验证的会话</string>
     <string name="a11y_device_manager_device_type_unknown">未知的设备类型</string>
     <string name="invites_title">邀请</string>
-</resources>
\ No newline at end of file
+</resources>
diff --git a/library/ui-strings/src/main/res/values-zh-rTW/strings.xml b/library/ui-strings/src/main/res/values-zh-rTW/strings.xml
index 0f5208bcde..56d38e6e65 100644
--- a/library/ui-strings/src/main/res/values-zh-rTW/strings.xml
+++ b/library/ui-strings/src/main/res/values-zh-rTW/strings.xml
@@ -2551,8 +2551,8 @@
     <string name="a11y_open_settings">開啟設定</string>
     <string name="all_chats">所有聊天</string>
     <string name="device_manager_settings_active_sessions_show_all">顯示所有工作階段 (V2, WIP)</string>
-    <string name="settings_sessions_other_description">為了取得最佳安全性,請驗證您的工作階段並登出任何您無法識別或不再使用的工作階段。</string>
-    <string name="settings_sessions_other_title">其他工作階段</string>
+    <string name="device_manager_sessions_other_description">為了取得最佳安全性,請驗證您的工作階段並登出任何您無法識別或不再使用的工作階段。</string>
+    <string name="device_manager_sessions_other_title">其他工作階段</string>
     <string name="settings_sessions_list">工作階段</string>
     <string name="a11y_open_spaces">開啟空間清單</string>
     <string name="a11y_create_message">建立新的對話或聊天室</string>
diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml
index 2731ba8837..c3c0fc1db0 100644
--- a/library/ui-strings/src/main/res/values/strings.xml
+++ b/library/ui-strings/src/main/res/values/strings.xml
@@ -2362,8 +2362,8 @@
     <string name="settings_active_sessions_manage">Manage Sessions</string>
     <string name="settings_active_sessions_signout_device">Sign out of this session</string>
     <string name="settings_sessions_list">Sessions</string>
-    <string name="settings_sessions_other_title">Other sessions</string>
-    <string name="settings_sessions_other_description">For best security, verify your sessions and sign out from any session that you don’t recognize or use anymore.</string>
+    <string name="device_manager_sessions_other_title">Other sessions</string>
+    <string name="device_manager_sessions_other_description">For best security, verify your sessions and sign out from any session that you don’t recognize or use anymore.</string>
 
     <string name="settings_server_name">Server name</string>
     <string name="settings_server_version">Server version</string>
diff --git a/vector/src/main/res/layout/fragment_other_sessions.xml b/vector/src/main/res/layout/fragment_other_sessions.xml
index ea39e9d58d..fe532b887d 100644
--- a/vector/src/main/res/layout/fragment_other_sessions.xml
+++ b/vector/src/main/res/layout/fragment_other_sessions.xml
@@ -18,7 +18,7 @@
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             app:navigationIcon="@drawable/ic_back_24dp"
-            app:title="@string/settings_sessions_other_title">
+            app:title="@string/device_manager_sessions_other_title">
 
             <FrameLayout
                 android:id="@+id/otherSessionsFilterFrameLayout"
@@ -53,7 +53,7 @@
         android:id="@+id/deviceListHeaderOtherSessions"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
-        app:devicesListHeaderDescription="@string/settings_sessions_other_description"
+        app:devicesListHeaderDescription="@string/device_manager_sessions_other_description"
         app:devicesListHeaderTitle=""
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
diff --git a/vector/src/main/res/layout/fragment_settings_devices.xml b/vector/src/main/res/layout/fragment_settings_devices.xml
index 9cefd6aa24..c367506819 100644
--- a/vector/src/main/res/layout/fragment_settings_devices.xml
+++ b/vector/src/main/res/layout/fragment_settings_devices.xml
@@ -90,8 +90,8 @@
             android:id="@+id/deviceListHeaderOtherSessions"
             android:layout_width="0dp"
             android:layout_height="wrap_content"
-            app:devicesListHeaderDescription="@string/settings_sessions_other_description"
-            app:devicesListHeaderTitle="@string/settings_sessions_other_title"
+            app:devicesListHeaderDescription="@string/device_manager_sessions_other_description"
+            app:devicesListHeaderTitle="@string/device_manager_sessions_other_title"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintTop_toBottomOf="@id/deviceListDividerCurrentSession" />

From 3e0f76a3629b040e2815bf04ae0298a6153ea493 Mon Sep 17 00:00:00 2001
From: Onuray Sahin <onurays@element.io>
Date: Thu, 15 Sep 2022 15:38:37 +0300
Subject: [PATCH 061/108] Code review fix.

---
 .../settings/devices/v2/list/SessionsListHeaderView.kt    | 8 ++------
 1 file changed, 2 insertions(+), 6 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionsListHeaderView.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionsListHeaderView.kt
index 2b93c3447e..924228444a 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionsListHeaderView.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/list/SessionsListHeaderView.kt
@@ -24,6 +24,7 @@ import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.core.content.res.use
 import androidx.core.view.isVisible
 import im.vector.app.R
+import im.vector.app.core.extensions.setTextOrHide
 import im.vector.app.core.extensions.setTextWithColoredPart
 import im.vector.app.databinding.ViewSessionsListHeaderBinding
 
@@ -54,12 +55,7 @@ class SessionsListHeaderView @JvmOverloads constructor(
 
     private fun setTitle(typedArray: TypedArray) {
         val title = typedArray.getString(R.styleable.SessionsListHeaderView_devicesListHeaderTitle)
-        if (title.isNullOrEmpty()) {
-            binding.sessionsListHeaderTitle.isVisible = false
-        } else {
-            binding.sessionsListHeaderTitle.isVisible = true
-            binding.sessionsListHeaderTitle.text = title
-        }
+        binding.sessionsListHeaderTitle.setTextOrHide(title)
     }
 
     private fun setDescription(typedArray: TypedArray) {

From fd9dca9621dff0e109a9075f0447dd5219e405e7 Mon Sep 17 00:00:00 2001
From: Onuray Sahin <onurays@element.io>
Date: Thu, 15 Sep 2022 17:39:08 +0300
Subject: [PATCH 062/108] Fix existing tests.

---
 .../features/settings/devices/v2/DevicesViewModelTest.kt | 2 +-
 .../devices/v2/GetDeviceFullInfoListUseCaseTest.kt       | 9 +++++++--
 2 files changed, 8 insertions(+), 3 deletions(-)

diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/DevicesViewModelTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/DevicesViewModelTest.kt
index cc5cdf6e39..ebcfee324c 100644
--- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/DevicesViewModelTest.kt
+++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/DevicesViewModelTest.kt
@@ -181,7 +181,7 @@ class DevicesViewModelTest {
         )
         val deviceFullInfoList = listOf(deviceFullInfo1, deviceFullInfo2)
         val deviceFullInfoListFlow = flowOf(deviceFullInfoList)
-        every { getDeviceFullInfoListUseCase.execute() } returns deviceFullInfoListFlow
+        every { getDeviceFullInfoListUseCase.execute(any(), any()) } returns deviceFullInfoListFlow
         return deviceFullInfoList
     }
 
diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/GetDeviceFullInfoListUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/GetDeviceFullInfoListUseCaseTest.kt
index fa9f742976..54b160f196 100644
--- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/GetDeviceFullInfoListUseCaseTest.kt
+++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/GetDeviceFullInfoListUseCaseTest.kt
@@ -16,6 +16,8 @@
 
 package im.vector.app.features.settings.devices.v2
 
+import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
+import im.vector.app.features.settings.devices.v2.filter.FilterDevicesUseCase
 import im.vector.app.features.settings.devices.v2.list.CheckIfSessionIsInactiveUseCase
 import im.vector.app.test.fakes.FakeActiveSessionHolder
 import im.vector.app.test.test
@@ -47,12 +49,14 @@ class GetDeviceFullInfoListUseCaseTest {
     private val checkIfSessionIsInactiveUseCase = mockk<CheckIfSessionIsInactiveUseCase>()
     private val getEncryptionTrustLevelForDeviceUseCase = mockk<GetEncryptionTrustLevelForDeviceUseCase>()
     private val getCurrentSessionCrossSigningInfoUseCase = mockk<GetCurrentSessionCrossSigningInfoUseCase>()
+    private val filterDevicesUseCase = mockk<FilterDevicesUseCase>()
 
     private val getDeviceFullInfoListUseCase = GetDeviceFullInfoListUseCase(
             activeSessionHolder = fakeActiveSessionHolder.instance,
             checkIfSessionIsInactiveUseCase = checkIfSessionIsInactiveUseCase,
             getEncryptionTrustLevelForDeviceUseCase = getEncryptionTrustLevelForDeviceUseCase,
             getCurrentSessionCrossSigningInfoUseCase = getCurrentSessionCrossSigningInfoUseCase,
+            filterDevicesUseCase = filterDevicesUseCase,
     )
 
     @Before
@@ -117,9 +121,10 @@ class GetDeviceFullInfoListUseCaseTest {
                 isInactive = false
         )
         val expectedResult = listOf(expectedResult3, expectedResult2, expectedResult1)
+        every { filterDevicesUseCase.execute(any(), any()) } returns expectedResult
 
         // When
-        val result = getDeviceFullInfoListUseCase.execute()
+        val result = getDeviceFullInfoListUseCase.execute(DeviceManagerFilterType.ALL_SESSIONS, excludeCurrentDevice = false)
                 .test(this)
 
         // Then
@@ -144,7 +149,7 @@ class GetDeviceFullInfoListUseCaseTest {
         fakeActiveSessionHolder.givenGetSafeActiveSessionReturns(null)
 
         // When
-        val result = getDeviceFullInfoListUseCase.execute()
+        val result = getDeviceFullInfoListUseCase.execute(DeviceManagerFilterType.ALL_SESSIONS, excludeCurrentDevice = false)
                 .test(this)
 
         // Then

From e2313ad1cdaec3398a142f3eaa55ca65a8743c43 Mon Sep 17 00:00:00 2001
From: Onuray Sahin <onurays@element.io>
Date: Fri, 16 Sep 2022 13:05:06 +0300
Subject: [PATCH 063/108] Implement unit tests.

---
 .../VectorSettingsDevicesViewNavigatorTest.kt |  20 ++++
 .../v2/filter/FilterDevicesUseCaseTest.kt     | 110 ++++++++++++++++++
 2 files changed, 130 insertions(+)
 create mode 100644 vector/src/test/java/im/vector/app/features/settings/devices/v2/filter/FilterDevicesUseCaseTest.kt

diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesViewNavigatorTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesViewNavigatorTest.kt
index 2a4c53f34f..a1f0918b31 100644
--- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesViewNavigatorTest.kt
+++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/VectorSettingsDevicesViewNavigatorTest.kt
@@ -17,6 +17,7 @@
 package im.vector.app.features.settings.devices.v2
 
 import android.content.Intent
+import im.vector.app.features.settings.devices.v2.othersessions.OtherSessionsActivity
 import im.vector.app.features.settings.devices.v2.overview.SessionOverviewActivity
 import im.vector.app.test.fakes.FakeContext
 import io.mockk.every
@@ -38,6 +39,7 @@ class VectorSettingsDevicesViewNavigatorTest {
     @Before
     fun setUp() {
         mockkObject(SessionOverviewActivity.Companion)
+        mockkObject(OtherSessionsActivity.Companion)
     }
 
     @After
@@ -57,9 +59,27 @@ class VectorSettingsDevicesViewNavigatorTest {
         }
     }
 
+    @Test
+    fun `given an intent when navigating to other sessions list then it starts the correct activity`() {
+        val intent = givenIntentForOtherSessions()
+        context.givenStartActivity(intent)
+
+        vectorSettingsDevicesViewNavigator.navigateToOtherSessions(context.instance)
+
+        verify {
+            context.instance.startActivity(intent)
+        }
+    }
+
     private fun givenIntentForSessionOverview(sessionId: String): Intent {
         val intent = mockk<Intent>()
         every { SessionOverviewActivity.newIntent(context.instance, sessionId) } returns intent
         return intent
     }
+
+    private fun givenIntentForOtherSessions(): Intent {
+        val intent = mockk<Intent>()
+        every { OtherSessionsActivity.newIntent(context.instance) } returns intent
+        return intent
+    }
 }
diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/filter/FilterDevicesUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/filter/FilterDevicesUseCaseTest.kt
new file mode 100644
index 0000000000..1254e2a80a
--- /dev/null
+++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/filter/FilterDevicesUseCaseTest.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.settings.devices.v2.filter
+
+import im.vector.app.features.settings.devices.v2.DeviceFullInfo
+import org.amshove.kluent.shouldBeEqualTo
+import org.amshove.kluent.shouldContainAll
+import org.junit.Test
+import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
+import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
+import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
+import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
+
+private val activeVerifiedDevice = DeviceFullInfo(
+        deviceInfo = DeviceInfo(deviceId = "ACTIVE_VERIFIED_DEVICE"),
+        cryptoDeviceInfo = CryptoDeviceInfo(
+                userId = "USER_ID_1",
+                deviceId = "ACTIVE_VERIFIED_DEVICE",
+                trustLevel = DeviceTrustLevel(crossSigningVerified = true, locallyVerified = true)
+        ),
+        roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Trusted,
+        isInactive = false
+)
+private val inactiveVerifiedDevice = DeviceFullInfo(
+        deviceInfo = DeviceInfo(deviceId = "INACTIVE_VERIFIED_DEVICE"),
+        cryptoDeviceInfo = CryptoDeviceInfo(
+                userId = "USER_ID_1",
+                deviceId = "INACTIVE_VERIFIED_DEVICE",
+                trustLevel = DeviceTrustLevel(crossSigningVerified = true, locallyVerified = true)
+        ),
+        roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Trusted,
+        isInactive = true
+)
+private val activeUnverifiedDevice = DeviceFullInfo(
+        deviceInfo = DeviceInfo(deviceId = "ACTIVE_UNVERIFIED_DEVICE"),
+        cryptoDeviceInfo = CryptoDeviceInfo(
+                userId = "USER_ID_1",
+                deviceId = "ACTIVE_UNVERIFIED_DEVICE",
+                trustLevel = DeviceTrustLevel(crossSigningVerified = false, locallyVerified = false)
+        ),
+        roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Warning,
+        isInactive = false
+)
+private val inactiveUnverifiedDevice = DeviceFullInfo(
+        deviceInfo = DeviceInfo(deviceId = "INACTIVE_UNVERIFIED_DEVICE"),
+        cryptoDeviceInfo = CryptoDeviceInfo(
+                userId = "USER_ID_1",
+                deviceId = "INACTIVE_UNVERIFIED_DEVICE",
+                trustLevel = DeviceTrustLevel(crossSigningVerified = false, locallyVerified = false)
+        ),
+        roomEncryptionTrustLevel = RoomEncryptionTrustLevel.Warning,
+        isInactive = true
+)
+
+private val devices = listOf(
+        activeVerifiedDevice,
+        inactiveVerifiedDevice,
+        activeUnverifiedDevice,
+        inactiveUnverifiedDevice,
+)
+
+class FilterDevicesUseCaseTest {
+
+    private val filterDevicesUseCase = FilterDevicesUseCase()
+
+    @Test
+    fun `given a device list when filter type is ALL_SESSIONS then returns the same list`() {
+        val filteredDeviceList = filterDevicesUseCase.execute(devices, DeviceManagerFilterType.ALL_SESSIONS, emptyList())
+
+        filteredDeviceList.size shouldBeEqualTo devices.size
+    }
+
+    @Test
+    fun `given a device list when filter type is VERIFIED then returns only verified devices`() {
+        val filteredDeviceList = filterDevicesUseCase.execute(devices, DeviceManagerFilterType.VERIFIED, emptyList())
+
+        filteredDeviceList.size shouldBeEqualTo 2
+        filteredDeviceList shouldContainAll listOf(activeVerifiedDevice, inactiveVerifiedDevice)
+    }
+
+    @Test
+    fun `given a device list when filter type is UNVERIFIED then returns only unverified devices`() {
+        val filteredDeviceList = filterDevicesUseCase.execute(devices, DeviceManagerFilterType.UNVERIFIED, emptyList())
+
+        filteredDeviceList.size shouldBeEqualTo 2
+        filteredDeviceList shouldContainAll listOf(activeUnverifiedDevice, inactiveUnverifiedDevice)
+    }
+
+    @Test
+    fun `given a device list when filter type is INACTIVE then returns only inactive devices`() {
+        val filteredDeviceList = filterDevicesUseCase.execute(devices, DeviceManagerFilterType.INACTIVE, emptyList())
+
+        filteredDeviceList.size shouldBeEqualTo 2
+        filteredDeviceList shouldContainAll listOf(inactiveVerifiedDevice, inactiveUnverifiedDevice)
+    }
+}

From e87d4db72c3b975bcaf75f53a1782f877ee3082c Mon Sep 17 00:00:00 2001
From: Onuray Sahin <onurays@element.io>
Date: Fri, 16 Sep 2022 14:42:20 +0300
Subject: [PATCH 064/108] Refactor duplicated code.

---
 .../settings/devices/v2/DevicesViewModel.kt   | 21 ++------
 .../devices/v2/VectorSessionsListViewModel.kt | 51 +++++++++++++++++++
 .../othersessions/OtherSessionsViewModel.kt   | 38 +++-----------
 3 files changed, 62 insertions(+), 48 deletions(-)
 create mode 100644 vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSessionsListViewModel.kt

diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt
index 99afc33a8a..2fd1c2ce94 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt
@@ -24,10 +24,7 @@ import dagger.assisted.AssistedInject
 import im.vector.app.core.di.ActiveSessionHolder
 import im.vector.app.core.di.MavericksAssistedViewModelFactory
 import im.vector.app.core.di.hiltMavericksViewModelFactory
-import im.vector.app.core.platform.VectorViewModel
-import im.vector.app.core.utils.PublishDataSource
 import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
-import im.vector.lib.core.utils.flow.throttleFirst
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.launch
@@ -35,16 +32,15 @@ import org.matrix.android.sdk.api.extensions.orFalse
 import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
 import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction
 import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
-import kotlin.time.Duration.Companion.seconds
 
 class DevicesViewModel @AssistedInject constructor(
         @Assisted initialState: DevicesViewState,
         private val activeSessionHolder: ActiveSessionHolder,
         private val getCurrentSessionCrossSigningInfoUseCase: GetCurrentSessionCrossSigningInfoUseCase,
         private val getDeviceFullInfoListUseCase: GetDeviceFullInfoListUseCase,
-        private val refreshDevicesUseCase: RefreshDevicesUseCase,
         private val refreshDevicesOnCryptoDevicesChangeUseCase: RefreshDevicesOnCryptoDevicesChangeUseCase,
-) : VectorViewModel<DevicesViewState, DevicesAction, DevicesViewEvent>(initialState), VerificationService.Listener {
+        refreshDevicesUseCase: RefreshDevicesUseCase,
+) : VectorSessionsListViewModel<DevicesViewState, DevicesAction, DevicesViewEvent>(initialState, refreshDevicesUseCase), VerificationService.Listener {
 
     @AssistedFactory
     interface Factory : MavericksAssistedViewModelFactory<DevicesViewModel, DevicesViewState> {
@@ -53,14 +49,10 @@ class DevicesViewModel @AssistedInject constructor(
 
     companion object : MavericksViewModelFactory<DevicesViewModel, DevicesViewState> by hiltMavericksViewModelFactory()
 
-    private val refreshSource = PublishDataSource<Unit>()
-    private val refreshThrottleDelayMs = 4.seconds.inWholeMilliseconds
-
     init {
         addVerificationListener()
         observeCurrentSessionCrossSigningInfo()
         observeDevices()
-        observeRefreshSource()
         refreshDevicesOnCryptoDevicesChange()
         queryRefreshDevicesList()
     }
@@ -123,13 +115,6 @@ class DevicesViewModel @AssistedInject constructor(
         }
     }
 
-    private fun observeRefreshSource() {
-        refreshSource.stream()
-                .throttleFirst(refreshThrottleDelayMs)
-                .onEach { refreshDevicesUseCase.execute() }
-                .launchIn(viewModelScope)
-    }
-
     override fun transactionUpdated(tx: VerificationTransaction) {
         if (tx.state == VerificationTxState.Verified) {
             queryRefreshDevicesList()
@@ -142,7 +127,7 @@ class DevicesViewModel @AssistedInject constructor(
      * It can be any mobile devices, and any browsers.
      */
     private fun queryRefreshDevicesList() {
-        refreshSource.post(Unit)
+        refreshDeviceList()
     }
 
     override fun handle(action: DevicesAction) {
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSessionsListViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSessionsListViewModel.kt
new file mode 100644
index 0000000000..dfd0c1be6d
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSessionsListViewModel.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.settings.devices.v2
+
+import com.airbnb.mvrx.MavericksState
+import im.vector.app.core.platform.VectorViewEvents
+import im.vector.app.core.platform.VectorViewModel
+import im.vector.app.core.platform.VectorViewModelAction
+import im.vector.app.core.utils.PublishDataSource
+import im.vector.lib.core.utils.flow.throttleFirst
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlin.time.Duration.Companion.seconds
+
+abstract class VectorSessionsListViewModel<S : MavericksState, VA : VectorViewModelAction, VE : VectorViewEvents>(
+        initialState: S,
+        private val refreshDevicesUseCase: RefreshDevicesUseCase,
+) : VectorViewModel<S, VA, VE>(initialState) {
+
+    private val refreshSource = PublishDataSource<Unit>()
+    private val refreshThrottleDelayMs = 4.seconds.inWholeMilliseconds
+
+    init {
+        observeRefreshSource()
+    }
+
+    private fun observeRefreshSource() {
+        refreshSource.stream()
+                .throttleFirst(refreshThrottleDelayMs)
+                .onEach { refreshDevicesUseCase.execute() }
+                .launchIn(viewModelScope)
+    }
+
+    fun refreshDeviceList() {
+        refreshSource.post(Unit)
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt
index 4a7f911ff4..e4d758eb18 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt
@@ -17,33 +17,28 @@
 package im.vector.app.features.settings.devices.v2.othersessions
 
 import com.airbnb.mvrx.MavericksViewModelFactory
-import com.airbnb.mvrx.Success
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 import im.vector.app.core.di.ActiveSessionHolder
 import im.vector.app.core.di.MavericksAssistedViewModelFactory
 import im.vector.app.core.di.hiltMavericksViewModelFactory
-import im.vector.app.core.platform.VectorViewModel
-import im.vector.app.core.utils.PublishDataSource
 import im.vector.app.features.settings.devices.v2.GetDeviceFullInfoListUseCase
 import im.vector.app.features.settings.devices.v2.RefreshDevicesUseCase
+import im.vector.app.features.settings.devices.v2.VectorSessionsListViewModel
 import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
-import im.vector.lib.core.utils.flow.throttleFirst
 import kotlinx.coroutines.Job
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
 import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
 import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction
 import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
-import kotlin.time.Duration.Companion.seconds
 
 class OtherSessionsViewModel @AssistedInject constructor(
         @Assisted initialState: OtherSessionsViewState,
         private val activeSessionHolder: ActiveSessionHolder,
         private val getDeviceFullInfoListUseCase: GetDeviceFullInfoListUseCase,
-        private val refreshDevicesUseCase: RefreshDevicesUseCase,
-) : VectorViewModel<OtherSessionsViewState, OtherSessionsAction, OtherSessionsViewEvents>(initialState), VerificationService.Listener {
+        refreshDevicesUseCase: RefreshDevicesUseCase
+) : VectorSessionsListViewModel<OtherSessionsViewState, OtherSessionsAction, OtherSessionsViewEvents>(initialState, refreshDevicesUseCase),
+        VerificationService.Listener {
 
     @AssistedFactory
     interface Factory : MavericksAssistedViewModelFactory<OtherSessionsViewModel, OtherSessionsViewState> {
@@ -54,13 +49,9 @@ class OtherSessionsViewModel @AssistedInject constructor(
 
     private var observeDevicesJob: Job? = null
 
-    private val refreshSource = PublishDataSource<Unit>()
-    private val refreshThrottleDelayMs = 4.seconds.inWholeMilliseconds
-
     init {
         observeDevices(initialState.currentFilter)
         addVerificationListener()
-        observeRefreshSource()
     }
 
     override fun onCleared() {
@@ -75,15 +66,9 @@ class OtherSessionsViewModel @AssistedInject constructor(
                 excludeCurrentDevice = true
         )
                 .execute { async ->
-                    if (async is Success) {
-                        copy(
-                                devices = async,
-                        )
-                    } else {
-                        copy(
-                                devices = async
-                        )
-                    }
+                    copy(
+                            devices = async,
+                    )
                 }
     }
 
@@ -101,13 +86,6 @@ class OtherSessionsViewModel @AssistedInject constructor(
                 ?.removeListener(this)
     }
 
-    private fun observeRefreshSource() {
-        refreshSource.stream()
-                .throttleFirst(refreshThrottleDelayMs)
-                .onEach { refreshDevicesUseCase.execute() }
-                .launchIn(viewModelScope)
-    }
-
     override fun transactionUpdated(tx: VerificationTransaction) {
         if (tx.state == VerificationTxState.Verified) {
             queryRefreshDevicesList()
@@ -115,7 +93,7 @@ class OtherSessionsViewModel @AssistedInject constructor(
     }
 
     private fun queryRefreshDevicesList() {
-        refreshSource.post(Unit)
+        refreshDeviceList()
     }
 
     override fun handle(action: OtherSessionsAction) {

From eb5253ab1ad06b607171bcb9c58b1f9bb81ecc38 Mon Sep 17 00:00:00 2001
From: Onuray Sahin <onurays@element.io>
Date: Fri, 16 Sep 2022 14:51:40 +0300
Subject: [PATCH 065/108] Refactor duplicated code.

---
 .../settings/devices/v2/DevicesViewModel.kt   | 44 ++-----------------
 .../devices/v2/VectorSessionsListViewModel.kt | 38 +++++++++++++++-
 .../othersessions/OtherSessionsViewModel.kt   | 40 ++---------------
 3 files changed, 44 insertions(+), 78 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt
index 2fd1c2ce94..9c1b70a7e2 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt
@@ -29,18 +29,15 @@ import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.launch
 import org.matrix.android.sdk.api.extensions.orFalse
-import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
-import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction
-import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
 
 class DevicesViewModel @AssistedInject constructor(
         @Assisted initialState: DevicesViewState,
-        private val activeSessionHolder: ActiveSessionHolder,
+        activeSessionHolder: ActiveSessionHolder,
         private val getCurrentSessionCrossSigningInfoUseCase: GetCurrentSessionCrossSigningInfoUseCase,
         private val getDeviceFullInfoListUseCase: GetDeviceFullInfoListUseCase,
         private val refreshDevicesOnCryptoDevicesChangeUseCase: RefreshDevicesOnCryptoDevicesChangeUseCase,
         refreshDevicesUseCase: RefreshDevicesUseCase,
-) : VectorSessionsListViewModel<DevicesViewState, DevicesAction, DevicesViewEvent>(initialState, refreshDevicesUseCase), VerificationService.Listener {
+) : VectorSessionsListViewModel<DevicesViewState, DevicesAction, DevicesViewEvent>(initialState, activeSessionHolder, refreshDevicesUseCase) {
 
     @AssistedFactory
     interface Factory : MavericksAssistedViewModelFactory<DevicesViewModel, DevicesViewState> {
@@ -50,30 +47,10 @@ class DevicesViewModel @AssistedInject constructor(
     companion object : MavericksViewModelFactory<DevicesViewModel, DevicesViewState> by hiltMavericksViewModelFactory()
 
     init {
-        addVerificationListener()
         observeCurrentSessionCrossSigningInfo()
         observeDevices()
         refreshDevicesOnCryptoDevicesChange()
-        queryRefreshDevicesList()
-    }
-
-    override fun onCleared() {
-        removeVerificationListener()
-        super.onCleared()
-    }
-
-    private fun addVerificationListener() {
-        activeSessionHolder.getSafeActiveSession()
-                ?.cryptoService()
-                ?.verificationService()
-                ?.addListener(this)
-    }
-
-    private fun removeVerificationListener() {
-        activeSessionHolder.getSafeActiveSession()
-                ?.cryptoService()
-                ?.verificationService()
-                ?.removeListener(this)
+        refreshDeviceList()
     }
 
     private fun observeCurrentSessionCrossSigningInfo() {
@@ -115,21 +92,6 @@ class DevicesViewModel @AssistedInject constructor(
         }
     }
 
-    override fun transactionUpdated(tx: VerificationTransaction) {
-        if (tx.state == VerificationTxState.Verified) {
-            queryRefreshDevicesList()
-        }
-    }
-
-    /**
-     * Force the refresh of the devices list.
-     * The devices list is the list of the devices where the user is logged in.
-     * It can be any mobile devices, and any browsers.
-     */
-    private fun queryRefreshDevicesList() {
-        refreshDeviceList()
-    }
-
     override fun handle(action: DevicesAction) {
         when (action) {
             is DevicesAction.MarkAsManuallyVerified -> handleMarkAsManuallyVerifiedAction()
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSessionsListViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSessionsListViewModel.kt
index dfd0c1be6d..8cb69a31ed 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSessionsListViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/VectorSessionsListViewModel.kt
@@ -17,6 +17,7 @@
 package im.vector.app.features.settings.devices.v2
 
 import com.airbnb.mvrx.MavericksState
+import im.vector.app.core.di.ActiveSessionHolder
 import im.vector.app.core.platform.VectorViewEvents
 import im.vector.app.core.platform.VectorViewModel
 import im.vector.app.core.platform.VectorViewModelAction
@@ -24,20 +25,44 @@ import im.vector.app.core.utils.PublishDataSource
 import im.vector.lib.core.utils.flow.throttleFirst
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
+import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
+import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction
+import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
 import kotlin.time.Duration.Companion.seconds
 
 abstract class VectorSessionsListViewModel<S : MavericksState, VA : VectorViewModelAction, VE : VectorViewEvents>(
         initialState: S,
+        private val activeSessionHolder: ActiveSessionHolder,
         private val refreshDevicesUseCase: RefreshDevicesUseCase,
-) : VectorViewModel<S, VA, VE>(initialState) {
+) : VectorViewModel<S, VA, VE>(initialState), VerificationService.Listener {
 
     private val refreshSource = PublishDataSource<Unit>()
     private val refreshThrottleDelayMs = 4.seconds.inWholeMilliseconds
 
     init {
+        addVerificationListener()
         observeRefreshSource()
     }
 
+    override fun onCleared() {
+        removeVerificationListener()
+        super.onCleared()
+    }
+
+    private fun addVerificationListener() {
+        activeSessionHolder.getSafeActiveSession()
+                ?.cryptoService()
+                ?.verificationService()
+                ?.addListener(this)
+    }
+
+    private fun removeVerificationListener() {
+        activeSessionHolder.getSafeActiveSession()
+                ?.cryptoService()
+                ?.verificationService()
+                ?.removeListener(this)
+    }
+
     private fun observeRefreshSource() {
         refreshSource.stream()
                 .throttleFirst(refreshThrottleDelayMs)
@@ -45,6 +70,17 @@ abstract class VectorSessionsListViewModel<S : MavericksState, VA : VectorViewMo
                 .launchIn(viewModelScope)
     }
 
+    override fun transactionUpdated(tx: VerificationTransaction) {
+        if (tx.state == VerificationTxState.Verified) {
+            refreshDeviceList()
+        }
+    }
+
+    /**
+     * Force the refresh of the devices list.
+     * The devices list is the list of the devices where the user is logged in.
+     * It can be any mobile devices, and any browsers.
+     */
     fun refreshDeviceList() {
         refreshSource.post(Unit)
     }
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt
index e4d758eb18..a40d95c6d9 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt
@@ -28,17 +28,15 @@ import im.vector.app.features.settings.devices.v2.RefreshDevicesUseCase
 import im.vector.app.features.settings.devices.v2.VectorSessionsListViewModel
 import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType
 import kotlinx.coroutines.Job
-import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
-import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction
-import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
 
 class OtherSessionsViewModel @AssistedInject constructor(
         @Assisted initialState: OtherSessionsViewState,
-        private val activeSessionHolder: ActiveSessionHolder,
+        activeSessionHolder: ActiveSessionHolder,
         private val getDeviceFullInfoListUseCase: GetDeviceFullInfoListUseCase,
         refreshDevicesUseCase: RefreshDevicesUseCase
-) : VectorSessionsListViewModel<OtherSessionsViewState, OtherSessionsAction, OtherSessionsViewEvents>(initialState, refreshDevicesUseCase),
-        VerificationService.Listener {
+) : VectorSessionsListViewModel<OtherSessionsViewState, OtherSessionsAction, OtherSessionsViewEvents>(
+        initialState, activeSessionHolder, refreshDevicesUseCase
+) {
 
     @AssistedFactory
     interface Factory : MavericksAssistedViewModelFactory<OtherSessionsViewModel, OtherSessionsViewState> {
@@ -51,12 +49,6 @@ class OtherSessionsViewModel @AssistedInject constructor(
 
     init {
         observeDevices(initialState.currentFilter)
-        addVerificationListener()
-    }
-
-    override fun onCleared() {
-        removeVerificationListener()
-        super.onCleared()
     }
 
     private fun observeDevices(currentFilter: DeviceManagerFilterType) {
@@ -72,30 +64,6 @@ class OtherSessionsViewModel @AssistedInject constructor(
                 }
     }
 
-    private fun addVerificationListener() {
-        activeSessionHolder.getSafeActiveSession()
-                ?.cryptoService()
-                ?.verificationService()
-                ?.addListener(this)
-    }
-
-    private fun removeVerificationListener() {
-        activeSessionHolder.getSafeActiveSession()
-                ?.cryptoService()
-                ?.verificationService()
-                ?.removeListener(this)
-    }
-
-    override fun transactionUpdated(tx: VerificationTransaction) {
-        if (tx.state == VerificationTxState.Verified) {
-            queryRefreshDevicesList()
-        }
-    }
-
-    private fun queryRefreshDevicesList() {
-        refreshDeviceList()
-    }
-
     override fun handle(action: OtherSessionsAction) {
         when (action) {
             is OtherSessionsAction.FilterDevices -> handleFilterDevices(action)

From 6823258abb28a5e42063d661ef0c67dea4ed3fdf Mon Sep 17 00:00:00 2001
From: Onuray Sahin <onurays@element.io>
Date: Fri, 16 Sep 2022 17:41:51 +0300
Subject: [PATCH 066/108] Add test for view navigation.

---
 .../app/core/di/MavericksViewModelModule.kt   |  2 +-
 .../devices/v2/DevicesViewModelTest.kt        |  2 +-
 .../OtherSessionsViewNavigatorTest.kt         | 65 +++++++++++++++++++
 3 files changed, 67 insertions(+), 2 deletions(-)
 create mode 100644 vector/src/test/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewNavigatorTest.kt

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 10aee61ae5..6fb2505386 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
@@ -88,8 +88,8 @@ import im.vector.app.features.settings.account.deactivation.DeactivateAccountVie
 import im.vector.app.features.settings.crosssigning.CrossSigningSettingsViewModel
 import im.vector.app.features.settings.devices.DeviceVerificationInfoBottomSheetViewModel
 import im.vector.app.features.settings.devices.DevicesViewModel
-import im.vector.app.features.settings.devices.v2.othersessions.OtherSessionsViewModel
 import im.vector.app.features.settings.devices.v2.details.SessionDetailsViewModel
+import im.vector.app.features.settings.devices.v2.othersessions.OtherSessionsViewModel
 import im.vector.app.features.settings.devices.v2.overview.SessionOverviewViewModel
 import im.vector.app.features.settings.devtools.AccountDataViewModel
 import im.vector.app.features.settings.devtools.GossipingEventsPaperTrailViewModel
diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/DevicesViewModelTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/DevicesViewModelTest.kt
index ebcfee324c..351d6b8eb0 100644
--- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/DevicesViewModelTest.kt
+++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/DevicesViewModelTest.kt
@@ -52,8 +52,8 @@ class DevicesViewModelTest {
                 fakeActiveSessionHolder.instance,
                 getCurrentSessionCrossSigningInfoUseCase,
                 getDeviceFullInfoListUseCase,
-                refreshDevicesUseCase,
                 refreshDevicesOnCryptoDevicesChangeUseCase,
+                refreshDevicesUseCase,
         )
     }
 
diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewNavigatorTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewNavigatorTest.kt
new file mode 100644
index 0000000000..3123572521
--- /dev/null
+++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewNavigatorTest.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.settings.devices.v2.othersessions
+
+import android.content.Intent
+import im.vector.app.features.settings.devices.v2.overview.SessionOverviewActivity
+import im.vector.app.test.fakes.FakeContext
+import io.mockk.every
+import io.mockk.mockk
+import io.mockk.mockkObject
+import io.mockk.unmockkAll
+import io.mockk.verify
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+
+private const val A_DEVICE_ID = "A_DEVICE_ID"
+
+class OtherSessionsViewNavigatorTest {
+
+    private val context = FakeContext()
+    private val otherSessionsViewNavigator = OtherSessionsViewNavigator()
+
+    @Before
+    fun setUp() {
+        mockkObject(SessionOverviewActivity)
+    }
+
+    @After
+    fun tearDown() {
+        unmockkAll()
+    }
+
+    @Test
+    fun `given a device id when navigating to overview then it starts the correct activity`() {
+        val intent = givenIntentForDeviceOverview(A_DEVICE_ID)
+        context.givenStartActivity(intent)
+
+        otherSessionsViewNavigator.navigateToSessionOverview(context.instance, A_DEVICE_ID)
+
+        verify {
+            context.instance.startActivity(intent)
+        }
+    }
+
+    private fun givenIntentForDeviceOverview(deviceId: String): Intent {
+        val intent = mockk<Intent>()
+        every { SessionOverviewActivity.newIntent(context.instance, deviceId) } returns intent
+        return intent
+    }
+}

From 0685fb1e1a16cb2dce12ed2e2785db31e8efab46 Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoit@matrix.org>
Date: Fri, 16 Sep 2022 19:03:19 +0200
Subject: [PATCH 067/108] Changelog

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

diff --git a/changelog.d/7108.misc b/changelog.d/7108.misc
new file mode 100644
index 0000000000..d4b15a0222
--- /dev/null
+++ b/changelog.d/7108.misc
@@ -0,0 +1 @@
+Move some GitHub action to buildjet runner, and remove the second attempt to run integration tests.

From 43a1bdb6201982d6f0759ae088dafa440df9a515 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 16 Sep 2022 23:18:13 +0000
Subject: [PATCH 068/108] Bump play-services-location from 16.0.0 to 20.0.0

Bumps play-services-location from 16.0.0 to 20.0.0.

---
updated-dependencies:
- dependency-name: com.google.android.gms:play-services-location
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
---
 vector-app/build.gradle | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/vector-app/build.gradle b/vector-app/build.gradle
index 82c433d2df..ea543166fe 100644
--- a/vector-app/build.gradle
+++ b/vector-app/build.gradle
@@ -370,7 +370,7 @@ dependencies {
     debugImplementation 'com.facebook.soloader:soloader:0.10.4'
     debugImplementation "com.kgurgul.flipper:flipper-realm-android:2.2.0"
 
-    gplayImplementation "com.google.android.gms:play-services-location:16.0.0"
+    gplayImplementation "com.google.android.gms:play-services-location:20.0.0"
     // UnifiedPush gplay flavor only
     gplayImplementation('com.github.UnifiedPush:android-embedded_fcm_distributor:2.1.2') {
         exclude group: 'com.google.firebase', module: 'firebase-core'

From b8b2601e0b2534c2c0e9b3e3de0bae06281e652e Mon Sep 17 00:00:00 2001
From: ericdecanini <eddecanini@gmail.com>
Date: Sat, 17 Sep 2022 13:12:45 -0400
Subject: [PATCH 069/108] Enables app layout by default in labs

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

diff --git a/vector-config/src/main/res/values/config-settings.xml b/vector-config/src/main/res/values/config-settings.xml
index 1701fd45b0..8953138e5e 100755
--- a/vector-config/src/main/res/values/config-settings.xml
+++ b/vector-config/src/main/res/values/config-settings.xml
@@ -38,7 +38,7 @@
 
     <!-- Level 1: Labs -->
     <bool name="settings_labs_thread_messages_default">false</bool>
-    <bool name="settings_labs_new_app_layout_default">false</bool>
+    <bool name="settings_labs_new_app_layout_default">true</bool>
     <bool name="settings_timeline_show_live_sender_info_visible">true</bool>
     <bool name="settings_timeline_show_live_sender_info_default">false</bool>
     <!-- Level 1: Advanced settings -->

From 57c9161e00f19569cd94267fdd1339b01c8f8f20 Mon Sep 17 00:00:00 2001
From: ericdecanini <eddecanini@gmail.com>
Date: Sat, 17 Sep 2022 13:17:39 -0400
Subject: [PATCH 070/108] Adds changelog file

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

diff --git a/changelog.d/7166.misc b/changelog.d/7166.misc
new file mode 100644
index 0000000000..d223208853
--- /dev/null
+++ b/changelog.d/7166.misc
@@ -0,0 +1 @@
+New App Layout is now enabled by default! Go to the Settings > Labs to toggle this

From 298aaece01acab6c44afedf4988839abfcbcb170 Mon Sep 17 00:00:00 2001
From: NIkita Fedrunov <fedrunov@element.io>
Date: Sun, 18 Sep 2022 18:02:45 +0200
Subject: [PATCH 071/108] fixed checkVerifyPopup test fail

---
 .../java/im/vector/app/VerifySessionInteractiveTest.kt        | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/vector-app/src/androidTest/java/im/vector/app/VerifySessionInteractiveTest.kt b/vector-app/src/androidTest/java/im/vector/app/VerifySessionInteractiveTest.kt
index da13e49e84..901ef8e4c1 100644
--- a/vector-app/src/androidTest/java/im/vector/app/VerifySessionInteractiveTest.kt
+++ b/vector-app/src/androidTest/java/im/vector/app/VerifySessionInteractiveTest.kt
@@ -225,8 +225,8 @@ class VerifySessionInteractiveTest : VerificationTestBase() {
 
         // Wait until local secrets are known (gossip)
         withIdlingResource(allSecretsKnownIdling(uiSession)) {
-            onView(withId(R.id.groupToolbarAvatarImageView))
-                    .perform(click())
+            onView(withId(R.id.roomListContainer))
+                    .check(matches(isDisplayed()))
         }
     }
 

From 925fffac455accadc2d7f6a38169b72fb1bb82a6 Mon Sep 17 00:00:00 2001
From: bmarty <bmarty@users.noreply.github.com>
Date: Mon, 19 Sep 2022 00:03:51 +0000
Subject: [PATCH 072/108] Sync Emojis

---
 .../emoji_picker_datasource_formatted.json    | 597 +++++++++++++-----
 .../main/res/raw/emoji_picker_datasource.json |   2 +-
 2 files changed, 439 insertions(+), 160 deletions(-)

diff --git a/tools/emojis/emoji_picker_datasource_formatted.json b/tools/emojis/emoji_picker_datasource_formatted.json
index 551ec824b7..c00bd10371 100644
--- a/tools/emojis/emoji_picker_datasource_formatted.json
+++ b/tools/emojis/emoji_picker_datasource_formatted.json
@@ -54,6 +54,7 @@
                 "grimacing-face",
                 "face-exhaling",
                 "lying-face",
+                "shaking-face",
                 "relieved-face",
                 "pensive-face",
                 "sleepy-face",
@@ -104,7 +105,7 @@
                 "tired-face",
                 "yawning-face",
                 "face-with-steam-from-nose",
-                "pouting-face",
+                "enraged-face",
                 "angry-face",
                 "face-with-symbols-on-mouth",
                 "smiling-face-with-horns",
@@ -131,7 +132,6 @@
                 "seenoevil-monkey",
                 "hearnoevil-monkey",
                 "speaknoevil-monkey",
-                "kiss-mark",
                 "love-letter",
                 "heart-with-arrow",
                 "heart-with-ribbon",
@@ -146,14 +146,18 @@
                 "heart-on-fire",
                 "mending-heart",
                 "red-heart",
+                "pink-heart",
                 "orange-heart",
                 "yellow-heart",
                 "green-heart",
                 "blue-heart",
+                "light-blue-heart",
                 "purple-heart",
                 "brown-heart",
                 "black-heart",
+                "grey-heart",
                 "white-heart",
+                "kiss-mark",
                 "hundred-points",
                 "anger-symbol",
                 "collision",
@@ -161,7 +165,6 @@
                 "sweat-droplets",
                 "dashing-away",
                 "hole",
-                "bomb",
                 "speech-balloon",
                 "eye-in-speech-bubble",
                 "left-speech-bubble",
@@ -183,6 +186,8 @@
                 "leftwards-hand",
                 "palm-down-hand",
                 "palm-up-hand",
+                "leftwards-pushing-hand",
+                "rightwards-pushing-hand",
                 "ok-hand",
                 "pinched-fingers",
                 "pinching-hand",
@@ -561,6 +566,8 @@
                 "tiger",
                 "leopard",
                 "horse-face",
+                "moose",
+                "donkey",
                 "horse",
                 "unicorn",
                 "zebra",
@@ -623,6 +630,9 @@
                 "flamingo",
                 "peacock",
                 "parrot",
+                "wing",
+                "black-bird",
+                "goose",
                 "frog",
                 "crocodile",
                 "turtle",
@@ -643,6 +653,7 @@
                 "octopus",
                 "spiral-shell",
                 "coral",
+                "jellyfish",
                 "snail",
                 "butterfly",
                 "bug",
@@ -670,6 +681,7 @@
                 "sunflower",
                 "blossom",
                 "tulip",
+                "hyacinth",
                 "seedling",
                 "potted-plant",
                 "evergreen-tree",
@@ -684,7 +696,8 @@
                 "fallen-leaf",
                 "leaf-fluttering-in-wind",
                 "empty-nest",
-                "nest-with-eggs"
+                "nest-with-eggs",
+                "mushroom"
             ]
         },
         {
@@ -722,10 +735,11 @@
                 "broccoli",
                 "garlic",
                 "onion",
-                "mushroom",
                 "peanuts",
                 "beans",
                 "chestnut",
+                "ginger-root",
+                "pea-pod",
                 "bread",
                 "croissant",
                 "baguette-bread",
@@ -1110,11 +1124,10 @@
                 "bullseye",
                 "yoyo",
                 "kite",
+                "water-pistol",
                 "pool-8-ball",
                 "crystal-ball",
                 "magic-wand",
-                "nazar-amulet",
-                "hamsa",
                 "video-game",
                 "joystick",
                 "slot-machine",
@@ -1165,6 +1178,7 @@
                 "shorts",
                 "bikini",
                 "womans-clothes",
+                "folding-hand-fan",
                 "purse",
                 "handbag",
                 "clutch-bag",
@@ -1179,6 +1193,7 @@
                 "womans-sandal",
                 "ballet-shoes",
                 "womans-boot",
+                "hair-pick",
                 "crown",
                 "womans-hat",
                 "top-hat",
@@ -1217,6 +1232,8 @@
                 "banjo",
                 "drum",
                 "long-drum",
+                "maracas",
+                "flute",
                 "mobile-phone",
                 "mobile-phone-with-arrow",
                 "telephone",
@@ -1336,7 +1353,7 @@
                 "hammer-and-wrench",
                 "dagger",
                 "crossed-swords",
-                "water-pistol",
+                "bomb",
                 "boomerang",
                 "bow-and-arrow",
                 "shield",
@@ -1397,6 +1414,8 @@
                 "coffin",
                 "headstone",
                 "funeral-urn",
+                "nazar-amulet",
+                "hamsa",
                 "moai",
                 "placard",
                 "identification-card"
@@ -1465,6 +1484,7 @@
                 "peace-symbol",
                 "menorah",
                 "dotted-sixpointed-star",
+                "khanda",
                 "aries",
                 "taurus",
                 "gemini",
@@ -1500,6 +1520,7 @@
                 "dim-button",
                 "bright-button",
                 "antenna-bars",
+                "wireless",
                 "vibration-mode",
                 "mobile-phone-off",
                 "female-sign",
@@ -2050,7 +2071,7 @@
             ]
         },
         "melting-face": {
-            "a": "⊛ Melting Face",
+            "a": "Melting Face",
             "b": "1FAE0",
             "j": [
                 "disappear",
@@ -2345,7 +2366,7 @@
             ]
         },
         "face-with-open-eyes-and-hand-over-mouth": {
-            "a": "⊛ Face with Open Eyes and Hand over Mouth",
+            "a": "Face with Open Eyes and Hand over Mouth",
             "b": "1FAE2",
             "j": [
                 "amazement",
@@ -2360,7 +2381,7 @@
             ]
         },
         "face-with-peeking-eye": {
-            "a": "⊛ Face with Peeking Eye",
+            "a": "Face with Peeking Eye",
             "b": "1FAE3",
             "j": [
                 "captivated",
@@ -2394,10 +2415,10 @@
             ]
         },
         "saluting-face": {
-            "a": "⊛ Saluting Face",
+            "a": "Saluting Face",
             "b": "1FAE1",
             "j": [
-                "ok",
+                "OK",
                 "salute",
                 "sunny",
                 "troops",
@@ -2470,7 +2491,7 @@
             ]
         },
         "dotted-line-face": {
-            "a": "⊛ Dotted Line Face",
+            "a": "Dotted Line Face",
             "b": "1FAE5",
             "j": [
                 "depressed",
@@ -2570,6 +2591,17 @@
                 "pinocchio"
             ]
         },
+        "shaking-face": {
+            "a": "⊛ Shaking Face",
+            "b": "1FAE8",
+            "j": [
+                "earthquake",
+                "face",
+                "shaking",
+                "shock",
+                "vibrate"
+            ]
+        },
         "relieved-face": {
             "a": "Relieved Face",
             "b": "1F60C",
@@ -2599,6 +2631,7 @@
             "b": "1F62A",
             "j": [
                 "face",
+                "good night",
                 "sleep",
                 "tired",
                 "rest",
@@ -2618,11 +2651,13 @@
             "b": "1F634",
             "j": [
                 "face",
+                "good night",
                 "sleep",
-                "zzz",
+                "ZZZ",
                 "tired",
                 "sleepy",
-                "night"
+                "night",
+                "zzz"
             ]
         },
         "face-with-medical-mask": {
@@ -2852,9 +2887,10 @@
             "a": "Face with Monocle",
             "b": "1F9D0",
             "j": [
+                "face",
+                "monocle",
                 "stuffy",
-                "wealthy",
-                "face"
+                "wealthy"
             ]
         },
         "confused-face": {
@@ -2872,7 +2908,7 @@
             ]
         },
         "face-with-diagonal-mouth": {
-            "a": "⊛ Face with Diagonal Mouth",
+            "a": "Face with Diagonal Mouth",
             "b": "1FAE4",
             "j": [
                 "disappointed",
@@ -2981,7 +3017,7 @@
             ]
         },
         "face-holding-back-tears": {
-            "a": "⊛ Face Holding Back Tears",
+            "a": "Face Holding Back Tears",
             "b": "1F979",
             "j": [
                 "angry",
@@ -3192,16 +3228,18 @@
                 "pride"
             ]
         },
-        "pouting-face": {
-            "a": "Pouting Face",
+        "enraged-face": {
+            "a": "Enraged Face",
             "b": "1F621",
             "j": [
                 "angry",
+                "enraged",
                 "face",
                 "mad",
                 "pouting",
                 "rage",
                 "red",
+                "pouting_face",
                 "hate",
                 "despise"
             ]
@@ -3579,19 +3617,6 @@
                 "omg"
             ]
         },
-        "kiss-mark": {
-            "a": "Kiss Mark",
-            "b": "1F48B",
-            "j": [
-                "kiss",
-                "lips",
-                "face",
-                "love",
-                "like",
-                "affection",
-                "valentines"
-            ]
-        },
         "love-letter": {
             "a": "Love Letter",
             "b": "1F48C",
@@ -3765,6 +3790,17 @@
                 "valentines"
             ]
         },
+        "pink-heart": {
+            "a": "⊛ Pink Heart",
+            "b": "1FA77",
+            "j": [
+                "cute",
+                "heart",
+                "like",
+                "love",
+                "pink"
+            ]
+        },
         "orange-heart": {
             "a": "Orange Heart",
             "b": "1F9E1",
@@ -3809,6 +3845,17 @@
                 "valentines"
             ]
         },
+        "light-blue-heart": {
+            "a": "⊛ Light Blue Heart",
+            "b": "1FA75",
+            "j": [
+                "cyan",
+                "heart",
+                "light blue",
+                "light blue heart",
+                "teal"
+            ]
+        },
         "purple-heart": {
             "a": "Purple Heart",
             "b": "1F49C",
@@ -3838,6 +3885,17 @@
                 "wicked"
             ]
         },
+        "grey-heart": {
+            "a": "⊛ Grey Heart",
+            "b": "1FA76",
+            "j": [
+                "gray",
+                "grey heart",
+                "heart",
+                "silver",
+                "slate"
+            ]
+        },
         "white-heart": {
             "a": "White Heart",
             "b": "1F90D",
@@ -3847,6 +3905,19 @@
                 "pure"
             ]
         },
+        "kiss-mark": {
+            "a": "Kiss Mark",
+            "b": "1F48B",
+            "j": [
+                "kiss",
+                "lips",
+                "face",
+                "love",
+                "like",
+                "affection",
+                "valentines"
+            ]
+        },
         "hundred-points": {
             "a": "Hundred Points",
             "b": "1F4AF",
@@ -3931,17 +4002,6 @@
                 "embarrassing"
             ]
         },
-        "bomb": {
-            "a": "Bomb",
-            "b": "1F4A3",
-            "j": [
-                "comic",
-                "boom",
-                "explode",
-                "explosion",
-                "terrorism"
-            ]
-        },
         "speech-balloon": {
             "a": "Speech Balloon",
             "b": "1F4AC",
@@ -3961,8 +4021,10 @@
             "a": "Eye in Speech Bubble",
             "b": "1F441-FE0F-200D-1F5E8-FE0F",
             "j": [
+                "balloon",
+                "bubble",
                 "eye",
-                "speech bubble",
+                "speech",
                 "witness",
                 "info"
             ]
@@ -3971,6 +4033,8 @@
             "a": "Left Speech Bubble",
             "b": "1F5E8",
             "j": [
+                "balloon",
+                "bubble",
                 "dialog",
                 "speech",
                 "words",
@@ -4011,7 +4075,9 @@
             "b": "1F4A4",
             "j": [
                 "comic",
+                "good night",
                 "sleep",
+                "ZZZ",
                 "sleepy",
                 "tired",
                 "dream"
@@ -4081,7 +4147,7 @@
             ]
         },
         "rightwards-hand": {
-            "a": "⊛ Rightwards Hand",
+            "a": "Rightwards Hand",
             "b": "1FAF1",
             "j": [
                 "hand",
@@ -4092,7 +4158,7 @@
             ]
         },
         "leftwards-hand": {
-            "a": "⊛ Leftwards Hand",
+            "a": "Leftwards Hand",
             "b": "1FAF2",
             "j": [
                 "hand",
@@ -4103,7 +4169,7 @@
             ]
         },
         "palm-down-hand": {
-            "a": "⊛ Palm Down Hand",
+            "a": "Palm Down Hand",
             "b": "1FAF3",
             "j": [
                 "dismiss",
@@ -4113,7 +4179,7 @@
             ]
         },
         "palm-up-hand": {
-            "a": "⊛ Palm Up Hand",
+            "a": "Palm Up Hand",
             "b": "1FAF4",
             "j": [
                 "beckon",
@@ -4124,6 +4190,32 @@
                 "demand"
             ]
         },
+        "leftwards-pushing-hand": {
+            "a": "⊛ Leftwards Pushing Hand",
+            "b": "1FAF7",
+            "j": [
+                "high five",
+                "leftward",
+                "leftwards pushing hand",
+                "push",
+                "refuse",
+                "stop",
+                "wait"
+            ]
+        },
+        "rightwards-pushing-hand": {
+            "a": "⊛ Rightwards Pushing Hand",
+            "b": "1FAF8",
+            "j": [
+                "high five",
+                "push",
+                "refuse",
+                "rightward",
+                "rightwards pushing hand",
+                "stop",
+                "wait"
+            ]
+        },
         "ok-hand": {
             "a": "Ok Hand",
             "b": "1F44C",
@@ -4187,7 +4279,7 @@
             ]
         },
         "hand-with-index-finger-and-thumb-crossed": {
-            "a": "⊛ Hand with Index Finger and Thumb Crossed",
+            "a": "Hand with Index Finger and Thumb Crossed",
             "b": "1FAF0",
             "j": [
                 "expensive",
@@ -4229,6 +4321,8 @@
             "j": [
                 "call",
                 "hand",
+                "hang loose",
+                "Shaka",
                 "hands",
                 "gesture",
                 "shaka"
@@ -4314,7 +4408,7 @@
             ]
         },
         "index-pointing-at-the-viewer": {
-            "a": "⊛ Index Pointing at the Viewer",
+            "a": "Index Pointing at the Viewer",
             "b": "1FAF5",
             "j": [
                 "point",
@@ -4430,7 +4524,7 @@
             ]
         },
         "heart-hands": {
-            "a": "⊛ Heart Hands",
+            "a": "Heart Hands",
             "b": "1FAF6",
             "j": [
                 "love",
@@ -4687,7 +4781,7 @@
             ]
         },
         "biting-lip": {
-            "a": "⊛ Biting Lip",
+            "a": "Biting Lip",
             "b": "1FAE6",
             "j": [
                 "anxious",
@@ -6089,7 +6183,7 @@
             ]
         },
         "person-with-crown": {
-            "a": "⊛ Person with Crown",
+            "a": "Person with Crown",
             "b": "1FAC5",
             "j": [
                 "monarch",
@@ -6263,7 +6357,7 @@
             ]
         },
         "pregnant-man": {
-            "a": "⊛ Pregnant Man",
+            "a": "Pregnant Man",
             "b": "1FAC3",
             "j": [
                 "belly",
@@ -6274,7 +6368,7 @@
             ]
         },
         "pregnant-person": {
-            "a": "⊛ Pregnant Person",
+            "a": "Pregnant Person",
             "b": "1FAC4",
             "j": [
                 "belly",
@@ -6670,7 +6764,7 @@
             ]
         },
         "troll": {
-            "a": "⊛ Troll",
+            "a": "Troll",
             "b": "1F9CC",
             "j": [
                 "fairy tale",
@@ -7634,6 +7728,7 @@
             "a": "Person in Bed",
             "b": "1F6CC",
             "j": [
+                "good night",
                 "hotel",
                 "sleep",
                 "bed",
@@ -8515,6 +8610,30 @@
                 "nature"
             ]
         },
+        "moose": {
+            "a": "⊛ Moose",
+            "b": "1FACE",
+            "j": [
+                "animal",
+                "antlers",
+                "elk",
+                "mammal",
+                "moose"
+            ]
+        },
+        "donkey": {
+            "a": "⊛ Donkey",
+            "b": "1FACF",
+            "j": [
+                "animal",
+                "ass",
+                "burro",
+                "donkey",
+                "mammal",
+                "mule",
+                "stubborn"
+            ]
+        },
         "horse": {
             "a": "Horse",
             "b": "1F40E",
@@ -9181,6 +9300,40 @@
                 "nature"
             ]
         },
+        "wing": {
+            "a": "⊛ Wing",
+            "b": "1FABD",
+            "j": [
+                "angelic",
+                "aviation",
+                "bird",
+                "flying",
+                "mythology",
+                "wing"
+            ]
+        },
+        "black-bird": {
+            "a": "⊛ Black Bird",
+            "b": "1F426-200D-2B1B",
+            "j": [
+                "bird",
+                "black",
+                "crow",
+                "raven",
+                "rook"
+            ]
+        },
+        "goose": {
+            "a": "⊛ Goose",
+            "b": "1FABF",
+            "j": [
+                "bird",
+                "fowl",
+                "goose",
+                "honk",
+                "silly"
+            ]
+        },
         "frog": {
             "a": "Frog",
             "b": "1F438",
@@ -9411,7 +9564,7 @@
             ]
         },
         "coral": {
-            "a": "⊛ Coral",
+            "a": "Coral",
             "b": "1FAB8",
             "j": [
                 "ocean",
@@ -9419,6 +9572,19 @@
                 "sea"
             ]
         },
+        "jellyfish": {
+            "a": "⊛ Jellyfish",
+            "b": "1FABC",
+            "j": [
+                "burn",
+                "invertebrate",
+                "jelly",
+                "jellyfish",
+                "marine",
+                "ouch",
+                "stinger"
+            ]
+        },
         "snail": {
             "a": "Snail",
             "b": "1F40C",
@@ -9622,7 +9788,7 @@
             ]
         },
         "lotus": {
-            "a": "⊛ Lotus",
+            "a": "Lotus",
             "b": "1FAB7",
             "j": [
                 "Buddhism",
@@ -9711,6 +9877,18 @@
                 "spring"
             ]
         },
+        "hyacinth": {
+            "a": "⊛ Hyacinth",
+            "b": "1FABB",
+            "j": [
+                "bluebonnet",
+                "flower",
+                "hyacinth",
+                "lavender",
+                "lupine",
+                "snapdragon"
+            ]
+        },
         "seedling": {
             "a": "Seedling",
             "b": "1F331",
@@ -9875,7 +10053,7 @@
             ]
         },
         "empty-nest": {
-            "a": "⊛ Empty Nest",
+            "a": "Empty Nest",
             "b": "1FAB9",
             "j": [
                 "nesting",
@@ -9883,13 +10061,22 @@
             ]
         },
         "nest-with-eggs": {
-            "a": "⊛ Nest with Eggs",
+            "a": "Nest with Eggs",
             "b": "1FABA",
             "j": [
                 "nesting",
                 "bird"
             ]
         },
+        "mushroom": {
+            "a": "Mushroom",
+            "b": "1F344",
+            "j": [
+                "toadstool",
+                "plant",
+                "vegetable"
+            ]
+        },
         "grapes": {
             "a": "Grapes",
             "b": "1F347",
@@ -10201,15 +10388,6 @@
                 "spice"
             ]
         },
-        "mushroom": {
-            "a": "Mushroom",
-            "b": "1F344",
-            "j": [
-                "toadstool",
-                "plant",
-                "vegetable"
-            ]
-        },
         "peanuts": {
             "a": "Peanuts",
             "b": "1F95C",
@@ -10221,7 +10399,7 @@
             ]
         },
         "beans": {
-            "a": "⊛ Beans",
+            "a": "Beans",
             "b": "1FAD8",
             "j": [
                 "food",
@@ -10238,6 +10416,28 @@
                 "squirrel"
             ]
         },
+        "ginger-root": {
+            "a": "⊛ Ginger Root",
+            "b": "1FADA",
+            "j": [
+                "beer",
+                "ginger root",
+                "root",
+                "spice"
+            ]
+        },
+        "pea-pod": {
+            "a": "⊛ Pea Pod",
+            "b": "1FADB",
+            "j": [
+                "beans",
+                "edamame",
+                "legume",
+                "pea",
+                "pod",
+                "vegetable"
+            ]
+        },
         "bread": {
             "a": "Bread",
             "b": "1F35E",
@@ -11258,7 +11458,7 @@
             ]
         },
         "pouring-liquid": {
-            "a": "⊛ Pouring Liquid",
+            "a": "Pouring Liquid",
             "b": "1FAD7",
             "j": [
                 "drink",
@@ -11387,7 +11587,7 @@
             ]
         },
         "jar": {
-            "a": "⊛ Jar",
+            "a": "Jar",
             "b": "1FAD9",
             "j": [
                 "condiment",
@@ -12078,7 +12278,7 @@
             ]
         },
         "playground-slide": {
-            "a": "⊛ Playground Slide",
+            "a": "Playground Slide",
             "b": "1F6DD",
             "j": [
                 "amusement park",
@@ -12609,7 +12809,7 @@
             ]
         },
         "wheel": {
-            "a": "⊛ Wheel",
+            "a": "Wheel",
             "b": "1F6DE",
             "j": [
                 "circle",
@@ -12691,7 +12891,7 @@
             ]
         },
         "ring-buoy": {
-            "a": "⊛ Ring Buoy",
+            "a": "Ring Buoy",
             "b": "1F6DF",
             "j": [
                 "float",
@@ -14686,6 +14886,20 @@
                 "wind"
             ]
         },
+        "water-pistol": {
+            "a": "Water Pistol",
+            "b": "1F52B",
+            "j": [
+                "gun",
+                "handgun",
+                "pistol",
+                "revolver",
+                "tool",
+                "water",
+                "weapon",
+                "violence"
+            ]
+        },
         "pool-8-ball": {
             "a": "Pool 8 Ball",
             "b": "1F3B1",
@@ -14729,30 +14943,6 @@
                 "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",
-                "religion"
-            ]
-        },
         "video-game": {
             "a": "Video Game",
             "b": "1F3AE",
@@ -14834,7 +15024,7 @@
             ]
         },
         "mirror-ball": {
-            "a": "⊛ Mirror Ball",
+            "a": "Mirror Ball",
             "b": "1FAA9",
             "j": [
                 "dance",
@@ -15255,6 +15445,19 @@
                 "female"
             ]
         },
+        "folding-hand-fan": {
+            "a": "⊛ Folding Hand Fan",
+            "b": "1FAAD",
+            "j": [
+                "cooling",
+                "dance",
+                "fan",
+                "flutter",
+                "folding hand fan",
+                "hot",
+                "shy"
+            ]
+        },
         "purse": {
             "a": "Purse",
             "b": "1F45B",
@@ -15429,6 +15632,16 @@
                 "fashion"
             ]
         },
+        "hair-pick": {
+            "a": "⊛ Hair Pick",
+            "b": "1FAAE",
+            "j": [
+                "Afro",
+                "comb",
+                "hair",
+                "pick"
+            ]
+        },
         "crown": {
             "a": "Crown",
             "b": "1F451",
@@ -15867,6 +16080,30 @@
                 "music"
             ]
         },
+        "maracas": {
+            "a": "⊛ Maracas",
+            "b": "1FA87",
+            "j": [
+                "instrument",
+                "maracas",
+                "music",
+                "percussion",
+                "rattle",
+                "shake"
+            ]
+        },
+        "flute": {
+            "a": "⊛ Flute",
+            "b": "1FA88",
+            "j": [
+                "fife",
+                "flute",
+                "music",
+                "pipe",
+                "recorder",
+                "woodwind"
+            ]
+        },
         "mobile-phone": {
             "a": "Mobile Phone",
             "b": "1F4F1",
@@ -15944,7 +16181,7 @@
             ]
         },
         "low-battery": {
-            "a": "⊛ Low Battery",
+            "a": "Low Battery",
             "b": "1FAAB",
             "j": [
                 "electronic",
@@ -16057,7 +16294,7 @@
             "a": "Optical Disk",
             "b": "1F4BF",
             "j": [
-                "cd",
+                "CD",
                 "computer",
                 "disk",
                 "optical",
@@ -16071,9 +16308,10 @@
             "a": "Dvd",
             "b": "1F4C0",
             "j": [
-                "blu-ray",
+                "Blu-ray",
                 "computer",
                 "disk",
+                "DVD",
                 "optical",
                 "cd",
                 "disc"
@@ -17261,18 +17499,15 @@
                 "weapon"
             ]
         },
-        "water-pistol": {
-            "a": "Water Pistol",
-            "b": "1F52B",
+        "bomb": {
+            "a": "Bomb",
+            "b": "1F4A3",
             "j": [
-                "gun",
-                "handgun",
-                "pistol",
-                "revolver",
-                "tool",
-                "water",
-                "weapon",
-                "violence"
+                "comic",
+                "boom",
+                "explode",
+                "explosion",
+                "terrorism"
             ]
         },
         "boomerang": {
@@ -17587,7 +17822,7 @@
             ]
         },
         "crutch": {
-            "a": "⊛ Crutch",
+            "a": "Crutch",
             "b": "1FA7C",
             "j": [
                 "cane",
@@ -17610,7 +17845,7 @@
             ]
         },
         "xray": {
-            "a": "⊛ X-Ray",
+            "a": "X-Ray",
             "b": "1FA7B",
             "j": [
                 "bones",
@@ -17817,7 +18052,7 @@
             ]
         },
         "bubbles": {
-            "a": "⊛ Bubbles",
+            "a": "Bubbles",
             "b": "1FAE7",
             "j": [
                 "burp",
@@ -17920,6 +18155,30 @@
                 "rip"
             ]
         },
+        "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",
+                "religion"
+            ]
+        },
         "moai": {
             "a": "Moai",
             "b": "1F5FF",
@@ -17943,7 +18202,7 @@
             ]
         },
         "identification-card": {
-            "a": "⊛ Identification Card",
+            "a": "Identification Card",
             "b": "1FAAA",
             "j": [
                 "credentials",
@@ -17957,7 +18216,7 @@
             "a": "Atm Sign",
             "b": "1F3E7",
             "j": [
-                "atm",
+                "ATM",
                 "ATM sign",
                 "automated",
                 "bank",
@@ -18009,13 +18268,15 @@
             "a": "Men’S Room",
             "b": "1F6B9",
             "j": [
+                "bathroom",
                 "lavatory",
                 "man",
                 "men’s room",
                 "restroom",
-                "wc",
-                "men_s_room",
                 "toilet",
+                "WC",
+                "men_s_room",
+                "wc",
                 "blue-square",
                 "gender",
                 "male"
@@ -18025,15 +18286,16 @@
             "a": "Women’S Room",
             "b": "1F6BA",
             "j": [
+                "bathroom",
                 "lavatory",
                 "restroom",
-                "wc",
+                "toilet",
+                "WC",
                 "woman",
                 "women’s room",
                 "women_s_room",
                 "purple-square",
                 "female",
-                "toilet",
                 "loo",
                 "gender"
             ]
@@ -18042,10 +18304,11 @@
             "a": "Restroom",
             "b": "1F6BB",
             "j": [
+                "bathroom",
                 "lavatory",
+                "toilet",
                 "WC",
                 "blue-square",
-                "toilet",
                 "refresh",
                 "wc",
                 "gender"
@@ -18065,12 +18328,13 @@
             "a": "Water Closet",
             "b": "1F6BE",
             "j": [
+                "bathroom",
                 "closet",
                 "lavatory",
                 "restroom",
-                "water",
-                "wc",
                 "toilet",
+                "water",
+                "WC",
                 "blue-square"
             ]
         },
@@ -18507,8 +18771,7 @@
             "b": "1F519",
             "j": [
                 "arrow",
-                "back",
-                "BACK arrow",
+                "BACK",
                 "words",
                 "return"
             ]
@@ -18518,8 +18781,7 @@
             "b": "1F51A",
             "j": [
                 "arrow",
-                "end",
-                "END arrow",
+                "END",
                 "words"
             ]
         },
@@ -18529,8 +18791,8 @@
             "j": [
                 "arrow",
                 "mark",
-                "on",
-                "ON! arrow",
+                "ON",
+                "ON!",
                 "words"
             ]
         },
@@ -18539,8 +18801,7 @@
             "b": "1F51C",
             "j": [
                 "arrow",
-                "soon",
-                "SOON arrow",
+                "SOON",
                 "words"
             ]
         },
@@ -18549,8 +18810,7 @@
             "b": "1F51D",
             "j": [
                 "arrow",
-                "top",
-                "TOP arrow",
+                "TOP",
                 "up",
                 "words",
                 "blue-square"
@@ -18692,6 +18952,15 @@
                 "hexagram"
             ]
         },
+        "khanda": {
+            "a": "⊛ Khanda",
+            "b": "1FAAF",
+            "j": [
+                "khanda",
+                "religion",
+                "Sikh"
+            ]
+        },
         "aries": {
             "a": "Aries",
             "b": "2648",
@@ -18969,7 +19238,6 @@
             "j": [
                 "arrow",
                 "button",
-                "red",
                 "blue-square",
                 "triangle",
                 "direction",
@@ -18996,7 +19264,6 @@
                 "arrow",
                 "button",
                 "down",
-                "red",
                 "blue-square",
                 "direction",
                 "bottom"
@@ -19106,6 +19373,16 @@
                 "bars"
             ]
         },
+        "wireless": {
+            "a": "⊛ Wireless",
+            "b": "1F6DC",
+            "j": [
+                "computer",
+                "internet",
+                "network",
+                "wireless"
+            ]
+        },
         "vibration-mode": {
             "a": "Vibration Mode",
             "b": "1F4F3",
@@ -19216,7 +19493,7 @@
             ]
         },
         "heavy-equals-sign": {
-            "a": "⊛ Heavy Equals Sign",
+            "a": "Heavy Equals Sign",
             "b": "1F7F0",
             "j": [
                 "equality",
@@ -19595,7 +19872,7 @@
             "a": "Copyright",
             "b": "00A9",
             "j": [
-                "c",
+                "C",
                 "ip",
                 "license",
                 "circle",
@@ -19607,7 +19884,7 @@
             "a": "Registered",
             "b": "00AE",
             "j": [
-                "r",
+                "R",
                 "alphabet",
                 "circle"
             ]
@@ -19617,7 +19894,7 @@
             "b": "2122",
             "j": [
                 "mark",
-                "tm",
+                "TM",
                 "trademark",
                 "brand",
                 "law",
@@ -19815,7 +20092,7 @@
             "a": "A Button (Blood Type)",
             "b": "1F170",
             "j": [
-                "a",
+                "A",
                 "A button (blood type)",
                 "blood type",
                 "a_button",
@@ -19828,7 +20105,7 @@
             "a": "Ab Button (Blood Type)",
             "b": "1F18E",
             "j": [
-                "ab",
+                "AB",
                 "AB button (blood type)",
                 "blood type",
                 "ab_button",
@@ -19840,7 +20117,7 @@
             "a": "B Button (Blood Type)",
             "b": "1F171",
             "j": [
-                "b",
+                "B",
                 "B button (blood type)",
                 "blood type",
                 "b_button",
@@ -19853,7 +20130,7 @@
             "a": "Cl Button",
             "b": "1F191",
             "j": [
-                "cl",
+                "CL",
                 "CL button",
                 "alphabet",
                 "words",
@@ -19864,7 +20141,7 @@
             "a": "Cool Button",
             "b": "1F192",
             "j": [
-                "cool",
+                "COOL",
                 "COOL button",
                 "words",
                 "blue-square"
@@ -19874,7 +20151,7 @@
             "a": "Free Button",
             "b": "1F193",
             "j": [
-                "free",
+                "FREE",
                 "FREE button",
                 "blue-square",
                 "words"
@@ -19894,7 +20171,7 @@
             "a": "Id Button",
             "b": "1F194",
             "j": [
-                "id",
+                "ID",
                 "ID button",
                 "identity",
                 "purple-square",
@@ -19907,7 +20184,7 @@
             "j": [
                 "circle",
                 "circled M",
-                "m",
+                "M",
                 "alphabet",
                 "blue-circle",
                 "letter"
@@ -19917,7 +20194,7 @@
             "a": "New Button",
             "b": "1F195",
             "j": [
-                "new",
+                "NEW",
                 "NEW button",
                 "blue-square",
                 "words",
@@ -19928,7 +20205,7 @@
             "a": "Ng Button",
             "b": "1F196",
             "j": [
-                "ng",
+                "NG",
                 "NG button",
                 "blue-square",
                 "words",
@@ -19941,7 +20218,7 @@
             "b": "1F17E",
             "j": [
                 "blood type",
-                "o",
+                "O",
                 "O button (blood type)",
                 "o_button",
                 "alphabet",
@@ -19965,6 +20242,7 @@
             "a": "P Button",
             "b": "1F17F",
             "j": [
+                "P",
                 "P button",
                 "parking",
                 "cars",
@@ -19978,7 +20256,7 @@
             "b": "1F198",
             "j": [
                 "help",
-                "sos",
+                "SOS",
                 "SOS button",
                 "red-square",
                 "words",
@@ -19991,7 +20269,8 @@
             "b": "1F199",
             "j": [
                 "mark",
-                "up",
+                "UP",
+                "UP!",
                 "UP! button",
                 "blue-square",
                 "above",
@@ -20003,7 +20282,7 @@
             "b": "1F19A",
             "j": [
                 "versus",
-                "vs",
+                "VS",
                 "VS button",
                 "words",
                 "orange-square"
diff --git a/vector/src/main/res/raw/emoji_picker_datasource.json b/vector/src/main/res/raw/emoji_picker_datasource.json
index f8b05cb15e..66d3691ed3 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","hot","heat"]},"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","silence","secret","shock"]},"face-with-peeking-eye":{"a":"⊛ Face with Peeking Eye","b":"1FAE3","j":["captivated","peep","stare","scared","frightening","embarrassing","shy"]},"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","respect"]},"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","lonely","isolation","depression"]},"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","covid"]},"face-with-thermometer":{"a":"Face with Thermometer","b":"1F912","j":["face","ill","sick","thermometer","temperature","cold","fever","covid"]},"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","skeptic","confuse","frustrated","indifferent"]},"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","touched","gratitude"]},"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","palm","offer"]},"leftwards-hand":{"a":"⊛ Leftwards Hand","b":"1FAF2","j":["hand","left","leftward","palm","offer"]},"palm-down-hand":{"a":"⊛ Palm Down Hand","b":"1FAF3","j":["dismiss","drop","shoo","palm"]},"palm-up-hand":{"a":"⊛ Palm Up Hand","b":"1FAF4","j":["beckon","catch","come","offer","lift","demand"]},"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","recruit"]},"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","appreciation","support"]},"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","thank you","appreciate"]},"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","flirt","sexy","pain","worry"]},"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","power"]},"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","baby"]},"pregnant-person":{"a":"⊛ Pregnant Person","b":"1FAC4","j":["belly","bloated","full","pregnant","baby"]},"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","mystical"]},"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","sea"]},"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","covid"]},"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","calm","meditation"]},"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","rose"]},"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","bird"]},"nest-with-eggs":{"a":"⊛ Nest with Eggs","b":"1FABA","j":["nesting","bird"]},"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","france","bakery"]},"flatbread":{"a":"Flatbread","b":"1FAD3","j":["arepa","lavash","naan","pita","flour","food","bakery"]},"pretzel":{"a":"Pretzel","b":"1F968","j":["twisted","convoluted","food","bread","germany","bakery"]},"bagel":{"a":"Bagel","b":"1F96F","j":["bakery","breakfast","schmear","food","bread","jewish"]},"pancakes":{"a":"Pancakes","b":"1F95E","j":["breakfast","crêpe","food","hotcake","pancake","flapjacks","hotcakes","brunch"]},"waffle":{"a":"Waffle","b":"1F9C7","j":["breakfast","indecisive","iron","food","brunch"]},"cheese-wedge":{"a":"Cheese Wedge","b":"1F9C0","j":["cheese","food","chadder","swiss"]},"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","brunch"]},"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","potato"]},"pizza":{"a":"Pizza","b":"1F355","j":["cheese","slice","food","party","italy"]},"hot-dog":{"a":"Hot Dog","b":"1F32D","j":["frankfurter","hotdog","sausage","food","america"]},"sandwich":{"a":"Sandwich","b":"1F96A","j":["bread","food","lunch","toast","bakery"]},"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","mediterranean"]},"falafel":{"a":"Falafel","b":"1F9C6","j":["chickpea","meatball","food","mediterranean"]},"egg":{"a":"Egg","b":"1F95A","j":["breakfast","food","chicken"]},"cooking":{"a":"Cooking","b":"1F373","j":["breakfast","egg","frying","pan","food","kitchen","skillet"]},"shallow-pan-of-food":{"a":"Shallow Pan of Food","b":"1F958","j":["casserole","food","paella","pan","shallow","cooking","skillet"]},"pot-of-food":{"a":"Pot of Food","b":"1F372","j":["pot","stew","food","meat","soup","hot pot"]},"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","vegetable"]},"popcorn":{"a":"Popcorn","b":"1F37F","j":["food","movie theater","films","snack","drama"]},"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","tomatoes"]},"bento-box":{"a":"Bento Box","b":"1F371","j":["bento","box","food","japanese","lunch"]},"rice-cracker":{"a":"Rice Cracker","b":"1F358","j":["cracker","rice","food","japanese","snack"]},"rice-ball":{"a":"Rice Ball","b":"1F359","j":["ball","Japanese","rice","food","japanese"]},"cooked-rice":{"a":"Cooked Rice","b":"1F35A","j":["cooked","rice","food","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","plant"]},"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","dessert"]},"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","gyoza"]},"fortune-cookie":{"a":"Fortune Cookie","b":"1F960","j":["prophecy","food","dessert"]},"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","mug"]},"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","water"]},"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","fun","park"]},"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","car","transport"]},"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","religion"]},"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","drained","dead"]},"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","accessibility","assist"]},"stethoscope":{"a":"Stethoscope","b":"1FA7A","j":["doctor","heart","medicine","health"]},"xray":{"a":"⊛ X-Ray","b":"1FA7B","j":["bones","doctor","medical","skeleton","x-ray","medicine"]},"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","fun","carbonation","sparkling"]},"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","document"]},"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","jp","ja"]},"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","shaking-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","enraged-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","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","pink-heart","orange-heart","yellow-heart","green-heart","blue-heart","light-blue-heart","purple-heart","brown-heart","black-heart","grey-heart","white-heart","kiss-mark","hundred-points","anger-symbol","collision","dizzy","sweat-droplets","dashing-away","hole","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","leftwards-pushing-hand","rightwards-pushing-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","moose","donkey","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","wing","black-bird","goose","frog","crocodile","turtle","lizard","snake","dragon-face","dragon","sauropod","trex","spouting-whale","whale","dolphin","seal","fish","tropical-fish","blowfish","shark","octopus","spiral-shell","coral","jellyfish","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","hyacinth","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","mushroom"]},{"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","peanuts","beans","chestnut","ginger-root","pea-pod","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","water-pistol","pool-8-ball","crystal-ball","magic-wand","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","folding-hand-fan","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","hair-pick","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","maracas","flute","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","bomb","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","nazar-amulet","hamsa","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","khanda","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","wireless","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","hot","heat"]},"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","silence","secret","shock"]},"face-with-peeking-eye":{"a":"Face with Peeking Eye","b":"1FAE3","j":["captivated","peep","stare","scared","frightening","embarrassing","shy"]},"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","respect"]},"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","lonely","isolation","depression"]},"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"]},"shaking-face":{"a":"⊛ Shaking Face","b":"1FAE8","j":["earthquake","face","shaking","shock","vibrate"]},"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","good night","sleep","tired","rest","nap"]},"drooling-face":{"a":"Drooling Face","b":"1F924","j":["drooling","face"]},"sleeping-face":{"a":"Sleeping Face","b":"1F634","j":["face","good night","sleep","ZZZ","tired","sleepy","night","zzz"]},"face-with-medical-mask":{"a":"Face with Medical Mask","b":"1F637","j":["cold","doctor","face","mask","sick","ill","disease","covid"]},"face-with-thermometer":{"a":"Face with Thermometer","b":"1F912","j":["face","ill","sick","thermometer","temperature","cold","fever","covid"]},"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":["face","monocle","stuffy","wealthy"]},"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","skeptic","confuse","frustrated","indifferent"]},"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","touched","gratitude"]},"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"]},"enraged-face":{"a":"Enraged Face","b":"1F621","j":["angry","enraged","face","mad","pouting","rage","red","pouting_face","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"]},"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"]},"pink-heart":{"a":"⊛ Pink Heart","b":"1FA77","j":["cute","heart","like","love","pink"]},"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"]},"light-blue-heart":{"a":"⊛ Light Blue Heart","b":"1FA75","j":["cyan","heart","light blue","light blue heart","teal"]},"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"]},"grey-heart":{"a":"⊛ Grey Heart","b":"1FA76","j":["gray","grey heart","heart","silver","slate"]},"white-heart":{"a":"White Heart","b":"1F90D","j":["heart","white","pure"]},"kiss-mark":{"a":"Kiss Mark","b":"1F48B","j":["kiss","lips","face","love","like","affection","valentines"]},"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"]},"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":["balloon","bubble","eye","speech","witness","info"]},"left-speech-bubble":{"a":"Left Speech Bubble","b":"1F5E8","j":["balloon","bubble","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","good night","sleep","ZZZ","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","palm","offer"]},"leftwards-hand":{"a":"Leftwards Hand","b":"1FAF2","j":["hand","left","leftward","palm","offer"]},"palm-down-hand":{"a":"Palm Down Hand","b":"1FAF3","j":["dismiss","drop","shoo","palm"]},"palm-up-hand":{"a":"Palm Up Hand","b":"1FAF4","j":["beckon","catch","come","offer","lift","demand"]},"leftwards-pushing-hand":{"a":"⊛ Leftwards Pushing Hand","b":"1FAF7","j":["high five","leftward","leftwards pushing hand","push","refuse","stop","wait"]},"rightwards-pushing-hand":{"a":"⊛ Rightwards Pushing Hand","b":"1FAF8","j":["high five","push","refuse","rightward","rightwards pushing hand","stop","wait"]},"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","hang loose","Shaka","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","recruit"]},"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","appreciation","support"]},"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","thank you","appreciate"]},"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","flirt","sexy","pain","worry"]},"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","power"]},"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","baby"]},"pregnant-person":{"a":"Pregnant Person","b":"1FAC4","j":["belly","bloated","full","pregnant","baby"]},"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","mystical"]},"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":["good night","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"]},"moose":{"a":"⊛ Moose","b":"1FACE","j":["animal","antlers","elk","mammal","moose"]},"donkey":{"a":"⊛ Donkey","b":"1FACF","j":["animal","ass","burro","donkey","mammal","mule","stubborn"]},"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"]},"wing":{"a":"⊛ Wing","b":"1FABD","j":["angelic","aviation","bird","flying","mythology","wing"]},"black-bird":{"a":"⊛ Black Bird","b":"1F426-200D-2B1B","j":["bird","black","crow","raven","rook"]},"goose":{"a":"⊛ Goose","b":"1FABF","j":["bird","fowl","goose","honk","silly"]},"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","sea"]},"jellyfish":{"a":"⊛ Jellyfish","b":"1FABC","j":["burn","invertebrate","jelly","jellyfish","marine","ouch","stinger"]},"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","covid"]},"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","calm","meditation"]},"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","rose"]},"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"]},"hyacinth":{"a":"⊛ Hyacinth","b":"1FABB","j":["bluebonnet","flower","hyacinth","lavender","lupine","snapdragon"]},"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","bird"]},"nest-with-eggs":{"a":"Nest with Eggs","b":"1FABA","j":["nesting","bird"]},"mushroom":{"a":"Mushroom","b":"1F344","j":["toadstool","plant","vegetable"]},"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"]},"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"]},"ginger-root":{"a":"⊛ Ginger Root","b":"1FADA","j":["beer","ginger root","root","spice"]},"pea-pod":{"a":"⊛ Pea Pod","b":"1FADB","j":["beans","edamame","legume","pea","pod","vegetable"]},"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","france","bakery"]},"flatbread":{"a":"Flatbread","b":"1FAD3","j":["arepa","lavash","naan","pita","flour","food","bakery"]},"pretzel":{"a":"Pretzel","b":"1F968","j":["twisted","convoluted","food","bread","germany","bakery"]},"bagel":{"a":"Bagel","b":"1F96F","j":["bakery","breakfast","schmear","food","bread","jewish"]},"pancakes":{"a":"Pancakes","b":"1F95E","j":["breakfast","crêpe","food","hotcake","pancake","flapjacks","hotcakes","brunch"]},"waffle":{"a":"Waffle","b":"1F9C7","j":["breakfast","indecisive","iron","food","brunch"]},"cheese-wedge":{"a":"Cheese Wedge","b":"1F9C0","j":["cheese","food","chadder","swiss"]},"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","brunch"]},"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","potato"]},"pizza":{"a":"Pizza","b":"1F355","j":["cheese","slice","food","party","italy"]},"hot-dog":{"a":"Hot Dog","b":"1F32D","j":["frankfurter","hotdog","sausage","food","america"]},"sandwich":{"a":"Sandwich","b":"1F96A","j":["bread","food","lunch","toast","bakery"]},"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","mediterranean"]},"falafel":{"a":"Falafel","b":"1F9C6","j":["chickpea","meatball","food","mediterranean"]},"egg":{"a":"Egg","b":"1F95A","j":["breakfast","food","chicken"]},"cooking":{"a":"Cooking","b":"1F373","j":["breakfast","egg","frying","pan","food","kitchen","skillet"]},"shallow-pan-of-food":{"a":"Shallow Pan of Food","b":"1F958","j":["casserole","food","paella","pan","shallow","cooking","skillet"]},"pot-of-food":{"a":"Pot of Food","b":"1F372","j":["pot","stew","food","meat","soup","hot pot"]},"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","vegetable"]},"popcorn":{"a":"Popcorn","b":"1F37F","j":["food","movie theater","films","snack","drama"]},"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","tomatoes"]},"bento-box":{"a":"Bento Box","b":"1F371","j":["bento","box","food","japanese","lunch"]},"rice-cracker":{"a":"Rice Cracker","b":"1F358","j":["cracker","rice","food","japanese","snack"]},"rice-ball":{"a":"Rice Ball","b":"1F359","j":["ball","Japanese","rice","food","japanese"]},"cooked-rice":{"a":"Cooked Rice","b":"1F35A","j":["cooked","rice","food","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","plant"]},"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","dessert"]},"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","gyoza"]},"fortune-cookie":{"a":"Fortune Cookie","b":"1F960","j":["prophecy","food","dessert"]},"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","mug"]},"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","water"]},"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","fun","park"]},"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","car","transport"]},"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"]},"water-pistol":{"a":"Water Pistol","b":"1F52B","j":["gun","handgun","pistol","revolver","tool","water","weapon","violence"]},"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"]},"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"]},"folding-hand-fan":{"a":"⊛ Folding Hand Fan","b":"1FAAD","j":["cooling","dance","fan","flutter","folding hand fan","hot","shy"]},"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"]},"hair-pick":{"a":"⊛ Hair Pick","b":"1FAAE","j":["Afro","comb","hair","pick"]},"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"]},"maracas":{"a":"⊛ Maracas","b":"1FA87","j":["instrument","maracas","music","percussion","rattle","shake"]},"flute":{"a":"⊛ Flute","b":"1FA88","j":["fife","flute","music","pipe","recorder","woodwind"]},"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","drained","dead"]},"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","DVD","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"]},"bomb":{"a":"Bomb","b":"1F4A3","j":["comic","boom","explode","explosion","terrorism"]},"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","accessibility","assist"]},"stethoscope":{"a":"Stethoscope","b":"1FA7A","j":["doctor","heart","medicine","health"]},"xray":{"a":"X-Ray","b":"1FA7B","j":["bones","doctor","medical","skeleton","x-ray","medicine"]},"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","fun","carbonation","sparkling"]},"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"]},"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","religion"]},"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","document"]},"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":["bathroom","lavatory","man","men’s room","restroom","toilet","WC","men_s_room","wc","blue-square","gender","male"]},"womens-room":{"a":"Women’S Room","b":"1F6BA","j":["bathroom","lavatory","restroom","toilet","WC","woman","women’s room","women_s_room","purple-square","female","loo","gender"]},"restroom":{"a":"Restroom","b":"1F6BB","j":["bathroom","lavatory","toilet","WC","blue-square","refresh","wc","gender"]},"baby-symbol":{"a":"Baby Symbol","b":"1F6BC","j":["baby","changing","orange-square","child"]},"water-closet":{"a":"Water Closet","b":"1F6BE","j":["bathroom","closet","lavatory","restroom","toilet","water","WC","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","words","return"]},"end-arrow":{"a":"End Arrow","b":"1F51A","j":["arrow","END","words"]},"on-arrow":{"a":"On! Arrow","b":"1F51B","j":["arrow","mark","ON","ON!","words"]},"soon-arrow":{"a":"Soon Arrow","b":"1F51C","j":["arrow","SOON","words"]},"top-arrow":{"a":"Top Arrow","b":"1F51D","j":["arrow","TOP","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"]},"khanda":{"a":"⊛ Khanda","b":"1FAAF","j":["khanda","religion","Sikh"]},"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","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","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"]},"wireless":{"a":"⊛ Wireless","b":"1F6DC","j":["computer","internet","network","wireless"]},"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","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!","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","jp","ja"]},"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 e76793781dd4ff969cff34c4c57846aae3e9536b Mon Sep 17 00:00:00 2001
From: Florian Renaud <florianr@element.io>
Date: Mon, 12 Sep 2022 17:51:38 +0200
Subject: [PATCH 073/108] Use LocalRoomSummaryEntity.where extension

---
 .../session/room/create/CreateRoomFromLocalRoomTask.kt      | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt
index 02538a5cc3..d73ffbfbe3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt
@@ -17,7 +17,6 @@
 package org.matrix.android.sdk.internal.session.room.create
 
 import com.zhuinden.monarchy.Monarchy
-import io.realm.kotlin.where
 import kotlinx.coroutines.TimeoutCancellationException
 import org.matrix.android.sdk.api.extensions.orFalse
 import org.matrix.android.sdk.api.query.QueryStringValue
@@ -36,11 +35,11 @@ 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.LocalRoomSummaryEntity
-import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntityFields
 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.copyToRealmOrIgnore
 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.di.SessionDatabase
 import org.matrix.android.sdk.internal.di.UserId
@@ -86,8 +85,7 @@ internal class DefaultCreateRoomFromLocalRoomTask @Inject constructor(
         var createRoomParams: CreateRoomParams? = null
         var isEncrypted = false
         monarchy.doWithRealm { realm ->
-            realm.where<LocalRoomSummaryEntity>()
-                    .equalTo(LocalRoomSummaryEntityFields.ROOM_ID, params.localRoomId)
+            LocalRoomSummaryEntity.where(realm, params.localRoomId)
                     .findFirst()
                     ?.let {
                         createRoomParams = it.createRoomParams

From 824a4bcae5f47cb83ac3b1a1aeb16b2eebd1e120 Mon Sep 17 00:00:00 2001
From: Florian Renaud <florianr@element.io>
Date: Mon, 12 Sep 2022 17:52:27 +0200
Subject: [PATCH 074/108] Add comment to explain the replacementRoom behaviour

---
 .../internal/session/room/create/CreateRoomFromLocalRoomTask.kt  | 1 +
 1 file changed, 1 insertion(+)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt
index d73ffbfbe3..246b6aa241 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt
@@ -74,6 +74,7 @@ internal class DefaultCreateRoomFromLocalRoomTask @Inject constructor(
         get() = monarchy.realmConfiguration
 
     override suspend fun execute(params: CreateRoomFromLocalRoomTask.Params): String {
+        // If a room has already been created for the given local room, return the existing roomId
         val replacementRoomId = stateEventDataSource.getStateEvent(params.localRoomId, EventType.STATE_ROOM_TOMBSTONE, QueryStringValue.IsEmpty)
                 ?.content.toModel<RoomTombstoneContent>()
                 ?.replacementRoomId

From e2f0e14133f8783dd5608eb73a9021b753d5ba57 Mon Sep 17 00:00:00 2001
From: Florian Renaud <florianr@element.io>
Date: Tue, 13 Sep 2022 23:57:41 +0200
Subject: [PATCH 075/108] Start DM - Add loading wheel while creating the room

---
 .../org/matrix/android/sdk/flow/FlowRoom.kt   |   8 ++
 .../android/sdk/api/session/room/Room.kt      |  12 ++
 .../sdk/api/session/room/RoomService.kt       |   7 +
 .../room/model/LocalRoomCreationState.kt      |  24 ++++
 .../session/room/model/LocalRoomSummary.kt    |  46 ++++++
 .../database/RealmSessionStoreMigration.kt    |   4 +-
 .../database/mapper/LocalRoomSummaryMapper.kt |  36 +++++
 .../database/migration/MigrateSessionTo037.kt |  34 +++++
 .../database/model/LocalRoomSummaryEntity.kt  |  11 +-
 .../query/LocalRoomSummaryEntityQueries.kt    |   8 +-
 .../sdk/internal/session/room/DefaultRoom.kt  |   9 ++
 .../session/room/DefaultRoomService.kt        |   5 +
 .../create/CreateRoomFromLocalRoomTask.kt     | 123 +++++++---------
 .../room/summary/RoomSummaryDataSource.kt     |  23 +++
 .../DefaultCreateRoomFromLocalRoomTaskTest.kt | 131 ++++++++++++------
 .../android/sdk/test/fakes/FakeMonarchy.kt    |   5 +
 .../test/fakes/FakeRoomSummaryDataSource.kt   |  37 +++++
 .../home/room/detail/RoomDetailViewEvents.kt  |   2 +-
 .../home/room/detail/TimelineFragment.kt      |   2 +-
 .../home/room/detail/TimelineViewModel.kt     |  47 ++++---
 20 files changed, 429 insertions(+), 145 deletions(-)
 create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/LocalRoomCreationState.kt
 create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/LocalRoomSummary.kt
 create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/LocalRoomSummaryMapper.kt
 create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo037.kt
 create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRoomSummaryDataSource.kt

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 8be8e83569..a6b4cc98a6 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
@@ -25,6 +25,7 @@ import org.matrix.android.sdk.api.session.room.getStateEvent
 import org.matrix.android.sdk.api.session.room.getTimelineEvent
 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.LocalRoomSummary
 import org.matrix.android.sdk.api.session.room.model.ReadReceipt
 import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
 import org.matrix.android.sdk.api.session.room.model.RoomSummary
@@ -46,6 +47,13 @@ class FlowRoom(private val room: Room) {
                 }
     }
 
+    fun liveLocalRoomSummary(): Flow<Optional<LocalRoomSummary>> {
+        return room.getLocalRoomSummaryLive().asFlow()
+                .startWith(room.coroutineDispatchers.io) {
+                    room.localRoomSummary().toOptional()
+                }
+    }
+
     fun liveRoomMembers(queryParams: RoomMemberQueryParams): Flow<List<RoomMemberSummary>> {
         return room.membershipService().getRoomMembersLive(queryParams).asFlow()
                 .startWith(room.coroutineDispatchers.io) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt
index 5d2769ac3c..8031fcaeea 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt
@@ -24,6 +24,7 @@ import org.matrix.android.sdk.api.session.room.call.RoomCallService
 import org.matrix.android.sdk.api.session.room.crypto.RoomCryptoService
 import org.matrix.android.sdk.api.session.room.location.LocationSharingService
 import org.matrix.android.sdk.api.session.room.members.MembershipService
+import org.matrix.android.sdk.api.session.room.model.LocalRoomSummary
 import org.matrix.android.sdk.api.session.room.model.RoomSummary
 import org.matrix.android.sdk.api.session.room.model.relation.RelationService
 import org.matrix.android.sdk.api.session.room.notification.RoomPushRuleService
@@ -60,11 +61,22 @@ interface Room {
      */
     fun getRoomSummaryLive(): LiveData<Optional<RoomSummary>>
 
+    /**
+     * A live [LocalRoomSummary] associated with the room.
+     * You can observe this summary to get dynamic data from this room.
+     */
+    fun getLocalRoomSummaryLive(): LiveData<Optional<LocalRoomSummary>>
+
     /**
      * A current snapshot of [RoomSummary] associated with the room.
      */
     fun roomSummary(): RoomSummary?
 
+    /**
+     * A current snapshot of [LocalRoomSummary] associated with the room.
+     */
+    fun localRoomSummary(): LocalRoomSummary?
+
     /**
      * Use this room as a Space, if the type is correct.
      */
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt
index ad8106c9c1..65383f1007 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt
@@ -22,6 +22,7 @@ import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.identity.model.SignInvitationResult
 import org.matrix.android.sdk.api.session.room.alias.RoomAliasDescription
 import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
+import org.matrix.android.sdk.api.session.room.model.LocalRoomSummary
 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
@@ -117,6 +118,12 @@ interface RoomService {
      */
     fun getRoomSummaryLive(roomId: String): LiveData<Optional<RoomSummary>>
 
+    /**
+     * A live [LocalRoomSummary] associated with the room with id [roomId].
+     * You can observe this summary to get dynamic data from this room, even if the room is not joined yet
+     */
+    fun getLocalRoomSummaryLive(roomId: String): LiveData<Optional<LocalRoomSummary>>
+
     /**
      * Get a snapshot list of room summaries.
      * @return the immutable list of [RoomSummary]
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/LocalRoomCreationState.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/LocalRoomCreationState.kt
new file mode 100644
index 0000000000..4fc99225c4
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/LocalRoomCreationState.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.api.session.room.model
+
+enum class LocalRoomCreationState {
+    NOT_CREATED,
+    CREATING,
+    FAILURE,
+    CREATED
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/LocalRoomSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/LocalRoomSummary.kt
new file mode 100644
index 0000000000..eced1dd581
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/LocalRoomSummary.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.api.session.room.model
+
+import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
+
+/**
+ * This class holds some data of a local room.
+ * It can be retrieved by [org.matrix.android.sdk.api.session.room.Room] and [org.matrix.android.sdk.api.session.room.RoomService]
+ */
+data class LocalRoomSummary(
+        /**
+         * The roomId of the room.
+         */
+        val roomId: String,
+        /**
+         * The room summary of the room.
+         */
+        val roomSummary: RoomSummary?,
+        /**
+         * The creation params attached to the room.
+         */
+        val createRoomParams: CreateRoomParams?,
+        /**
+         * The roomId of the created room (ie. created on the server), if any.
+         */
+        val replacementRoomId: String?,
+        /**
+         * The creation state of the room.
+         */
+        val creationState: LocalRoomCreationState,
+)
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 0b11863864..2693ca474c 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
@@ -53,6 +53,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo033
 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo034
 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo035
 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo036
+import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo037
 import org.matrix.android.sdk.internal.util.Normalizer
 import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration
 import javax.inject.Inject
@@ -61,7 +62,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
         private val normalizer: Normalizer
 ) : MatrixRealmMigration(
         dbName = "Session",
-        schemaVersion = 36L,
+        schemaVersion = 37L,
 ) {
     /**
      * Forces all RealmSessionStoreMigration instances to be equal.
@@ -107,5 +108,6 @@ internal class RealmSessionStoreMigration @Inject constructor(
         if (oldVersion < 34) MigrateSessionTo034(realm).perform()
         if (oldVersion < 35) MigrateSessionTo035(realm).perform()
         if (oldVersion < 36) MigrateSessionTo036(realm).perform()
+        if (oldVersion < 37) MigrateSessionTo037(realm).perform()
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/LocalRoomSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/LocalRoomSummaryMapper.kt
new file mode 100644
index 0000000000..09cb5985f3
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/LocalRoomSummaryMapper.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.database.mapper
+
+import org.matrix.android.sdk.api.session.room.model.LocalRoomSummary
+import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntity
+import javax.inject.Inject
+
+internal class LocalRoomSummaryMapper @Inject constructor(
+        private val roomSummaryMapper: RoomSummaryMapper,
+) {
+
+    fun map(localRoomSummaryEntity: LocalRoomSummaryEntity): LocalRoomSummary {
+        return LocalRoomSummary(
+                roomId = localRoomSummaryEntity.roomId,
+                roomSummary = localRoomSummaryEntity.roomSummaryEntity?.let { roomSummaryMapper.map(it) },
+                createRoomParams = localRoomSummaryEntity.createRoomParams,
+                replacementRoomId = localRoomSummaryEntity.replacementRoomId,
+                creationState = localRoomSummaryEntity.creationState
+        )
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo037.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo037.kt
new file mode 100644
index 0000000000..cdb0b6c682
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo037.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.api.session.room.model.LocalRoomCreationState
+import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntityFields
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+
+internal class MigrateSessionTo037(realm: DynamicRealm) : RealmMigrator(realm, 37) {
+
+    override fun doMigrate(realm: DynamicRealm) {
+        realm.schema.get("LocalRoomSummaryEntity")
+                ?.addField(LocalRoomSummaryEntityFields.REPLACEMENT_ROOM_ID, String::class.java)
+                ?.addField(LocalRoomSummaryEntityFields.STATE_STR, String::class.java)
+                ?.transform { obj ->
+                    obj.set(LocalRoomSummaryEntityFields.STATE_STR, LocalRoomCreationState.NOT_CREATED.name)
+                }
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/LocalRoomSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/LocalRoomSummaryEntity.kt
index fd8331e986..a978e3719d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/LocalRoomSummaryEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/LocalRoomSummaryEntity.kt
@@ -18,15 +18,24 @@ package org.matrix.android.sdk.internal.database.model
 
 import io.realm.RealmObject
 import io.realm.annotations.PrimaryKey
+import org.matrix.android.sdk.api.session.room.model.LocalRoomCreationState
 import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
 import org.matrix.android.sdk.api.session.room.model.create.toJSONString
 
 internal open class LocalRoomSummaryEntity(
         @PrimaryKey var roomId: String = "",
         var roomSummaryEntity: RoomSummaryEntity? = null,
-        private var createRoomParamsStr: String? = null
+        var replacementRoomId: String? = null,
 ) : RealmObject() {
 
+    private var stateStr: String = LocalRoomCreationState.NOT_CREATED.name
+    var creationState: LocalRoomCreationState
+        get() = LocalRoomCreationState.valueOf(stateStr)
+        set(value) {
+            stateStr = value.name
+        }
+
+    private var createRoomParamsStr: String? = null
     var createRoomParams: CreateRoomParams?
         get() {
             return CreateRoomParams.fromJson(createRoomParamsStr)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LocalRoomSummaryEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LocalRoomSummaryEntityQueries.kt
index 527350bedc..44730eb75d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LocalRoomSummaryEntityQueries.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LocalRoomSummaryEntityQueries.kt
@@ -22,10 +22,6 @@ import io.realm.kotlin.where
 import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntity
 import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntityFields
 
-internal fun LocalRoomSummaryEntity.Companion.where(realm: Realm, roomId: String? = null): RealmQuery<LocalRoomSummaryEntity> {
-    val query = realm.where<LocalRoomSummaryEntity>()
-    if (roomId != null) {
-        query.equalTo(LocalRoomSummaryEntityFields.ROOM_ID, roomId)
-    }
-    return query
+internal fun LocalRoomSummaryEntity.Companion.where(realm: Realm, roomId: String): RealmQuery<LocalRoomSummaryEntity> {
+    return realm.where<LocalRoomSummaryEntity>().equalTo(LocalRoomSummaryEntityFields.ROOM_ID, roomId)
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt
index abea2d34cd..262c111b73 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt
@@ -25,6 +25,7 @@ import org.matrix.android.sdk.api.session.room.call.RoomCallService
 import org.matrix.android.sdk.api.session.room.crypto.RoomCryptoService
 import org.matrix.android.sdk.api.session.room.location.LocationSharingService
 import org.matrix.android.sdk.api.session.room.members.MembershipService
+import org.matrix.android.sdk.api.session.room.model.LocalRoomSummary
 import org.matrix.android.sdk.api.session.room.model.RoomSummary
 import org.matrix.android.sdk.api.session.room.model.RoomType
 import org.matrix.android.sdk.api.session.room.model.relation.RelationService
@@ -82,6 +83,14 @@ internal class DefaultRoom(
         return roomSummaryDataSource.getRoomSummary(roomId)
     }
 
+    override fun getLocalRoomSummaryLive(): LiveData<Optional<LocalRoomSummary>> {
+        return roomSummaryDataSource.getLocalRoomSummaryLive(roomId)
+    }
+
+    override fun localRoomSummary(): LocalRoomSummary? {
+        return roomSummaryDataSource.getLocalRoomSummary(roomId)
+    }
+
     override fun asSpace(): Space? {
         if (roomSummary()?.roomType != RoomType.SPACE) return null
         return DefaultSpace(this, roomSummaryDataSource, viaParameterFinder)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt
index 989bcaee44..381e331540 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt
@@ -29,6 +29,7 @@ import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
 import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult
 import org.matrix.android.sdk.api.session.room.alias.RoomAliasDescription
 import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
+import org.matrix.android.sdk.api.session.room.model.LocalRoomSummary
 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
@@ -106,6 +107,10 @@ internal class DefaultRoomService @Inject constructor(
         return roomSummaryDataSource.getRoomSummaryLive(roomId)
     }
 
+    override fun getLocalRoomSummaryLive(roomId: String): LiveData<Optional<LocalRoomSummary>> {
+        return roomSummaryDataSource.getLocalRoomSummaryLive(roomId)
+    }
+
     override fun getRoomSummaries(
             queryParams: RoomSummaryQueryParams,
             sortOrder: RoomSortOrder
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt
index 246b6aa241..57ffe7fb0c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt
@@ -18,36 +18,22 @@ package org.matrix.android.sdk.internal.session.room.create
 
 import com.zhuinden.monarchy.Monarchy
 import kotlinx.coroutines.TimeoutCancellationException
-import org.matrix.android.sdk.api.extensions.orFalse
-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.events.model.EventType
-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.failure.CreateRoomFailure
-import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
-import org.matrix.android.sdk.api.session.room.model.tombstone.RoomTombstoneContent
-import org.matrix.android.sdk.api.session.room.send.SendState
+import org.matrix.android.sdk.api.session.room.model.LocalRoomCreationState
+import org.matrix.android.sdk.api.session.room.model.LocalRoomSummary
+import org.matrix.android.sdk.api.session.room.model.RoomSummary
 import org.matrix.android.sdk.internal.database.awaitNotEmptyResult
-import org.matrix.android.sdk.internal.database.mapper.toEntity
-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.LocalRoomSummaryEntity
 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.copyToRealmOrIgnore
-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.di.SessionDatabase
-import org.matrix.android.sdk.internal.di.UserId
-import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource
+import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
 import org.matrix.android.sdk.internal.task.Task
-import org.matrix.android.sdk.internal.util.awaitTransaction
-import org.matrix.android.sdk.internal.util.time.Clock
-import java.util.UUID
 import java.util.concurrent.TimeUnit
 import javax.inject.Inject
 
@@ -55,94 +41,85 @@ import javax.inject.Inject
  * Create a room on the server from a local room.
  * The configuration of the local room will be use to configure the new room.
  * The potential local room members will also be invited to this new room.
- *
- * A local tombstone event will be created to indicate that the local room has been replacing by the new one.
  */
 internal interface CreateRoomFromLocalRoomTask : Task<CreateRoomFromLocalRoomTask.Params, String> {
     data class Params(val localRoomId: String)
 }
 
 internal class DefaultCreateRoomFromLocalRoomTask @Inject constructor(
-        @UserId private val userId: String,
         @SessionDatabase private val monarchy: Monarchy,
         private val createRoomTask: CreateRoomTask,
-        private val stateEventDataSource: StateEventDataSource,
-        private val clock: Clock,
+        private val roomSummaryDataSource: RoomSummaryDataSource,
 ) : CreateRoomFromLocalRoomTask {
 
     private val realmConfiguration
         get() = monarchy.realmConfiguration
 
     override suspend fun execute(params: CreateRoomFromLocalRoomTask.Params): String {
+        val localRoomSummary = roomSummaryDataSource.getLocalRoomSummary(params.localRoomId)
+                ?.takeIf { it.createRoomParams != null && it.roomSummary != null }
+                ?: error("Invalid LocalRoomSummary for ${params.localRoomId}")
+
         // If a room has already been created for the given local room, return the existing roomId
-        val replacementRoomId = stateEventDataSource.getStateEvent(params.localRoomId, EventType.STATE_ROOM_TOMBSTONE, QueryStringValue.IsEmpty)
-                ?.content.toModel<RoomTombstoneContent>()
-                ?.replacementRoomId
-
-        if (replacementRoomId != null) {
-            return replacementRoomId
+        if (localRoomSummary.replacementRoomId != null) {
+            return localRoomSummary.replacementRoomId
         }
 
-        var createRoomParams: CreateRoomParams? = null
-        var isEncrypted = false
-        monarchy.doWithRealm { realm ->
-            LocalRoomSummaryEntity.where(realm, params.localRoomId)
-                    .findFirst()
-                    ?.let {
-                        createRoomParams = it.createRoomParams
-                        isEncrypted = it.roomSummaryEntity?.isEncrypted.orFalse()
-                    }
-        }
-        val roomId = createRoomTask.execute(createRoomParams!!)
+        return createRoom(localRoomSummary)
+    }
 
+    private suspend fun createRoom(localRoomSummary: LocalRoomSummary): String {
+        updateCreationState(localRoomSummary.roomId, LocalRoomCreationState.CREATING)
+        val replacementRoomId = runCatching {
+            createRoomTask.execute(localRoomSummary.createRoomParams!!)
+        }.fold(
+                { it },
+                {
+                    updateCreationState(roomId = localRoomSummary.roomId, LocalRoomCreationState.FAILURE)
+                    throw it
+                }
+        )
+        updateReplacementRoomId(localRoomSummary.roomId, replacementRoomId)
+        waitForRoomEvents(replacementRoomId, localRoomSummary.roomSummary!!)
+        updateCreationState(localRoomSummary.roomId, LocalRoomCreationState.CREATED)
+        return replacementRoomId
+    }
+
+    /**
+     * Wait for all the room events before triggering the created state.
+     */
+    private suspend fun waitForRoomEvents(replacementRoomId: String, roomSummary: RoomSummary) {
         try {
-            // Wait for all the room events before triggering the replacement room
             awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm ->
                 realm.where(RoomSummaryEntity::class.java)
-                        .equalTo(RoomSummaryEntityFields.ROOM_ID, roomId)
-                        .equalTo(RoomSummaryEntityFields.INVITED_MEMBERS_COUNT, createRoomParams?.invitedUserIds?.size ?: 0)
+                        .equalTo(RoomSummaryEntityFields.ROOM_ID, replacementRoomId)
+                        .equalTo(RoomSummaryEntityFields.INVITED_MEMBERS_COUNT, roomSummary.invitedMembersCount)
             }
             awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm ->
-                EventEntity.whereRoomId(realm, roomId)
+                EventEntity.whereRoomId(realm, replacementRoomId)
                         .equalTo(EventEntityFields.TYPE, EventType.STATE_ROOM_HISTORY_VISIBILITY)
             }
-            if (isEncrypted) {
+            if (roomSummary.isEncrypted) {
                 awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm ->
-                    EventEntity.whereRoomId(realm, roomId)
+                    EventEntity.whereRoomId(realm, replacementRoomId)
                             .equalTo(EventEntityFields.TYPE, EventType.STATE_ROOM_ENCRYPTION)
                 }
             }
         } catch (exception: TimeoutCancellationException) {
-            throw CreateRoomFailure.CreatedWithTimeout(roomId)
+            updateCreationState(roomSummary.roomId, LocalRoomCreationState.FAILURE)
+            throw CreateRoomFailure.CreatedWithTimeout(replacementRoomId)
         }
-
-        createTombstoneEvent(params, roomId)
-        return roomId
     }
 
-    /**
-     * Create a Tombstone event to indicate that the local room has been replaced by a new one.
-     */
-    private suspend fun createTombstoneEvent(params: CreateRoomFromLocalRoomTask.Params, roomId: String) {
-        val now = clock.epochMillis()
-        val event = Event(
-                type = EventType.STATE_ROOM_TOMBSTONE,
-                senderId = userId,
-                originServerTs = now,
-                stateKey = "",
-                eventId = UUID.randomUUID().toString(),
-                content = RoomTombstoneContent(
-                        replacementRoomId = roomId
-                ).toContent()
-        )
-        monarchy.awaitTransaction { realm ->
-            val eventEntity = event.toEntity(params.localRoomId, SendState.SYNCED, now).copyToRealmOrIgnore(realm, EventInsertType.INCREMENTAL_SYNC)
-            if (event.stateKey != null && event.type != null && event.eventId != null) {
-                CurrentStateEventEntity.getOrCreate(realm, params.localRoomId, event.stateKey, event.type).apply {
-                    eventId = event.eventId
-                    root = eventEntity
-                }
-            }
+    private fun updateCreationState(roomId: String, creationState: LocalRoomCreationState) {
+        monarchy.runTransactionSync { realm ->
+            LocalRoomSummaryEntity.where(realm, roomId).findFirst()?.creationState = creationState
+        }
+    }
+
+    private fun updateReplacementRoomId(localRoomId: String, replacementRoomId: String) {
+        monarchy.runTransactionSync { realm ->
+            LocalRoomSummaryEntity.where(realm, localRoomId).findFirst()?.replacementRoomId = replacementRoomId
         }
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt
index afc1d5012f..5c4ed8012b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt
@@ -34,6 +34,7 @@ import org.matrix.android.sdk.api.session.room.ResultBoundaries
 import org.matrix.android.sdk.api.session.room.RoomSortOrder
 import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
 import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult
+import org.matrix.android.sdk.api.session.room.model.LocalRoomSummary
 import org.matrix.android.sdk.api.session.room.model.Membership
 import org.matrix.android.sdk.api.session.room.model.RoomSummary
 import org.matrix.android.sdk.api.session.room.model.RoomType
@@ -43,7 +44,9 @@ import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotification
 import org.matrix.android.sdk.api.session.space.SpaceSummaryQueryParams
 import org.matrix.android.sdk.api.util.Optional
 import org.matrix.android.sdk.api.util.toOptional
+import org.matrix.android.sdk.internal.database.mapper.LocalRoomSummaryMapper
 import org.matrix.android.sdk.internal.database.mapper.RoomSummaryMapper
+import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntity
 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.findByAlias
@@ -57,6 +60,7 @@ import javax.inject.Inject
 internal class RoomSummaryDataSource @Inject constructor(
         @SessionDatabase private val monarchy: Monarchy,
         private val roomSummaryMapper: RoomSummaryMapper,
+        private val localRoomSummaryMapper: LocalRoomSummaryMapper,
         private val queryStringValueProcessor: QueryStringValueProcessor,
 ) {
 
@@ -95,6 +99,25 @@ internal class RoomSummaryDataSource @Inject constructor(
         )
     }
 
+    fun getLocalRoomSummary(roomId: String): LocalRoomSummary? {
+        return monarchy
+                .fetchCopyMap({
+                    LocalRoomSummaryEntity.where(it, roomId).findFirst()
+                }, { entity, _ ->
+                    localRoomSummaryMapper.map(entity)
+                })
+    }
+
+    fun getLocalRoomSummaryLive(roomId: String): LiveData<Optional<LocalRoomSummary>> {
+        val liveData = monarchy.findAllMappedWithChanges(
+                { realm -> LocalRoomSummaryEntity.where(realm, roomId) },
+                { localRoomSummaryMapper.map(it) }
+        )
+        return Transformations.map(liveData) { results ->
+            results.firstOrNull().toOptional()
+        }
+    }
+
     fun getRoomSummariesLive(
             queryParams: RoomSummaryQueryParams,
             sortOrder: RoomSortOrder = RoomSortOrder.NONE
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateRoomFromLocalRoomTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateRoomFromLocalRoomTaskTest.kt
index d3732363b5..9e34280437 100644
--- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateRoomFromLocalRoomTaskTest.kt
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateRoomFromLocalRoomTaskTest.kt
@@ -22,21 +22,22 @@ import io.mockk.coVerify
 import io.mockk.every
 import io.mockk.mockk
 import io.mockk.mockkStatic
+import io.mockk.spyk
 import io.mockk.unmockkAll
+import io.mockk.verify
+import io.mockk.verifyOrder
 import io.realm.kotlin.where
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runTest
 import org.amshove.kluent.shouldBeEqualTo
+import org.amshove.kluent.shouldBeNull
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
-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.events.model.EventType
-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.extensions.tryOrNull
+import org.matrix.android.sdk.api.session.room.model.LocalRoomCreationState
+import org.matrix.android.sdk.api.session.room.model.LocalRoomSummary
 import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
-import org.matrix.android.sdk.api.session.room.model.tombstone.RoomTombstoneContent
 import org.matrix.android.sdk.internal.database.awaitNotEmptyResult
 import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity
 import org.matrix.android.sdk.internal.database.model.EventEntity
@@ -44,29 +45,24 @@ import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntity
 import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntityFields
 import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore
 import org.matrix.android.sdk.internal.database.query.getOrCreate
-import org.matrix.android.sdk.internal.util.time.DefaultClock
 import org.matrix.android.sdk.test.fakes.FakeMonarchy
-import org.matrix.android.sdk.test.fakes.FakeStateEventDataSource
+import org.matrix.android.sdk.test.fakes.FakeRoomSummaryDataSource
 
 private const val A_LOCAL_ROOM_ID = "local.a-local-room-id"
 private const val AN_EXISTING_ROOM_ID = "an-existing-room-id"
 private const val A_ROOM_ID = "a-room-id"
-private const val MY_USER_ID = "my-user-id"
 
 @ExperimentalCoroutinesApi
 internal class DefaultCreateRoomFromLocalRoomTaskTest {
 
     private val fakeMonarchy = FakeMonarchy()
-    private val clock = DefaultClock()
     private val createRoomTask = mockk<CreateRoomTask>()
-    private val fakeStateEventDataSource = FakeStateEventDataSource()
+    private val fakeRoomSummaryDataSource = FakeRoomSummaryDataSource()
 
     private val defaultCreateRoomFromLocalRoomTask = DefaultCreateRoomFromLocalRoomTask(
-            userId = MY_USER_ID,
             monarchy = fakeMonarchy.instance,
             createRoomTask = createRoomTask,
-            stateEventDataSource = fakeStateEventDataSource.instance,
-            clock = clock
+            roomSummaryDataSource = fakeRoomSummaryDataSource.instance,
     )
 
     @Before
@@ -91,13 +87,12 @@ internal class DefaultCreateRoomFromLocalRoomTaskTest {
     @Test
     fun `given a local room id when execute then the existing room id is kept`() = runTest {
         // Given
-        givenATombstoneEvent(
-                Event(
-                        roomId = A_LOCAL_ROOM_ID,
-                        type = EventType.STATE_ROOM_TOMBSTONE,
-                        stateKey = "",
-                        content = RoomTombstoneContent(replacementRoomId = AN_EXISTING_ROOM_ID).toContent()
-                )
+        val aCreateRoomParams = mockk<CreateRoomParams>(relaxed = true)
+        givenALocalRoomSummary(aCreateRoomParams = aCreateRoomParams, aCreationState = LocalRoomCreationState.CREATED, aReplacementRoomId = AN_EXISTING_ROOM_ID)
+        val aLocalRoomSummaryEntity = givenALocalRoomSummaryEntity(
+                aCreateRoomParams = aCreateRoomParams,
+                aCreationState = LocalRoomCreationState.CREATED,
+                aReplacementRoomId = AN_EXISTING_ROOM_ID
         )
 
         // When
@@ -105,20 +100,18 @@ internal class DefaultCreateRoomFromLocalRoomTaskTest {
         val result = defaultCreateRoomFromLocalRoomTask.execute(params)
 
         // Then
-        verifyTombstoneEvent(AN_EXISTING_ROOM_ID)
+        fakeRoomSummaryDataSource.verifyGetLocalRoomSummary(A_LOCAL_ROOM_ID)
         result shouldBeEqualTo AN_EXISTING_ROOM_ID
+        aLocalRoomSummaryEntity.replacementRoomId shouldBeEqualTo AN_EXISTING_ROOM_ID
+        aLocalRoomSummaryEntity.creationState shouldBeEqualTo LocalRoomCreationState.CREATED
     }
 
     @Test
     fun `given a local room id when execute then it is correctly executed`() = runTest {
         // Given
-        val aCreateRoomParams = mockk<CreateRoomParams>()
-        val aLocalRoomSummaryEntity = mockk<LocalRoomSummaryEntity> {
-            every { roomSummaryEntity } returns mockk(relaxed = true)
-            every { createRoomParams } returns aCreateRoomParams
-        }
-        givenATombstoneEvent(null)
-        givenALocalRoomSummaryEntity(aLocalRoomSummaryEntity)
+        val aCreateRoomParams = mockk<CreateRoomParams>(relaxed = true)
+        givenALocalRoomSummary(aCreateRoomParams = aCreateRoomParams, aReplacementRoomId = null)
+        val aLocalRoomSummaryEntity = givenALocalRoomSummaryEntity(aCreateRoomParams = aCreateRoomParams, aReplacementRoomId = null)
 
         coEvery { createRoomTask.execute(any()) } returns A_ROOM_ID
 
@@ -127,32 +120,84 @@ internal class DefaultCreateRoomFromLocalRoomTaskTest {
         val result = defaultCreateRoomFromLocalRoomTask.execute(params)
 
         // Then
-        verifyTombstoneEvent(null)
+        fakeRoomSummaryDataSource.verifyGetLocalRoomSummary(A_LOCAL_ROOM_ID)
         // CreateRoomTask has been called with the initial CreateRoomParams
         coVerify { createRoomTask.execute(aCreateRoomParams) }
         // The resulting roomId matches the roomId returned by the createRoomTask
         result shouldBeEqualTo A_ROOM_ID
-        // A tombstone state event has been created
-        coVerify { CurrentStateEventEntity.getOrCreate(realm = any(), roomId = A_LOCAL_ROOM_ID, stateKey = any(), type = EventType.STATE_ROOM_TOMBSTONE) }
+        // The room creation state has correctly been updated
+        verifyOrder {
+            aLocalRoomSummaryEntity.creationState = LocalRoomCreationState.CREATING
+            aLocalRoomSummaryEntity.creationState = LocalRoomCreationState.CREATED
+        }
+        // The local room summary has been updated with the created room id
+        verify { aLocalRoomSummaryEntity.replacementRoomId = A_ROOM_ID }
+        aLocalRoomSummaryEntity.replacementRoomId shouldBeEqualTo A_ROOM_ID
+        aLocalRoomSummaryEntity.creationState shouldBeEqualTo LocalRoomCreationState.CREATED
     }
 
-    private fun givenATombstoneEvent(event: Event?) {
-        fakeStateEventDataSource.givenGetStateEventReturns(event)
+    @Test
+    fun `given a local room id when execute with an exception then the creation state is correctly updated`() = runTest {
+        // Given
+        val aCreateRoomParams = mockk<CreateRoomParams>(relaxed = true)
+        givenALocalRoomSummary(aCreateRoomParams = aCreateRoomParams, aReplacementRoomId = null)
+        val aLocalRoomSummaryEntity = givenALocalRoomSummaryEntity(aCreateRoomParams = aCreateRoomParams, aReplacementRoomId = null)
+
+        coEvery { createRoomTask.execute(any()) }.throws(mockk())
+
+        // When
+        val params = CreateRoomFromLocalRoomTask.Params(A_LOCAL_ROOM_ID)
+        tryOrNull { defaultCreateRoomFromLocalRoomTask.execute(params) }
+
+        // Then
+        fakeRoomSummaryDataSource.verifyGetLocalRoomSummary(A_LOCAL_ROOM_ID)
+        // CreateRoomTask has been called with the initial CreateRoomParams
+        coVerify { createRoomTask.execute(aCreateRoomParams) }
+        // The room creation state has correctly been updated
+        verifyOrder {
+            aLocalRoomSummaryEntity.creationState = LocalRoomCreationState.CREATING
+            aLocalRoomSummaryEntity.creationState = LocalRoomCreationState.FAILURE
+        }
+        // The local room summary has been updated with the created room id
+        aLocalRoomSummaryEntity.replacementRoomId.shouldBeNull()
+        aLocalRoomSummaryEntity.creationState shouldBeEqualTo LocalRoomCreationState.FAILURE
     }
 
-    private fun givenALocalRoomSummaryEntity(localRoomSummaryEntity: LocalRoomSummaryEntity) {
+    private fun givenALocalRoomSummary(
+            aCreateRoomParams: CreateRoomParams,
+            aCreationState: LocalRoomCreationState = LocalRoomCreationState.NOT_CREATED,
+            aReplacementRoomId: String? = null
+    ): LocalRoomSummary {
+        val aLocalRoomSummary = LocalRoomSummary(
+                roomId = A_LOCAL_ROOM_ID,
+                roomSummary = mockk(relaxed = true),
+                createRoomParams = aCreateRoomParams,
+                creationState = aCreationState,
+                replacementRoomId = aReplacementRoomId,
+        )
+        fakeRoomSummaryDataSource.givenGetLocalRoomSummaryReturns(A_LOCAL_ROOM_ID, aLocalRoomSummary)
+        return aLocalRoomSummary
+    }
+
+    private fun givenALocalRoomSummaryEntity(
+            aCreateRoomParams: CreateRoomParams,
+            aCreationState: LocalRoomCreationState = LocalRoomCreationState.NOT_CREATED,
+            aReplacementRoomId: String? = null
+    ): LocalRoomSummaryEntity {
+        val aLocalRoomSummaryEntity = spyk(LocalRoomSummaryEntity(
+                roomId = A_LOCAL_ROOM_ID,
+                roomSummaryEntity = mockk(relaxed = true),
+                replacementRoomId = aReplacementRoomId,
+        ).apply {
+            createRoomParams = aCreateRoomParams
+            creationState = aCreationState
+        })
         every {
             fakeMonarchy.fakeRealm.instance
                     .where<LocalRoomSummaryEntity>()
                     .equalTo(LocalRoomSummaryEntityFields.ROOM_ID, A_LOCAL_ROOM_ID)
                     .findFirst()
-        } returns localRoomSummaryEntity
-    }
-
-    private fun verifyTombstoneEvent(expectedRoomId: String?) {
-        fakeStateEventDataSource.verifyGetStateEvent(A_LOCAL_ROOM_ID, EventType.STATE_ROOM_TOMBSTONE, QueryStringValue.IsEmpty)
-        fakeStateEventDataSource.instance.getStateEvent(A_LOCAL_ROOM_ID, EventType.STATE_ROOM_TOMBSTONE, QueryStringValue.IsEmpty)
-                ?.content.toModel<RoomTombstoneContent>()
-                ?.replacementRoomId shouldBeEqualTo expectedRoomId
+        } returns aLocalRoomSummaryEntity
+        return aLocalRoomSummaryEntity
     }
 }
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeMonarchy.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeMonarchy.kt
index 2d501f12af..93999458c6 100644
--- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeMonarchy.kt
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeMonarchy.kt
@@ -47,6 +47,11 @@ internal class FakeMonarchy {
         } coAnswers {
             firstArg<Monarchy.RealmBlock>().doWithRealm(fakeRealm.instance)
         }
+        coEvery {
+            instance.runTransactionSync(any())
+        } coAnswers {
+            firstArg<Realm.Transaction>().execute(fakeRealm.instance)
+        }
         every { instance.realmConfiguration } returns mockk()
     }
 
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRoomSummaryDataSource.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRoomSummaryDataSource.kt
new file mode 100644
index 0000000000..8c857999ca
--- /dev/null
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRoomSummaryDataSource.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.test.fakes
+
+import io.mockk.every
+import io.mockk.mockk
+import io.mockk.verify
+import org.matrix.android.sdk.api.session.room.model.LocalRoomSummary
+import org.matrix.android.sdk.api.session.room.model.RoomSummary
+import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
+
+internal class FakeRoomSummaryDataSource {
+
+    val instance: RoomSummaryDataSource = mockk()
+
+    fun givenGetLocalRoomSummaryReturns(roomId: String?, localRoomSummary: LocalRoomSummary?) {
+        every { instance.getLocalRoomSummary(roomId = roomId ?: any()) } returns localRoomSummary
+    }
+
+    fun verifyGetLocalRoomSummary(roomId: String) {
+        verify { instance.getLocalRoomSummary(roomId) }
+    }
+}
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 3af849e965..399d5e0abe 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
@@ -51,7 +51,7 @@ sealed class RoomDetailViewEvents : VectorViewEvents {
     object OpenRoomProfile : RoomDetailViewEvents()
     data class ShowRoomAvatarFullScreen(val matrixItem: MatrixItem?, val view: View?) : RoomDetailViewEvents()
 
-    object ShowWaitingView : RoomDetailViewEvents()
+    data class ShowWaitingView(val text: String? = null) : RoomDetailViewEvents()
     object HideWaitingView : RoomDetailViewEvents()
 
     data class DownloadFileState(
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 8b6429abb1..5eb90dde4b 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
@@ -493,7 +493,7 @@ class TimelineFragment :
                 is RoomDetailViewEvents.ShowInfoOkDialog -> showDialogWithMessage(it.message)
                 is RoomDetailViewEvents.JoinJitsiConference -> joinJitsiRoom(it.widget, it.withVideo)
                 RoomDetailViewEvents.LeaveJitsiConference -> leaveJitsiConference()
-                RoomDetailViewEvents.ShowWaitingView -> vectorBaseActivity.showWaitingView()
+                is RoomDetailViewEvents.ShowWaitingView -> vectorBaseActivity.showWaitingView(it.text)
                 RoomDetailViewEvents.HideWaitingView -> vectorBaseActivity.hideWaitingView()
                 is RoomDetailViewEvents.RequestNativeWidgetPermission -> requestNativeWidgetPermission(it)
                 is RoomDetailViewEvents.OpenRoom -> handleOpenRoom(it)
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 535a949cd3..a6513ffc4f 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
@@ -83,7 +83,6 @@ import org.matrix.android.sdk.api.query.QueryStringValue
 import org.matrix.android.sdk.api.raw.RawService
 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.Event
 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
@@ -100,9 +99,11 @@ import org.matrix.android.sdk.api.session.room.getTimelineEvent
 import org.matrix.android.sdk.api.session.room.location.UpdateLiveLocationShareResult
 import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
 import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams
+import org.matrix.android.sdk.api.session.room.model.LocalRoomCreationState
 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.localecho.RoomLocalEcho
 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
@@ -185,6 +186,7 @@ class TimelineViewModel @AssistedInject constructor(
     init {
         // This method will take care of a null room to update the state.
         observeRoomSummary()
+        observeLocalRoomSummary()
         if (room == null) {
             timeline = null
         } else {
@@ -617,7 +619,7 @@ class TimelineViewModel @AssistedInject constructor(
     }
 
     private fun handleAddJitsiConference(action: RoomDetailAction.AddJitsiWidget) {
-        _viewEvents.post(RoomDetailViewEvents.ShowWaitingView)
+        _viewEvents.post(RoomDetailViewEvents.ShowWaitingView())
         viewModelScope.launch(Dispatchers.IO) {
             try {
                 val widget = jitsiService.createJitsiWidget(initialState.roomId, action.withVideo)
@@ -637,7 +639,7 @@ class TimelineViewModel @AssistedInject constructor(
                 if (isJitsiWidget) {
                     setState { copy(jitsiState = jitsiState.copy(deleteWidgetInProgress = true)) }
                 } else {
-                    _viewEvents.post(RoomDetailViewEvents.ShowWaitingView)
+                    _viewEvents.post(RoomDetailViewEvents.ShowWaitingView())
                 }
                 session.widgetService().destroyRoomWidget(initialState.roomId, widgetId)
                 // local echo
@@ -1231,6 +1233,28 @@ class TimelineViewModel @AssistedInject constructor(
         }
     }
 
+    private fun observeLocalRoomSummary() {
+        if (room != null && RoomLocalEcho.isLocalEchoId(room.roomId)) {
+            room.flow().liveLocalRoomSummary()
+                    .unwrap()
+                    .map { it.creationState }
+                    .distinctUntilChanged()
+                    .onEach { creationState ->
+                        when (creationState) {
+                            LocalRoomCreationState.NOT_CREATED -> Unit
+                            LocalRoomCreationState.CREATING ->
+                                _viewEvents.post(RoomDetailViewEvents.ShowWaitingView(stringProvider.getString(R.string.creating_direct_room)))
+                            LocalRoomCreationState.FAILURE -> {
+                                _viewEvents.post(RoomDetailViewEvents.HideWaitingView)
+                            }
+                            LocalRoomCreationState.CREATED ->
+                                _viewEvents.post(RoomDetailViewEvents.OpenRoom(room.localRoomSummary()?.replacementRoomId!!, true))
+                        }
+                    }
+                    .launchIn(viewModelScope)
+        }
+    }
+
     private fun getUnreadState() {
         if (room == null) return
         combine(
@@ -1322,26 +1346,11 @@ class TimelineViewModel @AssistedInject constructor(
                 }
             }
             room.getStateEvent(EventType.STATE_ROOM_TOMBSTONE, QueryStringValue.IsEmpty)?.also {
-                onRoomTombstoneUpdated(it)
+                setState { copy(tombstoneEvent = it) }
             }
         }
     }
 
-    private var roomTombstoneHandled = false
-    private fun onRoomTombstoneUpdated(tombstoneEvent: Event) = withState { state ->
-        if (roomTombstoneHandled) return@withState
-        if (state.isLocalRoom()) {
-            // Local room has been replaced, so navigate to the new room
-            val roomId = tombstoneEvent.getClearContent()?.toModel<RoomTombstoneContent>()
-                    ?.replacementRoomId
-                    ?: return@withState
-            _viewEvents.post(RoomDetailViewEvents.OpenRoom(roomId, closeCurrentRoom = true))
-            roomTombstoneHandled = true
-        } else {
-            setState { copy(tombstoneEvent = tombstoneEvent) }
-        }
-    }
-
     /**
      * Navigates to the appropriate event (by paginating the thread timeline until the event is found
      * in the snapshot. The main reason for this function is to support the /relations api

From 10b5e8fd042343a5736dc79d3f2f4521a550d69a Mon Sep 17 00:00:00 2001
From: Florian Renaud <florianr@element.io>
Date: Wed, 14 Sep 2022 16:28:09 +0200
Subject: [PATCH 076/108] Changelog

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

diff --git a/changelog.d/6970.wip b/changelog.d/6970.wip
new file mode 100644
index 0000000000..4ec53e0d53
--- /dev/null
+++ b/changelog.d/6970.wip
@@ -0,0 +1 @@
+Create DM room only on first message - Add a spinner when sending the first message

From 3f88811590bad89dc6716679d74256d74816e6b0 Mon Sep 17 00:00:00 2001
From: Florian Renaud <florianr@element.io>
Date: Wed, 14 Sep 2022 17:09:25 +0200
Subject: [PATCH 077/108] remove unused import

---
 .../matrix/android/sdk/test/fakes/FakeRoomSummaryDataSource.kt   | 1 -
 1 file changed, 1 deletion(-)

diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRoomSummaryDataSource.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRoomSummaryDataSource.kt
index 8c857999ca..c7b70a3ad5 100644
--- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRoomSummaryDataSource.kt
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRoomSummaryDataSource.kt
@@ -20,7 +20,6 @@ import io.mockk.every
 import io.mockk.mockk
 import io.mockk.verify
 import org.matrix.android.sdk.api.session.room.model.LocalRoomSummary
-import org.matrix.android.sdk.api.session.room.model.RoomSummary
 import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
 
 internal class FakeRoomSummaryDataSource {

From eac74bda0932931b7ec9d53383f813c748d8a12e Mon Sep 17 00:00:00 2001
From: Florian Renaud <florianr@element.io>
Date: Mon, 19 Sep 2022 09:37:22 +0200
Subject: [PATCH 078/108] Improve nullability check in
 CreateRoomFromLocalRoomTask

---
 .../create/CreateRoomFromLocalRoomTask.kt     | 45 ++++++++++++-------
 1 file changed, 30 insertions(+), 15 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt
index 57ffe7fb0c..2245eb8513 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt
@@ -21,8 +21,8 @@ import kotlinx.coroutines.TimeoutCancellationException
 import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure
 import org.matrix.android.sdk.api.session.room.model.LocalRoomCreationState
-import org.matrix.android.sdk.api.session.room.model.LocalRoomSummary
 import org.matrix.android.sdk.api.session.room.model.RoomSummary
+import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
 import org.matrix.android.sdk.internal.database.awaitNotEmptyResult
 import org.matrix.android.sdk.internal.database.model.EventEntity
 import org.matrix.android.sdk.internal.database.model.EventEntityFields
@@ -57,56 +57,71 @@ internal class DefaultCreateRoomFromLocalRoomTask @Inject constructor(
 
     override suspend fun execute(params: CreateRoomFromLocalRoomTask.Params): String {
         val localRoomSummary = roomSummaryDataSource.getLocalRoomSummary(params.localRoomId)
-                ?.takeIf { it.createRoomParams != null && it.roomSummary != null }
-                ?: error("Invalid LocalRoomSummary for ${params.localRoomId}")
+                ?: error("## CreateRoomFromLocalRoomTask - Cannot retrieve LocalRoomSummary with roomId ${params.localRoomId}")
 
         // If a room has already been created for the given local room, return the existing roomId
         if (localRoomSummary.replacementRoomId != null) {
             return localRoomSummary.replacementRoomId
         }
 
-        return createRoom(localRoomSummary)
+        if (localRoomSummary.createRoomParams != null && localRoomSummary.roomSummary != null) {
+            return createRoom(params.localRoomId, localRoomSummary.roomSummary, localRoomSummary.createRoomParams)
+        } else {
+            error("## CreateRoomFromLocalRoomTask - Invalid LocalRoomSummary: $localRoomSummary")
+        }
     }
 
-    private suspend fun createRoom(localRoomSummary: LocalRoomSummary): String {
-        updateCreationState(localRoomSummary.roomId, LocalRoomCreationState.CREATING)
+    /**
+     * Create a room on the server for the given local room.
+     *
+     * @param localRoomId the local room identifier.
+     * @param localRoomSummary the RoomSummary of the local room.
+     * @param createRoomParams the CreateRoomParams object which was used to configure the local room.
+     *
+     * @return the identifier of the created room.
+     */
+    private suspend fun createRoom(localRoomId: String, localRoomSummary: RoomSummary, createRoomParams: CreateRoomParams): String {
+        updateCreationState(localRoomId, LocalRoomCreationState.CREATING)
         val replacementRoomId = runCatching {
-            createRoomTask.execute(localRoomSummary.createRoomParams!!)
+            createRoomTask.execute(createRoomParams)
         }.fold(
                 { it },
                 {
-                    updateCreationState(roomId = localRoomSummary.roomId, LocalRoomCreationState.FAILURE)
+                    updateCreationState(localRoomId, LocalRoomCreationState.FAILURE)
                     throw it
                 }
         )
-        updateReplacementRoomId(localRoomSummary.roomId, replacementRoomId)
-        waitForRoomEvents(replacementRoomId, localRoomSummary.roomSummary!!)
-        updateCreationState(localRoomSummary.roomId, LocalRoomCreationState.CREATED)
+        updateReplacementRoomId(localRoomId, replacementRoomId)
+        waitForRoomEvents(replacementRoomId, localRoomSummary)
+        updateCreationState(localRoomId, LocalRoomCreationState.CREATED)
         return replacementRoomId
     }
 
     /**
      * Wait for all the room events before triggering the created state.
+     *
+     * @param replacementRoomId the identifier of the created room
+     * @param localRoomSummary the RoomSummary of the local room.
      */
-    private suspend fun waitForRoomEvents(replacementRoomId: String, roomSummary: RoomSummary) {
+    private suspend fun waitForRoomEvents(replacementRoomId: String, localRoomSummary: RoomSummary) {
         try {
             awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm ->
                 realm.where(RoomSummaryEntity::class.java)
                         .equalTo(RoomSummaryEntityFields.ROOM_ID, replacementRoomId)
-                        .equalTo(RoomSummaryEntityFields.INVITED_MEMBERS_COUNT, roomSummary.invitedMembersCount)
+                        .equalTo(RoomSummaryEntityFields.INVITED_MEMBERS_COUNT, localRoomSummary.invitedMembersCount)
             }
             awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm ->
                 EventEntity.whereRoomId(realm, replacementRoomId)
                         .equalTo(EventEntityFields.TYPE, EventType.STATE_ROOM_HISTORY_VISIBILITY)
             }
-            if (roomSummary.isEncrypted) {
+            if (localRoomSummary.isEncrypted) {
                 awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm ->
                     EventEntity.whereRoomId(realm, replacementRoomId)
                             .equalTo(EventEntityFields.TYPE, EventType.STATE_ROOM_ENCRYPTION)
                 }
             }
         } catch (exception: TimeoutCancellationException) {
-            updateCreationState(roomSummary.roomId, LocalRoomCreationState.FAILURE)
+            updateCreationState(localRoomSummary.roomId, LocalRoomCreationState.FAILURE)
             throw CreateRoomFailure.CreatedWithTimeout(replacementRoomId)
         }
     }

From 75236e9ed05cc1b08d4d15aed3c18422b76be921 Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoit@matrix.org>
Date: Mon, 19 Sep 2022 10:17:05 +0200
Subject: [PATCH 079/108] Start with `buildjet-2vcpu-ubuntu-2204`

---
 .github/workflows/post-pr.yml | 2 +-
 .github/workflows/tests.yml   | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/post-pr.yml b/.github/workflows/post-pr.yml
index bf948064ed..8eff24b0c8 100644
--- a/.github/workflows/post-pr.yml
+++ b/.github/workflows/post-pr.yml
@@ -31,7 +31,7 @@ jobs:
   ui-tests:
     name: UI Tests (Synapse)
     needs: should-i-run
-    runs-on: buildjet-4vcpu-ubuntu-2204
+    runs-on: buildjet-2vcpu-ubuntu-2204
     strategy:
       fail-fast: false
       matrix:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index ffffb2b760..c63ab593b4 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -13,7 +13,7 @@ env:
 jobs:
   tests:
     name: Runs all tests
-    runs-on: buildjet-4vcpu-ubuntu-2204 # for the emulator
+    runs-on: buildjet-2vcpu-ubuntu-2204
     # Allow all jobs on main and develop. Just one per PR.
     concurrency:
       group: ${{ github.ref == 'refs/heads/main' && format('unit-tests-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('unit-tests-develop-{0}', github.sha) || format('unit-tests-{0}', github.ref) }}

From 874bcc117a322aecd8e5a81a1760fcc1a0ec44c7 Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoit@matrix.org>
Date: Mon, 19 Sep 2022 10:32:10 +0200
Subject: [PATCH 080/108] Fix regression on our dependency, due to merge of
 #6788.

We do not use `android-embedded_fcm_distributor` anymore (since #7068).
The code was compiling because `android-embedded_fcm_distributor` has a dependency on `firebase-messaging`.
---
 vector-app/build.gradle | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/vector-app/build.gradle b/vector-app/build.gradle
index ea543166fe..dacd1416fd 100644
--- a/vector-app/build.gradle
+++ b/vector-app/build.gradle
@@ -372,7 +372,7 @@ dependencies {
 
     gplayImplementation "com.google.android.gms:play-services-location:20.0.0"
     // UnifiedPush gplay flavor only
-    gplayImplementation('com.github.UnifiedPush:android-embedded_fcm_distributor:2.1.2') {
+    gplayImplementation('com.google.firebase:firebase-messaging:23.0.8') {
         exclude group: 'com.google.firebase', module: 'firebase-core'
         exclude group: 'com.google.firebase', module: 'firebase-analytics'
         exclude group: 'com.google.firebase', module: 'firebase-measurement-connector'

From 237da2ce2273019c4310213aab03d3deb478272b Mon Sep 17 00:00:00 2001
From: NIkita Fedrunov <fedrunov@element.io>
Date: Mon, 19 Sep 2022 11:08:25 +0200
Subject: [PATCH 081/108] changed app layout flag for all_test

---
 .../src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/vector-app/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt b/vector-app/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt
index b70fcfec25..9fb7fceebf 100644
--- a/vector-app/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt
+++ b/vector-app/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt
@@ -50,7 +50,7 @@ import im.vector.app.withIdlingResource
 import timber.log.Timber
 
 class ElementRobot(
-        private val labsPreferences: LabFeaturesPreferences = LabFeaturesPreferences(false)
+        private val labsPreferences: LabFeaturesPreferences = LabFeaturesPreferences(true)
 ) {
     fun onboarding(block: OnboardingRobot.() -> Unit) {
         block(OnboardingRobot())

From bf493f27aee9bc214fe60c6a2c93bc996c2fbe62 Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoit@matrix.org>
Date: Mon, 19 Sep 2022 14:31:35 +0200
Subject: [PATCH 082/108] Revert to `buildjet-4vcpu-ubuntu-2204`

---
 .github/workflows/post-pr.yml | 2 +-
 .github/workflows/tests.yml   | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/post-pr.yml b/.github/workflows/post-pr.yml
index 8eff24b0c8..bf948064ed 100644
--- a/.github/workflows/post-pr.yml
+++ b/.github/workflows/post-pr.yml
@@ -31,7 +31,7 @@ jobs:
   ui-tests:
     name: UI Tests (Synapse)
     needs: should-i-run
-    runs-on: buildjet-2vcpu-ubuntu-2204
+    runs-on: buildjet-4vcpu-ubuntu-2204
     strategy:
       fail-fast: false
       matrix:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index c63ab593b4..e260749374 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -13,7 +13,7 @@ env:
 jobs:
   tests:
     name: Runs all tests
-    runs-on: buildjet-2vcpu-ubuntu-2204
+    runs-on: buildjet-4vcpu-ubuntu-2204
     # Allow all jobs on main and develop. Just one per PR.
     concurrency:
       group: ${{ github.ref == 'refs/heads/main' && format('unit-tests-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('unit-tests-develop-{0}', github.sha) || format('unit-tests-{0}', github.ref) }}

From 830e5ffa9f5cd1d9d7c77d68390a73e5d5537b15 Mon Sep 17 00:00:00 2001
From: Nikita Fedrunov <66663241+fedrunov@users.noreply.github.com>
Date: Mon, 19 Sep 2022 15:22:16 +0200
Subject: [PATCH 083/108] room summary now has constant height (#7145)

---
 changelog.d/7079.bugfix                       |  1 +
 .../core/utils/FirstItemUpdatedObserver.kt    | 47 ++++++++++++++
 .../home/room/list/RoomSummaryItem.kt         |  7 ++
 .../home/room/list/RoomSummaryItemFactory.kt  | 12 ++--
 .../room/list/RoomSummaryItemPlaceHolder.kt   | 17 ++++-
 .../room/list/RoomSummaryListController.kt    | 12 +++-
 .../room/list/RoomSummaryPagedController.kt   | 22 +++++--
 .../list/RoomSummaryPagedControllerFactory.kt |  8 ++-
 .../list/home/HomeFilteredRoomsController.kt  | 20 +++++-
 .../room/list/home/HomeRoomListFragment.kt    | 17 +++--
 .../home/header/HomeRoomsHeadersController.kt | 64 ++++++++++---------
 .../features/settings/FontScalePreferences.kt | 24 +++++--
 .../features/share/IncomingShareController.kt |  1 +
 .../main/res/drawable/placeholder_shape_8.xml |  3 +-
 .../src/main/res/layout/item_recent_room.xml  |  1 -
 vector/src/main/res/layout/item_room.xml      |  2 +-
 .../main/res/layout/item_room_placeholder.xml | 48 ++++++++------
 17 files changed, 222 insertions(+), 84 deletions(-)
 create mode 100644 changelog.d/7079.bugfix
 create mode 100644 vector/src/main/java/im/vector/app/core/utils/FirstItemUpdatedObserver.kt

diff --git a/changelog.d/7079.bugfix b/changelog.d/7079.bugfix
new file mode 100644
index 0000000000..b63d491e4b
--- /dev/null
+++ b/changelog.d/7079.bugfix
@@ -0,0 +1 @@
+Fixed problem when room list's scroll did jump after rooms placeholders were replaced with rooms summary items
diff --git a/vector/src/main/java/im/vector/app/core/utils/FirstItemUpdatedObserver.kt b/vector/src/main/java/im/vector/app/core/utils/FirstItemUpdatedObserver.kt
new file mode 100644
index 0000000000..25901cdf95
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/core/utils/FirstItemUpdatedObserver.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.core.utils
+
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+
+/**
+ * This observer detects when item was added or moved to the first position of the adapter, while recyclerView is scrolled to the top. This is necessary
+ * to force recycler to scroll to the top to make such item visible, because by default it will keep items on screen, while adding new item to the top,
+ * outside of the viewport
+ * @param layoutManager - [LinearLayoutManager] of the recycler view, which displays items
+ * @property onItemUpdated - callback to be called, when observer detects event
+ */
+class FirstItemUpdatedObserver(
+        layoutManager: LinearLayoutManager,
+        private val onItemUpdated: () -> Unit
+) : RecyclerView.AdapterDataObserver() {
+
+    val layoutManager: LinearLayoutManager? by weak(layoutManager)
+
+    override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) {
+        if ((toPosition == 0 || fromPosition == 0) && layoutManager?.findFirstCompletelyVisibleItemPosition() == 0) {
+            onItemUpdated.invoke()
+        }
+    }
+
+    override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
+        if (positionStart == 0 && layoutManager?.findFirstCompletelyVisibleItemPosition() == 0) {
+            onItemUpdated.invoke()
+        }
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItem.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItem.kt
index 58ae6520cf..e6d162e8c3 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItem.kt
@@ -103,6 +103,9 @@ abstract class RoomSummaryItem : VectorEpoxyModel<RoomSummaryItem.Holder>(R.layo
     @EpoxyAttribute
     var showSelected: Boolean = false
 
+    @EpoxyAttribute
+    var useSingleLineForLastEvent: Boolean = false
+
     override fun bind(holder: Holder) {
         super.bind(holder)
 
@@ -122,6 +125,10 @@ abstract class RoomSummaryItem : VectorEpoxyModel<RoomSummaryItem.Holder>(R.layo
         holder.roomAvatarFailSendingImageView.isVisible = hasFailedSending
         renderSelection(holder, showSelected)
         holder.roomAvatarPresenceImageView.render(showPresence, userPresence)
+
+        if (useSingleLineForLastEvent) {
+            holder.subtitleView.setLines(1)
+        }
     }
 
     private fun renderDisplayMode(holder: Holder) = when (displayMode) {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt
index 85879e6807..290b66e576 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt
@@ -51,7 +51,8 @@ class RoomSummaryItemFactory @Inject constructor(
             roomChangeMembershipStates: Map<String, ChangeMembershipState>,
             selectedRoomIds: Set<String>,
             displayMode: RoomListDisplayMode,
-            listener: RoomListListener?
+            listener: RoomListListener?,
+            singleLineLastEvent: Boolean = false
     ): VectorEpoxyModel<*> {
         return when (roomSummary.membership) {
             Membership.INVITE -> {
@@ -59,7 +60,7 @@ class RoomSummaryItemFactory @Inject constructor(
                 createInvitationItem(roomSummary, changeMembershipState, listener)
             }
             else -> createRoomItem(
-                    roomSummary, selectedRoomIds, displayMode, listener?.let { it::onRoomClicked }, listener?.let { it::onRoomLongClicked }
+                    roomSummary, selectedRoomIds, displayMode, singleLineLastEvent, listener?.let { it::onRoomClicked }, listener?.let { it::onRoomLongClicked }
             )
         }
     }
@@ -118,8 +119,9 @@ class RoomSummaryItemFactory @Inject constructor(
             roomSummary: RoomSummary,
             selectedRoomIds: Set<String>,
             displayMode: RoomListDisplayMode,
+            singleLineLastEvent: Boolean,
             onClick: ((RoomSummary) -> Unit)?,
-            onLongClick: ((RoomSummary) -> Boolean)?
+            onLongClick: ((RoomSummary) -> Boolean)?,
     ): VectorEpoxyModel<*> {
         val subtitle = getSearchResultSubtitle(roomSummary)
         val unreadCount = roomSummary.notificationCount
@@ -140,7 +142,7 @@ class RoomSummaryItemFactory @Inject constructor(
         } else {
             createRoomSummaryItem(
                     roomSummary, displayMode, subtitle, latestEventTime, typingMessage,
-                    latestFormattedEvent, showHighlighted, showSelected, unreadCount, onClick, onLongClick
+                    latestFormattedEvent, showHighlighted, showSelected, unreadCount, singleLineLastEvent, onClick, onLongClick
             )
         }
     }
@@ -155,6 +157,7 @@ class RoomSummaryItemFactory @Inject constructor(
             showHighlighted: Boolean,
             showSelected: Boolean,
             unreadCount: Int,
+            singleLineLastEvent: Boolean,
             onClick: ((RoomSummary) -> Unit)?,
             onLongClick: ((RoomSummary) -> Boolean)?
     ) = RoomSummaryItem_()
@@ -177,6 +180,7 @@ class RoomSummaryItemFactory @Inject constructor(
             .unreadNotificationCount(unreadCount)
             .hasUnreadMessage(roomSummary.hasUnreadMessages)
             .hasDraft(roomSummary.userDrafts.isNotEmpty())
+            .useSingleLineForLastEvent(singleLineLastEvent)
             .itemLongClickListener { _ -> onLongClick?.invoke(roomSummary) ?: false }
             .itemClickListener { onClick?.invoke(roomSummary) }
 
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemPlaceHolder.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemPlaceHolder.kt
index d4683f78a5..df191bc2ec 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemPlaceHolder.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemPlaceHolder.kt
@@ -16,6 +16,8 @@
 
 package im.vector.app.features.home.room.list
 
+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
@@ -23,5 +25,18 @@ import im.vector.app.core.epoxy.VectorEpoxyModel
 
 @EpoxyModelClass
 abstract class RoomSummaryItemPlaceHolder : VectorEpoxyModel<RoomSummaryItemPlaceHolder.Holder>(R.layout.item_room_placeholder) {
-    class Holder : VectorEpoxyHolder()
+
+    @EpoxyAttribute
+    var useSingleLineForLastEvent: Boolean = false
+
+    override fun bind(holder: Holder) {
+        super.bind(holder)
+        if (useSingleLineForLastEvent) {
+            holder.subtitleView.setLines(1)
+        }
+    }
+
+    class Holder : VectorEpoxyHolder() {
+        val subtitleView by bind<TextView>(R.id.subtitleView)
+    }
 }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryListController.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryListController.kt
index 2eb8921fd5..a2b6ed51d9 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryListController.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryListController.kt
@@ -17,18 +17,26 @@
 package im.vector.app.features.home.room.list
 
 import im.vector.app.features.home.RoomListDisplayMode
+import im.vector.app.features.settings.FontScalePreferences
 import org.matrix.android.sdk.api.session.room.model.RoomSummary
 
 class RoomSummaryListController(
         private val roomSummaryItemFactory: RoomSummaryItemFactory,
-        private val displayMode: RoomListDisplayMode
+        private val displayMode: RoomListDisplayMode,
+        fontScalePreferences: FontScalePreferences
 ) : CollapsableTypedEpoxyController<List<RoomSummary>>() {
 
     var listener: RoomListListener? = null
+    private val shouldUseSingleLine: Boolean
+
+    init {
+        val fontScale = fontScalePreferences.getResolvedFontScaleValue()
+        shouldUseSingleLine = fontScale.scale > FontScalePreferences.SCALE_LARGE
+    }
 
     override fun buildModels(data: List<RoomSummary>?) {
         data?.forEach {
-            add(roomSummaryItemFactory.create(it, emptyMap(), emptySet(), displayMode, listener))
+            add(roomSummaryItemFactory.create(it, emptyMap(), emptySet(), displayMode, listener, shouldUseSingleLine))
         }
     }
 }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryPagedController.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryPagedController.kt
index 445438eec9..10d7ef425c 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryPagedController.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryPagedController.kt
@@ -20,18 +20,26 @@ import com.airbnb.epoxy.EpoxyModel
 import com.airbnb.epoxy.paging.PagedListEpoxyController
 import im.vector.app.core.utils.createUIHandler
 import im.vector.app.features.home.RoomListDisplayMode
+import im.vector.app.features.settings.FontScalePreferences
 import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
 import org.matrix.android.sdk.api.session.room.model.RoomSummary
 
 class RoomSummaryPagedController(
         private val roomSummaryItemFactory: RoomSummaryItemFactory,
-        private val displayMode: RoomListDisplayMode
+        private val displayMode: RoomListDisplayMode,
+        fontScalePreferences: FontScalePreferences
 ) : PagedListEpoxyController<RoomSummary>(
         // Important it must match the PageList builder notify Looper
         modelBuildingHandler = createUIHandler()
 ), CollapsableControllerExtension {
 
     var listener: RoomListListener? = null
+    private val shouldUseSingleLine: Boolean
+
+    init {
+        val fontScale = fontScalePreferences.getResolvedFontScaleValue()
+        shouldUseSingleLine = fontScale.scale > FontScalePreferences.SCALE_LARGE
+    }
 
     var roomChangeMembershipStates: Map<String, ChangeMembershipState>? = null
         set(value) {
@@ -57,8 +65,14 @@ class RoomSummaryPagedController(
     }
 
     override fun buildItemModel(currentPosition: Int, item: RoomSummary?): EpoxyModel<*> {
-        // for place holder if enabled
-        item ?: return RoomSummaryItemPlaceHolder_().apply { id(currentPosition) }
-        return roomSummaryItemFactory.create(item, roomChangeMembershipStates.orEmpty(), emptySet(), displayMode, listener)
+        return if (item == null) {
+            val host = this
+            RoomSummaryItemPlaceHolder_().apply {
+                id(currentPosition)
+                useSingleLineForLastEvent(host.shouldUseSingleLine)
+            }
+        } else {
+            roomSummaryItemFactory.create(item, roomChangeMembershipStates.orEmpty(), emptySet(), displayMode, listener, shouldUseSingleLine)
+        }
     }
 }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryPagedControllerFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryPagedControllerFactory.kt
index f72698048d..c5edd9c063 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryPagedControllerFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryPagedControllerFactory.kt
@@ -17,18 +17,20 @@
 package im.vector.app.features.home.room.list
 
 import im.vector.app.features.home.RoomListDisplayMode
+import im.vector.app.features.settings.FontScalePreferences
 import javax.inject.Inject
 
 class RoomSummaryPagedControllerFactory @Inject constructor(
-        private val roomSummaryItemFactory: RoomSummaryItemFactory
+        private val roomSummaryItemFactory: RoomSummaryItemFactory,
+        private val fontScalePreferences: FontScalePreferences
 ) {
 
     fun createRoomSummaryPagedController(displayMode: RoomListDisplayMode): RoomSummaryPagedController {
-        return RoomSummaryPagedController(roomSummaryItemFactory, displayMode)
+        return RoomSummaryPagedController(roomSummaryItemFactory, displayMode, fontScalePreferences)
     }
 
     fun createRoomSummaryListController(displayMode: RoomListDisplayMode): RoomSummaryListController {
-        return RoomSummaryListController(roomSummaryItemFactory, displayMode)
+        return RoomSummaryListController(roomSummaryItemFactory, displayMode, fontScalePreferences)
     }
 
     fun createSuggestedRoomListController(): SuggestedRoomListController {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeFilteredRoomsController.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeFilteredRoomsController.kt
index ae0f9d328f..ebf322dc23 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeFilteredRoomsController.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeFilteredRoomsController.kt
@@ -24,12 +24,14 @@ import im.vector.app.features.home.RoomListDisplayMode
 import im.vector.app.features.home.room.list.RoomListListener
 import im.vector.app.features.home.room.list.RoomSummaryItemFactory
 import im.vector.app.features.home.room.list.RoomSummaryItemPlaceHolder_
+import im.vector.app.features.settings.FontScalePreferences
 import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
 import org.matrix.android.sdk.api.session.room.model.RoomSummary
 import javax.inject.Inject
 
 class HomeFilteredRoomsController @Inject constructor(
         private val roomSummaryItemFactory: RoomSummaryItemFactory,
+        fontScalePreferences: FontScalePreferences
 ) : PagedListEpoxyController<RoomSummary>(
         // Important it must match the PageList builder notify Looper
         modelBuildingHandler = createUIHandler()
@@ -47,6 +49,13 @@ class HomeFilteredRoomsController @Inject constructor(
     private var emptyStateData: StateView.State.Empty? = null
     private var currentState: StateView.State = StateView.State.Content
 
+    private val shouldUseSingleLine: Boolean
+
+    init {
+        val fontScale = fontScalePreferences.getResolvedFontScaleValue()
+        shouldUseSingleLine = fontScale.scale > FontScalePreferences.SCALE_LARGE
+    }
+
     override fun addModels(models: List<EpoxyModel<*>>) {
         if (models.isEmpty() && emptyStateData != null) {
             emptyStateData?.let { emptyState ->
@@ -67,7 +76,14 @@ class HomeFilteredRoomsController @Inject constructor(
     }
 
     override fun buildItemModel(currentPosition: Int, item: RoomSummary?): EpoxyModel<*> {
-        item ?: return RoomSummaryItemPlaceHolder_().apply { id(currentPosition) }
-        return roomSummaryItemFactory.create(item, roomChangeMembershipStates.orEmpty(), emptySet(), RoomListDisplayMode.ROOMS, listener)
+        return if (item == null) {
+            val host = this
+            RoomSummaryItemPlaceHolder_().apply {
+                id(currentPosition)
+                useSingleLineForLastEvent(host.shouldUseSingleLine)
+            }
+        } else {
+            roomSummaryItemFactory.create(item, roomChangeMembershipStates.orEmpty(), emptySet(), RoomListDisplayMode.ROOMS, listener, shouldUseSingleLine)
+        }
     }
 }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt
index 4ae2c7d514..88bbc6986f 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt
@@ -24,7 +24,6 @@ import android.view.ViewGroup
 import androidx.lifecycle.lifecycleScope
 import androidx.recyclerview.widget.ConcatAdapter
 import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.recyclerview.widget.RecyclerView
 import com.airbnb.epoxy.OnModelBuildFinishedListener
 import com.airbnb.mvrx.fragmentViewModel
 import com.airbnb.mvrx.withState
@@ -36,6 +35,7 @@ import im.vector.app.core.extensions.cleanup
 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.core.utils.FirstItemUpdatedObserver
 import im.vector.app.databinding.FragmentRoomListBinding
 import im.vector.app.features.analytics.plan.ViewRoom
 import im.vector.app.features.home.room.list.RoomListAnimator
@@ -66,6 +66,7 @@ class HomeRoomListFragment :
     private val roomListViewModel: HomeRoomListViewModel by fragmentViewModel()
     private lateinit var sharedQuickActionsViewModel: RoomListQuickActionsSharedActionViewModel
     private var concatAdapter = ConcatAdapter()
+    private lateinit var firstItemObserver: FirstItemUpdatedObserver
     private var modelBuildListener: OnModelBuildFinishedListener? = null
 
     private lateinit var stateRestorer: LayoutManagerStateRestorer
@@ -130,6 +131,9 @@ class HomeRoomListFragment :
 
     private fun setupRecyclerView() {
         val layoutManager = LinearLayoutManager(context)
+        firstItemObserver = FirstItemUpdatedObserver(layoutManager) {
+            layoutManager.scrollToPosition(0)
+        }
         stateRestorer = LayoutManagerStateRestorer(layoutManager).register()
         views.roomListView.layoutManager = layoutManager
         views.roomListView.itemAnimator = RoomListAnimator()
@@ -158,14 +162,7 @@ class HomeRoomListFragment :
 
         views.roomListView.adapter = concatAdapter
 
-        // we need to force scroll when recents/filter tabs are added to make them visible
-        concatAdapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
-            override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
-                if (positionStart == 0) {
-                    layoutManager.scrollToPosition(0)
-                }
-            }
-        })
+        concatAdapter.registerAdapterDataObserver(firstItemObserver)
     }
 
     override fun invalidate() = withState(roomListViewModel) { state ->
@@ -233,6 +230,8 @@ class HomeRoomListFragment :
 
         roomsController.listener = null
 
+        concatAdapter.unregisterAdapterDataObserver(firstItemObserver)
+
         super.onDestroyView()
     }
 
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/header/HomeRoomsHeadersController.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/header/HomeRoomsHeadersController.kt
index f7c9eccd0b..56cccd9c36 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/home/header/HomeRoomsHeadersController.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/header/HomeRoomsHeadersController.kt
@@ -18,7 +18,7 @@ package im.vector.app.features.home.room.list.home.header
 
 import android.content.res.Resources
 import android.util.TypedValue
-import androidx.recyclerview.widget.RecyclerView
+import androidx.recyclerview.widget.LinearLayoutManager
 import com.airbnb.epoxy.Carousel
 import com.airbnb.epoxy.CarouselModelBuilder
 import com.airbnb.epoxy.EpoxyController
@@ -27,6 +27,7 @@ import com.airbnb.epoxy.carousel
 import com.google.android.material.color.MaterialColors
 import im.vector.app.R
 import im.vector.app.core.resources.StringProvider
+import im.vector.app.core.utils.FirstItemUpdatedObserver
 import im.vector.app.features.home.AvatarRenderer
 import im.vector.app.features.home.room.list.RoomListListener
 import org.matrix.android.sdk.api.session.room.model.RoomSummary
@@ -47,22 +48,7 @@ class HomeRoomsHeadersController @Inject constructor(
 
     private var carousel: Carousel? = null
 
-    private val carouselAdapterObserver = object : RecyclerView.AdapterDataObserver() {
-        override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) {
-            if (toPosition == 0 || fromPosition == 0) {
-                carousel?.post {
-                    carousel?.layoutManager?.scrollToPosition(0)
-                }
-            }
-            super.onItemRangeMoved(fromPosition, toPosition, itemCount)
-        }
-
-        override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
-            if (positionStart == 0) {
-                carousel?.layoutManager?.scrollToPosition(0)
-            }
-        }
-    }
+    private var carouselAdapterObserver: FirstItemUpdatedObserver? = null
 
     private val recentsHPadding = TypedValue.applyDimension(
             TypedValue.COMPLEX_UNIT_DIP,
@@ -113,25 +99,16 @@ class HomeRoomsHeadersController @Inject constructor(
             )
             onBind { _, view, _ ->
                 host.carousel = view
+                host.unsubscribeAdapterObserver()
+                host.subscribeAdapterObserver()
 
                 val colorSurface = MaterialColors.getColor(view, R.attr.vctr_toolbar_background)
                 view.setBackgroundColor(colorSurface)
-
-                try {
-                    view.adapter?.registerAdapterDataObserver(host.carouselAdapterObserver)
-                } catch (e: IllegalStateException) {
-                    // do nothing
-                }
             }
 
-            onUnbind { _, view ->
+            onUnbind { _, _ ->
                 host.carousel = null
-
-                try {
-                    view.adapter?.unregisterAdapterDataObserver(host.carouselAdapterObserver)
-                } catch (e: IllegalStateException) {
-                    // do nothing
-                }
+                host.unsubscribeAdapterObserver()
             }
 
             withModelsFrom(recents) { roomSummary ->
@@ -150,6 +127,33 @@ class HomeRoomsHeadersController @Inject constructor(
         }
     }
 
+    private fun unsubscribeAdapterObserver() {
+        carouselAdapterObserver?.let { observer ->
+            try {
+                carousel?.adapter?.unregisterAdapterDataObserver(observer)
+                carouselAdapterObserver = null
+            } catch (e: IllegalStateException) {
+                // do nothing
+            }
+        }
+    }
+
+    private fun subscribeAdapterObserver() {
+        (carousel?.layoutManager as? LinearLayoutManager)?.let { layoutManager ->
+            carouselAdapterObserver = FirstItemUpdatedObserver(layoutManager) {
+                carousel?.post {
+                    layoutManager.scrollToPosition(0)
+                }
+            }.also { observer ->
+                try {
+                    carousel?.adapter?.registerAdapterDataObserver(observer)
+                } catch (e: IllegalStateException) {
+                    // do nothing
+                }
+            }
+        }
+    }
+
     private fun addRoomFilterHeaderItem(
             filterChangedListener: ((HomeRoomFilter) -> Unit)?,
             filtersList: List<HomeRoomFilter>,
diff --git a/vector/src/main/java/im/vector/app/features/settings/FontScalePreferences.kt b/vector/src/main/java/im/vector/app/features/settings/FontScalePreferences.kt
index 292d0107ba..34862adc4f 100644
--- a/vector/src/main/java/im/vector/app/features/settings/FontScalePreferences.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/FontScalePreferences.kt
@@ -57,6 +57,16 @@ interface FontScalePreferences {
      * @return list of values
      */
     fun getAvailableScales(): List<FontScaleValue>
+
+    companion object {
+        const val SCALE_TINY = 0.70f
+        const val SCALE_SMALL = 0.85f
+        const val SCALE_NORMAL = 1.00f
+        const val SCALE_LARGE = 1.15f
+        const val SCALE_LARGER = 1.30f
+        const val SCALE_LARGEST = 1.45f
+        const val SCALE_HUGE = 1.60f
+    }
 }
 
 /**
@@ -73,13 +83,13 @@ class FontScalePreferencesImpl @Inject constructor(
     }
 
     private val fontScaleValues = listOf(
-            FontScaleValue(0, "FONT_SCALE_TINY", 0.70f, R.string.tiny),
-            FontScaleValue(1, "FONT_SCALE_SMALL", 0.85f, R.string.small),
-            FontScaleValue(2, "FONT_SCALE_NORMAL", 1.00f, R.string.normal),
-            FontScaleValue(3, "FONT_SCALE_LARGE", 1.15f, R.string.large),
-            FontScaleValue(4, "FONT_SCALE_LARGER", 1.30f, R.string.larger),
-            FontScaleValue(5, "FONT_SCALE_LARGEST", 1.45f, R.string.largest),
-            FontScaleValue(6, "FONT_SCALE_HUGE", 1.60f, R.string.huge)
+            FontScaleValue(0, "FONT_SCALE_TINY", FontScalePreferences.SCALE_TINY, R.string.tiny),
+            FontScaleValue(1, "FONT_SCALE_SMALL", FontScalePreferences.SCALE_SMALL, R.string.small),
+            FontScaleValue(2, "FONT_SCALE_NORMAL", FontScalePreferences.SCALE_NORMAL, R.string.normal),
+            FontScaleValue(3, "FONT_SCALE_LARGE", FontScalePreferences.SCALE_LARGE, R.string.large),
+            FontScaleValue(4, "FONT_SCALE_LARGER", FontScalePreferences.SCALE_LARGER, R.string.larger),
+            FontScaleValue(5, "FONT_SCALE_LARGEST", FontScalePreferences.SCALE_LARGEST, R.string.largest),
+            FontScaleValue(6, "FONT_SCALE_HUGE", FontScalePreferences.SCALE_HUGE, R.string.huge)
     )
 
     private val normalFontScaleValue = fontScaleValues[2]
diff --git a/vector/src/main/java/im/vector/app/features/share/IncomingShareController.kt b/vector/src/main/java/im/vector/app/features/share/IncomingShareController.kt
index 6eede93143..0c556192ac 100644
--- a/vector/src/main/java/im/vector/app/features/share/IncomingShareController.kt
+++ b/vector/src/main/java/im/vector/app/features/share/IncomingShareController.kt
@@ -60,6 +60,7 @@ class IncomingShareController @Inject constructor(
                                 roomSummary,
                                 data.selectedRoomIds,
                                 RoomListDisplayMode.FILTERED,
+                                singleLineLastEvent = false,
                                 callback?.let { it::onRoomClicked },
                                 callback?.let { it::onRoomLongClicked }
                         )
diff --git a/vector/src/main/res/drawable/placeholder_shape_8.xml b/vector/src/main/res/drawable/placeholder_shape_8.xml
index 503389788d..4e015d4a56 100644
--- a/vector/src/main/res/drawable/placeholder_shape_8.xml
+++ b/vector/src/main/res/drawable/placeholder_shape_8.xml
@@ -2,10 +2,9 @@
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
     android:shape="rectangle">
 
-    <size android:width="40dp" android:height="40dp"/>
 
     <solid android:color="?vctr_reaction_background_off" />
 
     <corners android:radius="8dp" />
 
-</shape>
\ No newline at end of file
+</shape>
diff --git a/vector/src/main/res/layout/item_recent_room.xml b/vector/src/main/res/layout/item_recent_room.xml
index b2d311d328..7feb8f0d16 100644
--- a/vector/src/main/res/layout/item_recent_room.xml
+++ b/vector/src/main/res/layout/item_recent_room.xml
@@ -5,7 +5,6 @@
     android:id="@+id/recentRoot"
     android:layout_width="84dp"
     android:layout_height="wrap_content"
-    android:background="?vctr_toolbar_background"
     android:clickable="true"
     android:focusable="true"
     android:foreground="?attr/selectableItemBackground"
diff --git a/vector/src/main/res/layout/item_room.xml b/vector/src/main/res/layout/item_room.xml
index ab0af18acb..a94cc0738b 100644
--- a/vector/src/main/res/layout/item_room.xml
+++ b/vector/src/main/res/layout/item_room.xml
@@ -190,7 +190,7 @@
         android:layout_marginTop="3dp"
         android:layout_marginEnd="8dp"
         android:ellipsize="end"
-        android:maxLines="2"
+        android:lines="2"
         android:textAlignment="viewStart"
         android:textColor="?vctr_content_secondary"
         app:layout_constraintEnd_toEndOf="parent"
diff --git a/vector/src/main/res/layout/item_room_placeholder.xml b/vector/src/main/res/layout/item_room_placeholder.xml
index ea264f2668..fa1a83c2a1 100644
--- a/vector/src/main/res/layout/item_room_placeholder.xml
+++ b/vector/src/main/res/layout/item_room_placeholder.xml
@@ -16,7 +16,7 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_marginStart="8dp"
-        app:layout_constraintBottom_toBottomOf="parent"
+        android:layout_marginTop="12dp"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toTopOf="parent">
 
@@ -29,23 +29,20 @@
 
     </FrameLayout>
 
-    <!-- Margin bottom does not work, so I use space -->
-    <Space
-        android:id="@+id/roomAvatarBottomSpace"
-        android:layout_width="0dp"
-        android:layout_height="12dp"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/roomAvatarContainer"
-        tools:layout_marginStart="20dp" />
-
-    <View
+    <TextView
         android:id="@+id/roomNameView"
-        android:layout_width="wrap_content"
-        android:layout_height="15dp"
+        style="@style/Widget.Vector.TextView.Subtitle"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
         android:layout_marginStart="@dimen/layout_horizontal_margin"
         android:layout_marginTop="12dp"
         android:layout_marginEnd="70dp"
         android:background="@drawable/placeholder_shape_8"
+        android:duplicateParentState="true"
+        android:ellipsize="end"
+        android:maxLines="1"
+        android:textColor="?vctr_content_primary"
+        android:textStyle="bold"
         app:layout_constrainedWidth="true"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintHorizontal_bias="0.0"
@@ -53,23 +50,38 @@
         app:layout_constraintStart_toEndOf="@id/roomAvatarContainer"
         app:layout_constraintTop_toTopOf="parent" />
 
-    <View
-        android:id="@+id/roomTypingView"
+    <TextView
+        android:id="@+id/subtitleView"
+        style="@style/Widget.Vector.TextView.Body"
         android:layout_width="0dp"
-        android:layout_height="30dp"
-        android:layout_marginTop="8dp"
-        android:layout_marginEnd="20dp"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="3dp"
+        android:layout_marginEnd="8dp"
         android:background="@drawable/placeholder_shape_8"
+        android:ellipsize="end"
+        android:lines="2"
+        android:textAlignment="viewStart"
+        android:textColor="?vctr_content_secondary"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="@id/roomNameView"
         app:layout_constraintTop_toBottomOf="@id/roomNameView" />
 
+    <!-- Margin bottom does not work, so I use space -->
+    <Space
+        android:id="@+id/roomAvatarBottomSpace"
+        android:layout_width="0dp"
+        android:layout_height="7dp"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/subtitleView"
+        tools:layout_marginStart="120dp" />
+
     <!-- We use vctr_list_separator_system here for a better rendering -->
     <View
         android:id="@+id/roomDividerView"
         android:layout_width="0dp"
         android:layout_height="1dp"
         android:background="?vctr_list_separator_system"
+        app:layout_constraintTop_toBottomOf="@id/roomAvatarBottomSpace"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent" />

From 3c68222fd70c47b9f544707b7db9834a0cee2cb2 Mon Sep 17 00:00:00 2001
From: Florian Renaud <florianr@element.io>
Date: Fri, 16 Sep 2022 09:23:14 +0200
Subject: [PATCH 084/108] Do not save local room into recent rooms

---
 .../internal/session/user/accountdata/UpdateBreadcrumbsTask.kt | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateBreadcrumbsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateBreadcrumbsTask.kt
index c4ea029cbb..a66a8540e7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateBreadcrumbsTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateBreadcrumbsTask.kt
@@ -17,6 +17,7 @@
 package org.matrix.android.sdk.internal.session.user.accountdata
 
 import com.zhuinden.monarchy.Monarchy
+import org.matrix.android.sdk.api.session.room.model.localecho.RoomLocalEcho
 import org.matrix.android.sdk.internal.database.model.BreadcrumbsEntity
 import org.matrix.android.sdk.internal.database.query.get
 import org.matrix.android.sdk.internal.di.SessionDatabase
@@ -41,6 +42,8 @@ internal class DefaultUpdateBreadcrumbsTask @Inject constructor(
 ) : UpdateBreadcrumbsTask {
 
     override suspend fun execute(params: UpdateBreadcrumbsTask.Params) {
+        // Do not add local rooms to the recent rooms list as they should not be known by the server
+        if (RoomLocalEcho.isLocalEchoId(params.newTopRoomId)) return
         val newBreadcrumbs =
                 // Get the breadcrumbs entity, if any
                 monarchy.fetchCopied { BreadcrumbsEntity.get(it) }

From 14d2aec506793794a39566b1b2c62c363914a34e Mon Sep 17 00:00:00 2001
From: Florian Renaud <florianr@element.io>
Date: Fri, 16 Sep 2022 09:44:23 +0200
Subject: [PATCH 085/108] Start DM - Handle the local rooms within the new
 AppLayout

---
 .../room/list/home/HomeRoomListFragment.kt    |  6 ++++++
 .../room/list/home/HomeRoomListViewModel.kt   | 21 +++++++++++++++++++
 .../room/list/home/HomeRoomListViewState.kt   |  3 ++-
 3 files changed, 29 insertions(+), 1 deletion(-)

diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt
index 88bbc6986f..debcc101cf 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt
@@ -40,6 +40,7 @@ import im.vector.app.databinding.FragmentRoomListBinding
 import im.vector.app.features.analytics.plan.ViewRoom
 import im.vector.app.features.home.room.list.RoomListAnimator
 import im.vector.app.features.home.room.list.RoomListListener
+import im.vector.app.features.home.room.list.RoomListViewState
 import im.vector.app.features.home.room.list.actions.RoomListQuickActionsBottomSheet
 import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedAction
 import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel
@@ -98,6 +99,11 @@ class HomeRoomListFragment :
                 is HomeRoomListViewEvents.Done -> Unit
             }
         }
+
+        roomListViewModel.onEach(HomeRoomListViewState::localRoomIds) {
+            // Local rooms should not exist anymore when the room list is shown
+            roomListViewModel.deleteLocalRooms(it)
+        }
     }
 
     private fun handleQuickActions(quickAction: RoomListQuickActionsSharedAction) {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt
index e06815b5fd..c26783ab6c 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt
@@ -49,6 +49,7 @@ import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.launch
 import org.matrix.android.sdk.api.extensions.orFalse
+import org.matrix.android.sdk.api.query.QueryStringValue
 import org.matrix.android.sdk.api.query.RoomCategoryFilter
 import org.matrix.android.sdk.api.query.RoomTagQueryFilter
 import org.matrix.android.sdk.api.query.toActiveSpaceOrNoFilter
@@ -60,6 +61,7 @@ import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
 import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult
 import org.matrix.android.sdk.api.session.room.model.Membership
 import org.matrix.android.sdk.api.session.room.model.RoomSummary
+import org.matrix.android.sdk.api.session.room.model.localecho.RoomLocalEcho
 import org.matrix.android.sdk.api.session.room.model.tag.RoomTag
 import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
 import org.matrix.android.sdk.api.session.room.state.isPublic
@@ -103,6 +105,7 @@ class HomeRoomListViewModel @AssistedInject constructor(
         observeRecents()
         observeFilterTabs()
         observeRooms()
+        observeLocalRooms()
     }
 
     private fun observeInvites() {
@@ -261,6 +264,16 @@ class HomeRoomListViewModel @AssistedInject constructor(
         }.launchIn(viewModelScope)
     }
 
+    private fun observeLocalRooms() {
+        session
+                .flow()
+                .liveRoomSummaries(roomSummaryQueryParams {
+                    roomId = QueryStringValue.Contains(RoomLocalEcho.PREFIX)
+                })
+                .map { page -> page.map { it.roomId } }
+                .setOnEach { copy(localRoomIds = it) }
+    }
+
     private fun emitEmptyState() {
         viewModelScope.launch {
             val emptyState = getEmptyStateData(currentFilter, spaceStateHandler.getCurrentSpace())
@@ -349,6 +362,14 @@ class HomeRoomListViewModel @AssistedInject constructor(
         return session.getRoom(roomId)?.stateService()?.isPublic().orFalse()
     }
 
+    fun deleteLocalRooms(roomsIds: Iterable<String>) {
+        viewModelScope.launch {
+            roomsIds.forEach {
+                session.roomService().deleteLocalRoom(it)
+            }
+        }
+    }
+
     private fun handleSelectRoom(action: HomeRoomListAction.SelectRoom) = withState {
         _viewEvents.post(HomeRoomListViewEvents.SelectRoom(action.roomSummary, false))
     }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewState.kt
index 8647054f3d..2c0b6a63be 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewState.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewState.kt
@@ -26,5 +26,6 @@ import org.matrix.android.sdk.api.session.room.model.RoomSummary
 data class HomeRoomListViewState(
         val state: StateView.State = StateView.State.Content,
         val headersData: RoomsHeadersData = RoomsHeadersData(),
-        val roomsLivePagedList: LiveData<PagedList<RoomSummary>>? = null
+        val roomsLivePagedList: LiveData<PagedList<RoomSummary>>? = null,
+        val localRoomIds: List<String> = emptyList()
 ) : MavericksState

From c28271dd8b9118c6624a75c7ec24b33ca6270cf1 Mon Sep 17 00:00:00 2001
From: Florian Renaud <florianr@element.io>
Date: Fri, 16 Sep 2022 10:06:07 +0200
Subject: [PATCH 086/108] Add changelog

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

diff --git a/changelog.d/7153.wip b/changelog.d/7153.wip
new file mode 100644
index 0000000000..fd12a4197b
--- /dev/null
+++ b/changelog.d/7153.wip
@@ -0,0 +1 @@
+Create DM room only on first message - Handle the local rooms within the new AppLayout

From df3fd6f69175982ed6f3bcb54b605c5f776d099e Mon Sep 17 00:00:00 2001
From: Florian Renaud <florianr@element.io>
Date: Fri, 16 Sep 2022 10:08:16 +0200
Subject: [PATCH 087/108] Remove unused import

---
 .../app/features/home/room/list/home/HomeRoomListFragment.kt     | 1 -
 1 file changed, 1 deletion(-)

diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt
index debcc101cf..1826b58e26 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt
@@ -40,7 +40,6 @@ import im.vector.app.databinding.FragmentRoomListBinding
 import im.vector.app.features.analytics.plan.ViewRoom
 import im.vector.app.features.home.room.list.RoomListAnimator
 import im.vector.app.features.home.room.list.RoomListListener
-import im.vector.app.features.home.room.list.RoomListViewState
 import im.vector.app.features.home.room.list.actions.RoomListQuickActionsBottomSheet
 import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedAction
 import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel

From 8999b40c1af5f6a9e11868f2b1169c487007697c Mon Sep 17 00:00:00 2001
From: Florian Renaud <florianr@element.io>
Date: Mon, 19 Sep 2022 09:58:00 +0200
Subject: [PATCH 088/108] Add action for local rooms deletion

---
 .../features/home/room/list/RoomListAction.kt |  1 +
 .../home/room/list/RoomListFragment.kt        | 11 ++++---
 .../home/room/list/RoomListViewModel.kt       | 32 ++++++++-----------
 .../home/room/list/RoomListViewState.kt       |  1 -
 .../home/room/list/home/HomeRoomListAction.kt |  1 +
 .../room/list/home/HomeRoomListFragment.kt    | 12 ++++---
 .../room/list/home/HomeRoomListViewModel.kt   | 32 ++++++++-----------
 .../room/list/home/HomeRoomListViewState.kt   |  1 -
 8 files changed, 42 insertions(+), 49 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListAction.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListAction.kt
index e6b6b34503..aa982741f7 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListAction.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListAction.kt
@@ -31,4 +31,5 @@ sealed class RoomListAction : VectorViewModelAction {
     data class LeaveRoom(val roomId: String) : RoomListAction()
     data class JoinSuggestedRoom(val roomId: String, val viaServers: List<String>?) : RoomListAction()
     data class ShowRoomDetails(val roomId: String, val viaServers: List<String>?) : RoomListAction()
+    object DeleteAllLocalRoom : RoomListAction()
 }
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 2c876273ea..9591048725 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
@@ -149,10 +149,13 @@ class RoomListFragment :
                         (it.contentEpoxyController as? RoomSummaryPagedController)?.roomChangeMembershipStates = ms
                     }
         }
-        roomListViewModel.onEach(RoomListViewState::localRoomIds) {
-            // Local rooms should not exist anymore when the room list is shown
-            roomListViewModel.deleteLocalRooms(it)
-        }
+    }
+
+    override fun onStart() {
+        super.onStart()
+
+        // Local rooms should not exist anymore when the room list is shown
+        roomListViewModel.handle(RoomListAction.DeleteAllLocalRoom)
     }
 
     private fun refreshCollapseStates() {
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 8283447a4d..74b55d435d 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
@@ -97,7 +97,6 @@ class RoomListViewModel @AssistedInject constructor(
 
     init {
         observeMembershipChanges()
-        observeLocalRooms()
 
         spaceStateHandler.getSelectedSpaceFlow()
                 .distinctUntilChanged()
@@ -125,16 +124,6 @@ class RoomListViewModel @AssistedInject constructor(
                 }
     }
 
-    private fun observeLocalRooms() {
-        session
-                .flow()
-                .liveRoomSummaries(roomSummaryQueryParams {
-                    roomId = QueryStringValue.Contains(RoomLocalEcho.PREFIX)
-                })
-                .map { page -> page.map { it.roomId } }
-                .setOnEach { copy(localRoomIds = it) }
-    }
-
     companion object : MavericksViewModelFactory<RoomListViewModel, RoomListViewState> by hiltMavericksViewModelFactory()
 
     private val roomListSectionBuilder = RoomListSectionBuilder(
@@ -166,6 +155,7 @@ class RoomListViewModel @AssistedInject constructor(
             is RoomListAction.ToggleSection -> handleToggleSection(action.section)
             is RoomListAction.JoinSuggestedRoom -> handleJoinSuggestedRoom(action)
             is RoomListAction.ShowRoomDetails -> handleShowRoomDetails(action)
+            RoomListAction.DeleteAllLocalRoom -> handleDeleteLocalRooms()
         }
     }
 
@@ -173,14 +163,6 @@ class RoomListViewModel @AssistedInject constructor(
         return session.getRoom(roomId)?.stateService()?.isPublic().orFalse()
     }
 
-    fun deleteLocalRooms(roomsIds: Iterable<String>) {
-        viewModelScope.launch {
-            roomsIds.forEach {
-                session.roomService().deleteLocalRoom(it)
-            }
-        }
-    }
-
     // PRIVATE METHODS *****************************************************************************
 
     private fun handleSelectRoom(action: RoomListAction.SelectRoom) = withState {
@@ -338,4 +320,16 @@ class RoomListViewModel @AssistedInject constructor(
             _viewEvents.post(value)
         }
     }
+
+    private fun handleDeleteLocalRooms() {
+        val localRoomIds = session.roomService()
+                .getRoomSummaries(roomSummaryQueryParams { roomId = QueryStringValue.Contains(RoomLocalEcho.PREFIX) })
+                .map { it.roomId }
+
+        viewModelScope.launch {
+            localRoomIds.forEach {
+                session.roomService().deleteLocalRoom(it)
+            }
+        }
+    }
 }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewState.kt
index 5f62cba948..d897225fd6 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewState.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewState.kt
@@ -31,7 +31,6 @@ data class RoomListViewState(
         val asyncSuggestedRooms: Async<List<SpaceChildInfo>> = Uninitialized,
         val currentUserName: String? = null,
         val asyncSelectedSpace: Async<RoomSummary?> = Uninitialized,
-        val localRoomIds: List<String> = emptyList()
 ) : MavericksState {
 
     constructor(args: RoomListParams) : this(displayMode = args.displayMode)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListAction.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListAction.kt
index b7ade559da..5760874812 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListAction.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListAction.kt
@@ -27,4 +27,5 @@ sealed class HomeRoomListAction : VectorViewModelAction {
     data class ToggleTag(val roomId: String, val tag: String) : HomeRoomListAction()
     data class LeaveRoom(val roomId: String) : HomeRoomListAction()
     data class ChangeRoomFilter(val filter: HomeRoomFilter) : HomeRoomListAction()
+    object DeleteAllLocalRoom : HomeRoomListAction()
 }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt
index 1826b58e26..829634259a 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt
@@ -83,6 +83,13 @@ class HomeRoomListFragment :
         setupRecyclerView()
     }
 
+    override fun onStart() {
+        super.onStart()
+
+        // Local rooms should not exist anymore when the room list is shown
+        roomListViewModel.handle(HomeRoomListAction.DeleteAllLocalRoom)
+    }
+
     private fun setupObservers() {
         sharedQuickActionsViewModel = activityViewModelProvider[RoomListQuickActionsSharedActionViewModel::class.java]
         sharedQuickActionsViewModel
@@ -98,11 +105,6 @@ class HomeRoomListFragment :
                 is HomeRoomListViewEvents.Done -> Unit
             }
         }
-
-        roomListViewModel.onEach(HomeRoomListViewState::localRoomIds) {
-            // Local rooms should not exist anymore when the room list is shown
-            roomListViewModel.deleteLocalRooms(it)
-        }
     }
 
     private fun handleQuickActions(quickAction: RoomListQuickActionsSharedAction) {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt
index c26783ab6c..35b2f02917 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt
@@ -105,7 +105,6 @@ class HomeRoomListViewModel @AssistedInject constructor(
         observeRecents()
         observeFilterTabs()
         observeRooms()
-        observeLocalRooms()
     }
 
     private fun observeInvites() {
@@ -264,16 +263,6 @@ class HomeRoomListViewModel @AssistedInject constructor(
         }.launchIn(viewModelScope)
     }
 
-    private fun observeLocalRooms() {
-        session
-                .flow()
-                .liveRoomSummaries(roomSummaryQueryParams {
-                    roomId = QueryStringValue.Contains(RoomLocalEcho.PREFIX)
-                })
-                .map { page -> page.map { it.roomId } }
-                .setOnEach { copy(localRoomIds = it) }
-    }
-
     private fun emitEmptyState() {
         viewModelScope.launch {
             val emptyState = getEmptyStateData(currentFilter, spaceStateHandler.getCurrentSpace())
@@ -342,6 +331,7 @@ class HomeRoomListViewModel @AssistedInject constructor(
             is HomeRoomListAction.ChangeRoomNotificationState -> handleChangeNotificationMode(action)
             is HomeRoomListAction.ToggleTag -> handleToggleTag(action)
             is HomeRoomListAction.ChangeRoomFilter -> handleChangeRoomFilter(action.filter)
+            HomeRoomListAction.DeleteAllLocalRoom -> handleDeleteLocalRooms()
         }
     }
 
@@ -362,14 +352,6 @@ class HomeRoomListViewModel @AssistedInject constructor(
         return session.getRoom(roomId)?.stateService()?.isPublic().orFalse()
     }
 
-    fun deleteLocalRooms(roomsIds: Iterable<String>) {
-        viewModelScope.launch {
-            roomsIds.forEach {
-                session.roomService().deleteLocalRoom(it)
-            }
-        }
-    }
-
     private fun handleSelectRoom(action: HomeRoomListAction.SelectRoom) = withState {
         _viewEvents.post(HomeRoomListViewEvents.SelectRoom(action.roomSummary, false))
     }
@@ -420,6 +402,18 @@ class HomeRoomListViewModel @AssistedInject constructor(
         }
     }
 
+    private fun handleDeleteLocalRooms() = withState {
+        val localRoomIds = session.roomService()
+                .getRoomSummaries(roomSummaryQueryParams { roomId = QueryStringValue.Contains(RoomLocalEcho.PREFIX) })
+                .map { it.roomId }
+
+        viewModelScope.launch {
+            localRoomIds.forEach {
+                session.roomService().deleteLocalRoom(it)
+            }
+        }
+    }
+
     private fun String.otherTag(): String? {
         return when (this) {
             RoomTag.ROOM_TAG_FAVOURITE -> RoomTag.ROOM_TAG_LOW_PRIORITY
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewState.kt
index 2c0b6a63be..95625bc4b9 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewState.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewState.kt
@@ -27,5 +27,4 @@ data class HomeRoomListViewState(
         val state: StateView.State = StateView.State.Content,
         val headersData: RoomsHeadersData = RoomsHeadersData(),
         val roomsLivePagedList: LiveData<PagedList<RoomSummary>>? = null,
-        val localRoomIds: List<String> = emptyList()
 ) : MavericksState

From 648498e2de4e8af16b8a576c576c3f6737771812 Mon Sep 17 00:00:00 2001
From: Florian Renaud <florianr@element.io>
Date: Mon, 19 Sep 2022 15:22:17 +0200
Subject: [PATCH 089/108] Move local room check from UpdateBreadcrumbsTask to
 RoomService

---
 .../android/sdk/internal/session/room/DefaultRoomService.kt | 6 +++++-
 .../session/user/accountdata/UpdateBreadcrumbsTask.kt       | 3 ---
 2 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt
index 989bcaee44..dd945716a1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt
@@ -33,6 +33,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.create.CreateRoomParams
+import org.matrix.android.sdk.api.session.room.model.localecho.RoomLocalEcho
 import org.matrix.android.sdk.api.session.room.peeking.PeekResult
 import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
 import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
@@ -173,7 +174,10 @@ internal class DefaultRoomService @Inject constructor(
     }
 
     override suspend fun onRoomDisplayed(roomId: String) {
-        updateBreadcrumbsTask.execute(UpdateBreadcrumbsTask.Params(roomId))
+        // Do not add local rooms to the recent rooms list as they should not be known by the server
+        if (!RoomLocalEcho.isLocalEchoId(roomId)) {
+            updateBreadcrumbsTask.execute(UpdateBreadcrumbsTask.Params(roomId))
+        }
     }
 
     override suspend fun joinRoom(roomIdOrAlias: String, reason: String?, viaServers: List<String>) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateBreadcrumbsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateBreadcrumbsTask.kt
index a66a8540e7..c4ea029cbb 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateBreadcrumbsTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateBreadcrumbsTask.kt
@@ -17,7 +17,6 @@
 package org.matrix.android.sdk.internal.session.user.accountdata
 
 import com.zhuinden.monarchy.Monarchy
-import org.matrix.android.sdk.api.session.room.model.localecho.RoomLocalEcho
 import org.matrix.android.sdk.internal.database.model.BreadcrumbsEntity
 import org.matrix.android.sdk.internal.database.query.get
 import org.matrix.android.sdk.internal.di.SessionDatabase
@@ -42,8 +41,6 @@ internal class DefaultUpdateBreadcrumbsTask @Inject constructor(
 ) : UpdateBreadcrumbsTask {
 
     override suspend fun execute(params: UpdateBreadcrumbsTask.Params) {
-        // Do not add local rooms to the recent rooms list as they should not be known by the server
-        if (RoomLocalEcho.isLocalEchoId(params.newTopRoomId)) return
         val newBreadcrumbs =
                 // Get the breadcrumbs entity, if any
                 monarchy.fetchCopied { BreadcrumbsEntity.get(it) }

From 5e504942cad4e8182ce3cd23e277e43692c9a36b Mon Sep 17 00:00:00 2001
From: Florian Renaud <florianr@element.io>
Date: Mon, 19 Sep 2022 15:49:44 +0200
Subject: [PATCH 090/108] Delete the local read receipts when deleting the
 local rooms

---
 .../internal/database/query/ReadReceiptEntityQueries.kt  | 5 +++++
 .../internal/session/room/delete/DeleteLocalRoomTask.kt  | 9 +++++++++
 2 files changed, 14 insertions(+)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadReceiptEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadReceiptEntityQueries.kt
index b180c06e2c..170814d3f2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadReceiptEntityQueries.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadReceiptEntityQueries.kt
@@ -33,6 +33,11 @@ internal fun ReadReceiptEntity.Companion.whereUserId(realm: Realm, userId: Strin
             .equalTo(ReadReceiptEntityFields.USER_ID, userId)
 }
 
+internal fun ReadReceiptEntity.Companion.whereRoomId(realm: Realm, roomId: String): RealmQuery<ReadReceiptEntity> {
+    return realm.where<ReadReceiptEntity>()
+            .equalTo(ReadReceiptEntityFields.ROOM_ID, roomId)
+}
+
 internal fun ReadReceiptEntity.Companion.createUnmanaged(roomId: String, eventId: String, userId: String, originServerTs: Double): ReadReceiptEntity {
     return ReadReceiptEntity().apply {
         this.primaryKey = "${roomId}_$userId"
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/delete/DeleteLocalRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/delete/DeleteLocalRoomTask.kt
index 49951d2da0..a60c7e6a27 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/delete/DeleteLocalRoomTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/delete/DeleteLocalRoomTask.kt
@@ -22,12 +22,15 @@ 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.LocalRoomSummaryEntity
+import org.matrix.android.sdk.internal.database.model.ReadReceiptEntity
+import org.matrix.android.sdk.internal.database.model.ReadReceiptsSummaryEntity
 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.RoomSummaryEntity
 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.where
+import org.matrix.android.sdk.internal.database.query.whereInRoom
 import org.matrix.android.sdk.internal.database.query.whereRoomId
 import org.matrix.android.sdk.internal.di.SessionDatabase
 import org.matrix.android.sdk.internal.session.room.delete.DeleteLocalRoomTask.Params
@@ -50,6 +53,12 @@ internal class DefaultDeleteLocalRoomTask @Inject constructor(
         if (RoomLocalEcho.isLocalEchoId(roomId)) {
             monarchy.awaitTransaction { realm ->
                 Timber.i("## DeleteLocalRoomTask - delete local room id $roomId")
+                ReadReceiptsSummaryEntity.whereInRoom(realm, roomId = roomId).findAll()
+                        ?.also { Timber.i("## DeleteLocalRoomTask - ReadReceiptsSummaryEntity - delete ${it.size} entries") }
+                        ?.deleteAllFromRealm()
+                ReadReceiptEntity.whereRoomId(realm, roomId = roomId).findAll()
+                        ?.also { Timber.i("## DeleteLocalRoomTask - ReadReceiptEntity - delete ${it.size} entries") }
+                        ?.deleteAllFromRealm()
                 RoomMemberSummaryEntity.where(realm, roomId = roomId).findAll()
                         ?.also { Timber.i("## DeleteLocalRoomTask - RoomMemberSummaryEntity - delete ${it.size} entries") }
                         ?.deleteAllFromRealm()

From 0e45494c11e35e9f49c6a6e794548c6be1855bff Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoit@matrix.org>
Date: Mon, 19 Sep 2022 16:26:38 +0200
Subject: [PATCH 091/108] Comment out `continue-on-error: true` It does not
 mark the build as failed.

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

diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index e260749374..cda0bd5e31 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -39,7 +39,7 @@ jobs:
       - name: Run all the codecoverage tests at once
         id: tests
         uses: reactivecircus/android-emulator-runner@v2
-        continue-on-error: true
+        # continue-on-error: true
         with:
           api-level: 28
           arch: x86

From 0c28384ece527f78f8fe86bed89d47e990f05a9a Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoit@matrix.org>
Date: Mon, 19 Sep 2022 18:12:19 +0200
Subject: [PATCH 092/108] Create AVD and generate snapshot for caching. Also
 force AVD creation when no cache hit

---
 .github/workflows/tests.yml | 30 +++++++++++++++++++++++++++---
 1 file changed, 27 insertions(+), 3 deletions(-)

diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index cda0bd5e31..07eeec5643 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -14,6 +14,9 @@ jobs:
   tests:
     name: Runs all tests
     runs-on: buildjet-4vcpu-ubuntu-2204
+    strategy:
+      matrix:
+        api-level: [28]
     # Allow all jobs on main and develop. Just one per PR.
     concurrency:
       group: ${{ github.ref == 'refs/heads/main' && format('unit-tests-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('unit-tests-develop-{0}', github.sha) || format('unit-tests-{0}', github.ref) }}
@@ -36,18 +39,39 @@ jobs:
           httpPort: 8080
           disableRateLimiting: true
           public_baseurl: "http://10.0.2.2:8080/"
+
+      - name: AVD cache
+        uses: actions/cache@v3
+        id: avd-cache
+        with:
+          path: |
+            ~/.android/avd/*
+            ~/.android/adb*
+          key: avd-${{ matrix.api-level }}
+
+      - name: create AVD and generate snapshot for caching
+        if: steps.avd-cache.outputs.cache-hit != 'true'
+        uses: reactivecircus/android-emulator-runner@v2
+        with:
+          api-level: ${{ matrix.api-level }}
+          arch: x86
+          profile: Nexus 5X
+          force-avd-creation: true # Is set to false in the doc https://github.com/ReactiveCircus/android-emulator-runner
+          emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
+          disable-animations: true
+          script: echo "Generated AVD snapshot for caching."
+
       - name: Run all the codecoverage tests at once
-        id: tests
         uses: reactivecircus/android-emulator-runner@v2
         # continue-on-error: true
         with:
-          api-level: 28
+          api-level: ${{ matrix.api-level }}
           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
           disable-animations: true
-          emulator-build: 7425822
+          # emulator-build: 7425822
           script: |
             ./gradlew gatherGplayDebugStringTemplates $CI_GRADLE_ARG_PROPERTIES
             ./gradlew unitTestsWithCoverage $CI_GRADLE_ARG_PROPERTIES

From aa010dedff4e9070a559410eaa4fe9d896cd3699 Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoit@matrix.org>
Date: Mon, 19 Sep 2022 18:36:01 +0200
Subject: [PATCH 093/108] Try to upload integration test report log

---
 .github/workflows/tests.yml | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 07eeec5643..fb8e3080ae 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -95,6 +95,15 @@ jobs:
       ###       ./gradlew instrumentationTestsWithCoverage $CI_GRADLE_ARG_PROPERTIES
       ###       ./gradlew generateCoverageReport $CI_GRADLE_ARG_PROPERTIES
 
+      - name: Upload Integration Test Report Log
+        uses: actions/upload-artifact@v3
+        if: always()
+        with:
+          name: integration-test-error-results
+          path: |
+            */build/outputs/androidTest-results/connected/
+            */build/reports/androidTests/connected/
+
       # we may have failed a previous step and retried, that's OK
       - name: Publish results to Sonar
         env:

From 6da6f6a7f4bec96ceafcb8b21fab28b16a3fa1f9 Mon Sep 17 00:00:00 2001
From: Nikita Fedrunov <66663241+fedrunov@users.noreply.github.com>
Date: Mon, 19 Sep 2022 22:34:56 +0200
Subject: [PATCH 094/108] add qr code option to home screen menu (#7177)

---
 .../java/im/vector/app/features/home/HomeActivity.kt     | 9 +++++++++
 vector/src/main/res/menu/menu_new_home.xml               | 6 +++++-
 2 files changed, 14 insertions(+), 1 deletion(-)

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 dd27b5550c..8fb73d6571 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
@@ -84,6 +84,7 @@ import im.vector.app.features.spaces.SpaceSettingsMenuBottomSheet
 import im.vector.app.features.spaces.invite.SpaceInviteBottomSheet
 import im.vector.app.features.spaces.share.ShareSpaceBottomSheet
 import im.vector.app.features.themes.ThemeUtils
+import im.vector.app.features.usercode.UserCodeActivity
 import im.vector.app.features.workers.signout.ServerBackupStatusViewModel
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
@@ -634,10 +635,18 @@ class HomeActivity :
                 launchInviteFriends()
                 true
             }
+            R.id.menu_home_qr -> {
+                launchQrCode()
+                true
+            }
             else -> false
         }
     }
 
+    private fun launchQrCode() {
+        startActivity(UserCodeActivity.newIntent(this, sharedActionViewModel.session.myUserId))
+    }
+
     private fun launchInviteFriends() {
         activeSessionHolder.getSafeActiveSession()?.permalinkService()?.createPermalink(sharedActionViewModel.session.myUserId)?.let { permalink ->
             analyticsTracker.screen(MobileScreen(screenName = MobileScreen.ScreenName.InviteFriends))
diff --git a/vector/src/main/res/menu/menu_new_home.xml b/vector/src/main/res/menu/menu_new_home.xml
index 6cd52e5608..2292480bac 100644
--- a/vector/src/main/res/menu/menu_new_home.xml
+++ b/vector/src/main/res/menu/menu_new_home.xml
@@ -12,6 +12,11 @@
         android:title="@string/invite_friends"
         app:showAsAction="never" />
 
+    <item
+        android:id="@+id/menu_home_qr"
+        android:title="@string/add_by_qr_code"
+        app:showAsAction="never" />
+
     <item
         android:id="@+id/menu_home_suggestion"
         android:icon="@drawable/ic_material_bug_report"
@@ -42,5 +47,4 @@
         android:title="@string/home_filter_placeholder_home"
         app:iconTint="?vctr_content_secondary"
         app:showAsAction="ifRoom" />
-
 </menu>

From b4f730205757c6011c1f892df4921839ef5d799c Mon Sep 17 00:00:00 2001
From: Nikita Fedrunov <66663241+fedrunov@users.noreply.github.com>
Date: Mon, 19 Sep 2022 22:35:15 +0200
Subject: [PATCH 095/108] release notes screen now properly shown on update to
 a version with app layout labs flag enabled by default (#7175)

---
 .../features/home/HomeActivityViewModel.kt    | 22 ++++++++++---------
 .../release/ReleaseNotesPreferencesStore.kt   |  2 +-
 2 files changed, 13 insertions(+), 11 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt
index cbe531ea71..a08298e402 100644
--- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt
@@ -119,17 +119,19 @@ class HomeActivityViewModel @AssistedInject constructor(
     }
 
     private fun observeReleaseNotes() = withState { state ->
-        // we don't want to show release notes for new users or after relogin
-        if (state.authenticationDescription == null && vectorPreferences.isNewAppLayoutEnabled()) {
-            releaseNotesPreferencesStore.appLayoutOnboardingShown.onEach { isAppLayoutOnboardingShown ->
-                if (!isAppLayoutOnboardingShown) {
-                    _viewEvents.post(HomeActivityViewEvents.ShowReleaseNotes)
+        if (vectorPreferences.isNewAppLayoutEnabled()) {
+            // we don't want to show release notes for new users or after relogin
+            if (state.authenticationDescription == null) {
+                releaseNotesPreferencesStore.appLayoutOnboardingShown.onEach { isAppLayoutOnboardingShown ->
+                    if (!isAppLayoutOnboardingShown) {
+                        _viewEvents.post(HomeActivityViewEvents.ShowReleaseNotes)
+                    }
+                }.launchIn(viewModelScope)
+            } else {
+                // we assume that users which came from auth flow either have seen updates already (relogin) or don't need them (new user)
+                viewModelScope.launch {
+                    releaseNotesPreferencesStore.setAppLayoutOnboardingShown(true)
                 }
-            }.launchIn(viewModelScope)
-        } else {
-            // we assume that users which came from auth flow either have seen updates already (relogin) or don't need them (new user)
-            viewModelScope.launch {
-                releaseNotesPreferencesStore.setAppLayoutOnboardingShown(true)
             }
         }
     }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/release/ReleaseNotesPreferencesStore.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/release/ReleaseNotesPreferencesStore.kt
index cefe107905..3320bdf314 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/home/release/ReleaseNotesPreferencesStore.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/release/ReleaseNotesPreferencesStore.kt
@@ -34,7 +34,7 @@ class ReleaseNotesPreferencesStore @Inject constructor(
         private val context: Context
 ) {
 
-    private val isAppLayoutOnboardingShown = booleanPreferencesKey("SETTINGS_APP_LAYOUT_ONBOARDING_SHOWN")
+    private val isAppLayoutOnboardingShown = booleanPreferencesKey("SETTINGS_APP_LAYOUT_ONBOARDING_DISPLAYED")
 
     val appLayoutOnboardingShown: Flow<Boolean> = context.dataStore.data
             .map { preferences -> preferences[isAppLayoutOnboardingShown].orFalse() }

From d8ff688e76cf29a99f2649142a4a3d5e942a9e92 Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoitm@matrix.org>
Date: Tue, 20 Sep 2022 09:51:22 +0200
Subject: [PATCH 096/108] Fix typo in changelog.

Co-authored-by: manuroe <manuroe@users.noreply.github.com>
---
 changelog.d/7108.misc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/changelog.d/7108.misc b/changelog.d/7108.misc
index d4b15a0222..165bd52e57 100644
--- a/changelog.d/7108.misc
+++ b/changelog.d/7108.misc
@@ -1 +1 @@
-Move some GitHub action to buildjet runner, and remove the second attempt to run integration tests.
+Move some GitHub actions to buildjet runners, and remove the second attempt to run integration tests.

From f6dfd643261b243be872223c1dcad40b10dd23d0 Mon Sep 17 00:00:00 2001
From: NIkita Fedrunov <fedrunov@element.io>
Date: Tue, 20 Sep 2022 23:27:00 +0200
Subject: [PATCH 097/108] fixed all screens test to follow latest changes

---
 .../androidTest/java/im/vector/app/ui/robot/ElementRobot.kt | 6 ------
 1 file changed, 6 deletions(-)

diff --git a/vector-app/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt b/vector-app/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt
index 9fb7fceebf..d9dfb0facf 100644
--- a/vector-app/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt
+++ b/vector-app/src/androidTest/java/im/vector/app/ui/robot/ElementRobot.kt
@@ -110,9 +110,6 @@ class ElementRobot(
         closeSoftKeyboard()
         block(NewDirectMessageRobot())
         pressBack()
-        if (labsPreferences.isNewAppLayoutEnabled) {
-            pressBack() // close create dialog
-        }
         waitUntilViewVisible(withId(R.id.roomListContainer))
     }
 
@@ -121,9 +118,6 @@ class ElementRobot(
             clickOn(R.id.bottom_action_rooms)
         }
         RoomListRobot(labsPreferences).newRoom { block() }
-        if (labsPreferences.isNewAppLayoutEnabled) {
-            pressBack() // close create dialog
-        }
         waitUntilViewVisible(withId(R.id.roomListContainer))
     }
 

From fe1e74fa06b7814a50fa1d99d39e8e3f60e0707c Mon Sep 17 00:00:00 2001
From: ericdecanini <eddecanini@gmail.com>
Date: Tue, 20 Sep 2022 18:22:39 -0400
Subject: [PATCH 098/108] Fixes room list not getting updated when not in focus

---
 .../home/room/list/home/HomeRoomListFragment.kt        |  8 ++++----
 .../home/room/list/home/HomeRoomListViewModel.kt       | 10 +++++++++-
 .../home/room/list/home/HomeRoomListViewState.kt       |  3 +--
 3 files changed, 14 insertions(+), 7 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt
index 829634259a..a0bd2d670a 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt
@@ -152,10 +152,10 @@ class HomeRoomListFragment :
             headersController.submitData(it)
         }
 
-        roomListViewModel.onEach(HomeRoomListViewState::roomsLivePagedList) { roomsListLive ->
-            roomsListLive?.observe(viewLifecycleOwner) { roomsList ->
-                roomsController.submitList(roomsList)
-                if (roomsList.isEmpty()) {
+        roomListViewModel.onEach(HomeRoomListViewState::roomsLivePagedList) { roomsList ->
+            roomsList?.let {
+                roomsController.submitList(it)
+                if (it.isEmpty()) {
                     roomsController.requestForcedModelBuild()
                 }
             }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt
index 35b2f02917..93856abd30 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt
@@ -17,6 +17,7 @@
 package im.vector.app.features.home.room.list.home
 
 import android.widget.ImageView
+import androidx.lifecycle.asFlow
 import androidx.paging.PagedList
 import arrow.core.Option
 import arrow.core.toOption
@@ -43,6 +44,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
@@ -254,7 +256,13 @@ class HomeRoomListViewModel @AssistedInject constructor(
                 .also { roomsFlow = it }
                 .launchIn(viewModelScope)
 
-        setState { copy(roomsLivePagedList = liveResults.livePagedList) }
+        liveResults.livePagedList
+                .asFlow()
+                .onEach {
+                    setState { copy(roomsLivePagedList = it) }
+                }
+                .flowOn(Dispatchers.Default)
+                .launchIn(viewModelScope)
     }
 
     private fun observeOrderPreferences() {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewState.kt
index 95625bc4b9..7b7719981f 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewState.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewState.kt
@@ -16,7 +16,6 @@
 
 package im.vector.app.features.home.room.list.home
 
-import androidx.lifecycle.LiveData
 import androidx.paging.PagedList
 import com.airbnb.mvrx.MavericksState
 import im.vector.app.core.platform.StateView
@@ -26,5 +25,5 @@ import org.matrix.android.sdk.api.session.room.model.RoomSummary
 data class HomeRoomListViewState(
         val state: StateView.State = StateView.State.Content,
         val headersData: RoomsHeadersData = RoomsHeadersData(),
-        val roomsLivePagedList: LiveData<PagedList<RoomSummary>>? = null,
+        val roomsLivePagedList: PagedList<RoomSummary>? = null,
 ) : MavericksState

From 1a93bbf92fb0b6c7872db7f2fb9024b6c21290c5 Mon Sep 17 00:00:00 2001
From: ericdecanini <eddecanini@gmail.com>
Date: Tue, 20 Sep 2022 18:32:59 -0400
Subject: [PATCH 099/108] Renames roomsPagedList

---
 .../app/features/home/room/list/home/HomeRoomListFragment.kt    | 2 +-
 .../app/features/home/room/list/home/HomeRoomListViewModel.kt   | 2 +-
 .../app/features/home/room/list/home/HomeRoomListViewState.kt   | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt
index a0bd2d670a..4d61057b59 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt
@@ -152,7 +152,7 @@ class HomeRoomListFragment :
             headersController.submitData(it)
         }
 
-        roomListViewModel.onEach(HomeRoomListViewState::roomsLivePagedList) { roomsList ->
+        roomListViewModel.onEach(HomeRoomListViewState::roomsPagedList) { roomsList ->
             roomsList?.let {
                 roomsController.submitList(it)
                 if (it.isEmpty()) {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt
index 93856abd30..4bc5d8ba95 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt
@@ -259,7 +259,7 @@ class HomeRoomListViewModel @AssistedInject constructor(
         liveResults.livePagedList
                 .asFlow()
                 .onEach {
-                    setState { copy(roomsLivePagedList = it) }
+                    setState { copy(roomsPagedList = it) }
                 }
                 .flowOn(Dispatchers.Default)
                 .launchIn(viewModelScope)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewState.kt
index 7b7719981f..db3a57e63e 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewState.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewState.kt
@@ -25,5 +25,5 @@ import org.matrix.android.sdk.api.session.room.model.RoomSummary
 data class HomeRoomListViewState(
         val state: StateView.State = StateView.State.Content,
         val headersData: RoomsHeadersData = RoomsHeadersData(),
-        val roomsLivePagedList: PagedList<RoomSummary>? = null,
+        val roomsPagedList: PagedList<RoomSummary>? = null,
 ) : MavericksState

From 821636bcb23f2498e4f149add1bf803dae7c029c Mon Sep 17 00:00:00 2001
From: ericdecanini <eddecanini@gmail.com>
Date: Tue, 20 Sep 2022 18:36:25 -0400
Subject: [PATCH 100/108] Adds changelog file

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

diff --git a/changelog.d/7186.bugfix b/changelog.d/7186.bugfix
new file mode 100644
index 0000000000..418dbbda9f
--- /dev/null
+++ b/changelog.d/7186.bugfix
@@ -0,0 +1 @@
+Fixes Room List not getting updated when fragment is not in focus

From e9d809d9c338b9ed3707945ba9ef4dc6e24a22a2 Mon Sep 17 00:00:00 2001
From: Florian Renaud <florianr@element.io>
Date: Mon, 19 Sep 2022 18:50:05 +0200
Subject: [PATCH 101/108] Move and enable deferred DMs into labs settings

---
 library/ui-strings/src/main/res/values/strings.xml        | 3 +++
 .../features/debug/features/DebugFeaturesStateFactory.kt  | 5 -----
 .../app/features/debug/features/DebugVectorFeatures.kt    | 3 ---
 vector-config/src/main/res/values/config-settings.xml     | 1 +
 .../main/java/im/vector/app/features/VectorFeatures.kt    | 2 --
 .../features/createdirect/CreateDirectRoomViewModel.kt    | 6 +++---
 .../vector/app/features/createdirect/DirectRoomHelper.kt  | 6 +++---
 .../im/vector/app/features/settings/VectorPreferences.kt  | 8 ++++++++
 vector/src/main/res/xml/vector_settings_labs.xml          | 6 ++++++
 9 files changed, 24 insertions(+), 16 deletions(-)

diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml
index 714b48e8b4..99ff55a93d 100644
--- a/library/ui-strings/src/main/res/values/strings.xml
+++ b/library/ui-strings/src/main/res/values/strings.xml
@@ -442,6 +442,9 @@
     <string name="labs_enable_new_app_layout_title">Enable new layout</string>
     <string name="labs_enable_new_app_layout_summary">A simplified Element with optional tabs</string>
 
+    <string name="labs_enable_deferred_dm_title">Enable deferred DMs</string>
+    <string name="labs_enable_deferred_dm_summary">Direct rooms will be created after sending a first message.</string>
+
     <!-- Home fragment -->
     <string name="invitations_header">Invites</string>
     <string name="low_priority_header">Low priority</string>
diff --git a/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt b/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt
index 9b2711a8c3..9118dea1e3 100644
--- a/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt
+++ b/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt
@@ -80,11 +80,6 @@ class DebugFeaturesStateFactory @Inject constructor(
                                 key = DebugFeatureKeys.forceUsageOfOpusEncoder,
                                 factory = VectorFeatures::forceUsageOfOpusEncoder
                         ),
-                        createBooleanFeature(
-                                label = "Start DM on first message",
-                                key = DebugFeatureKeys.startDmOnFirstMsg,
-                                factory = VectorFeatures::shouldStartDmOnFirstMessage
-                        ),
                         createBooleanFeature(
                                 label = "Enable New App Layout",
                                 key = DebugFeatureKeys.newAppLayoutEnabled,
diff --git a/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt b/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt
index bb4cae3201..c01c058fc6 100644
--- a/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt
+++ b/vector-app/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt
@@ -73,9 +73,6 @@ class DebugVectorFeatures(
     override fun forceUsageOfOpusEncoder(): Boolean = read(DebugFeatureKeys.forceUsageOfOpusEncoder)
             ?: vectorFeatures.forceUsageOfOpusEncoder()
 
-    override fun shouldStartDmOnFirstMessage(): Boolean = read(DebugFeatureKeys.startDmOnFirstMsg)
-            ?: vectorFeatures.shouldStartDmOnFirstMessage()
-
     override fun isNewAppLayoutFeatureEnabled(): Boolean = read(DebugFeatureKeys.newAppLayoutEnabled)
             ?: vectorFeatures.isNewAppLayoutFeatureEnabled()
 
diff --git a/vector-config/src/main/res/values/config-settings.xml b/vector-config/src/main/res/values/config-settings.xml
index 8953138e5e..3342b1da14 100755
--- a/vector-config/src/main/res/values/config-settings.xml
+++ b/vector-config/src/main/res/values/config-settings.xml
@@ -37,6 +37,7 @@
     <bool name="settings_ignored_users_visible">true</bool>
 
     <!-- Level 1: Labs -->
+    <bool name="settings_labs_deferred_dm_default">true</bool>
     <bool name="settings_labs_thread_messages_default">false</bool>
     <bool name="settings_labs_new_app_layout_default">true</bool>
     <bool name="settings_timeline_show_live_sender_info_visible">true</bool>
diff --git a/vector/src/main/java/im/vector/app/features/VectorFeatures.kt b/vector/src/main/java/im/vector/app/features/VectorFeatures.kt
index dbdb0ba1c7..e1c083db29 100644
--- a/vector/src/main/java/im/vector/app/features/VectorFeatures.kt
+++ b/vector/src/main/java/im/vector/app/features/VectorFeatures.kt
@@ -33,7 +33,6 @@ interface VectorFeatures {
     fun isScreenSharingEnabled(): Boolean
     fun isLocationSharingEnabled(): Boolean
     fun forceUsageOfOpusEncoder(): Boolean
-    fun shouldStartDmOnFirstMessage(): Boolean
 
     /**
      * This is only to enable if the labs flag should be visible and effective.
@@ -56,7 +55,6 @@ class DefaultVectorFeatures : VectorFeatures {
     override fun isScreenSharingEnabled(): Boolean = true
     override fun isLocationSharingEnabled() = Config.ENABLE_LOCATION_SHARING
     override fun forceUsageOfOpusEncoder(): Boolean = false
-    override fun shouldStartDmOnFirstMessage(): Boolean = false
     override fun isNewAppLayoutFeatureEnabled(): Boolean = true
     override fun isNewDeviceManagementEnabled(): Boolean = false
 }
diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt
index 61ebc82767..3f67708a28 100644
--- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt
@@ -26,11 +26,11 @@ import im.vector.app.core.di.MavericksAssistedViewModelFactory
 import im.vector.app.core.di.hiltMavericksViewModelFactory
 import im.vector.app.core.mvrx.runCatchingToAsync
 import im.vector.app.core.platform.VectorViewModel
-import im.vector.app.features.VectorFeatures
 import im.vector.app.features.analytics.AnalyticsTracker
 import im.vector.app.features.analytics.plan.CreatedRoom
 import im.vector.app.features.raw.wellknown.getElementWellknown
 import im.vector.app.features.raw.wellknown.isE2EByDefault
+import im.vector.app.features.settings.VectorPreferences
 import im.vector.app.features.userdirectory.PendingSelection
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
@@ -45,9 +45,9 @@ import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
 class CreateDirectRoomViewModel @AssistedInject constructor(
         @Assisted initialState: CreateDirectRoomViewState,
         private val rawService: RawService,
+        private val vectorPreferences: VectorPreferences,
         val session: Session,
         val analyticsTracker: AnalyticsTracker,
-        val vectorFeatures: VectorFeatures
 ) :
         VectorViewModel<CreateDirectRoomViewState, CreateDirectRoomAction, CreateDirectRoomViewEvents>(initialState) {
 
@@ -124,7 +124,7 @@ class CreateDirectRoomViewModel @AssistedInject constructor(
                     }
 
             val result = runCatchingToAsync {
-                if (vectorFeatures.shouldStartDmOnFirstMessage()) {
+                if (vectorPreferences.isDeferredDmEnabled()) {
                     session.roomService().createLocalRoom(roomParams)
                 } else {
                     analyticsTracker.capture(CreatedRoom(isDM = roomParams.isDirect.orFalse()))
diff --git a/vector/src/main/java/im/vector/app/features/createdirect/DirectRoomHelper.kt b/vector/src/main/java/im/vector/app/features/createdirect/DirectRoomHelper.kt
index c2cc13920f..466aca1176 100644
--- a/vector/src/main/java/im/vector/app/features/createdirect/DirectRoomHelper.kt
+++ b/vector/src/main/java/im/vector/app/features/createdirect/DirectRoomHelper.kt
@@ -16,11 +16,11 @@
 
 package im.vector.app.features.createdirect
 
-import im.vector.app.features.VectorFeatures
 import im.vector.app.features.analytics.AnalyticsTracker
 import im.vector.app.features.analytics.plan.CreatedRoom
 import im.vector.app.features.raw.wellknown.getElementWellknown
 import im.vector.app.features.raw.wellknown.isE2EByDefault
+import im.vector.app.features.settings.VectorPreferences
 import org.matrix.android.sdk.api.extensions.orFalse
 import org.matrix.android.sdk.api.extensions.tryOrNull
 import org.matrix.android.sdk.api.raw.RawService
@@ -32,7 +32,7 @@ class DirectRoomHelper @Inject constructor(
         private val rawService: RawService,
         private val session: Session,
         private val analyticsTracker: AnalyticsTracker,
-        private val vectorFeatures: VectorFeatures,
+        private val vectorPreferences: VectorPreferences,
 ) {
 
     suspend fun ensureDMExists(userId: String): String {
@@ -50,7 +50,7 @@ class DirectRoomHelper @Inject constructor(
                 setDirectMessage()
                 enableEncryptionIfInvitedUsersSupportIt = adminE2EByDefault
             }
-            roomId = if (vectorFeatures.shouldStartDmOnFirstMessage()) {
+            roomId = if (vectorPreferences.isDeferredDmEnabled()) {
                 session.roomService().createLocalRoom(roomParams)
             } else {
                 analyticsTracker.capture(CreatedRoom(isDM = roomParams.isDirect.orFalse()))
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 fca931eaef..4da6455f74 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
@@ -66,6 +66,7 @@ class VectorPreferences @Inject constructor(
         const val SETTINGS_BACKGROUND_SYNC_DIVIDER_PREFERENCE_KEY = "SETTINGS_BACKGROUND_SYNC_DIVIDER_PREFERENCE_KEY"
         const val SETTINGS_LABS_PREFERENCE_KEY = "SETTINGS_LABS_PREFERENCE_KEY"
         const val SETTINGS_LABS_NEW_APP_LAYOUT_KEY = "SETTINGS_LABS_NEW_APP_LAYOUT_KEY"
+        const val SETTINGS_LABS_DEFERRED_DM_KEY = "SETTINGS_LABS_DEFERRED_DM_KEY"
         const val SETTINGS_CRYPTOGRAPHY_PREFERENCE_KEY = "SETTINGS_CRYPTOGRAPHY_PREFERENCE_KEY"
         const val SETTINGS_CRYPTOGRAPHY_DIVIDER_PREFERENCE_KEY = "SETTINGS_CRYPTOGRAPHY_DIVIDER_PREFERENCE_KEY"
         const val SETTINGS_CRYPTOGRAPHY_MANAGE_PREFERENCE_KEY = "SETTINGS_CRYPTOGRAPHY_MANAGE_PREFERENCE_KEY"
@@ -1162,6 +1163,13 @@ class VectorPreferences @Inject constructor(
                 defaultPrefs.getBoolean(SETTINGS_LABS_NEW_APP_LAYOUT_KEY, getDefault(R.bool.settings_labs_new_app_layout_default))
     }
 
+    /**
+     * Indicates whether or not deferred DMs are enabled.
+     */
+    fun isDeferredDmEnabled(): Boolean {
+        return defaultPrefs.getBoolean(SETTINGS_LABS_DEFERRED_DM_KEY, getDefault(R.bool.settings_labs_deferred_dm_default))
+    }
+
     fun showLiveSenderInfo(): Boolean {
         return defaultPrefs.getBoolean(SETTINGS_TIMELINE_SHOW_LIVE_SENDER_INFO, getDefault(R.bool.settings_timeline_show_live_sender_info_default))
     }
diff --git a/vector/src/main/res/xml/vector_settings_labs.xml b/vector/src/main/res/xml/vector_settings_labs.xml
index f61d5fe7bc..8baeaad3c6 100644
--- a/vector/src/main/res/xml/vector_settings_labs.xml
+++ b/vector/src/main/res/xml/vector_settings_labs.xml
@@ -89,4 +89,10 @@
         android:summary="@string/labs_enable_new_app_layout_summary"
         android:title="@string/labs_enable_new_app_layout_title" />
 
+    <im.vector.app.core.preference.VectorSwitchPreference
+        android:defaultValue="@bool/settings_labs_deferred_dm_default"
+        android:key="SETTINGS_LABS_DEFERRED_DM_KEY"
+        android:summary="@string/labs_enable_deferred_dm_summary"
+        android:title="@string/labs_enable_deferred_dm_title" />
+
 </androidx.preference.PreferenceScreen>

From 3786bd9c65f6dc473393e556fc55a4f68ad2a863 Mon Sep 17 00:00:00 2001
From: Florian Renaud <florianr@element.io>
Date: Tue, 20 Sep 2022 10:02:33 +0200
Subject: [PATCH 102/108] changelog

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

diff --git a/changelog.d/7180.feature b/changelog.d/7180.feature
new file mode 100644
index 0000000000..bdfe090ceb
--- /dev/null
+++ b/changelog.d/7180.feature
@@ -0,0 +1 @@
+Deferred DMs - Enable and move the feature to labs settings

From dd92bb756a3c331fe6d0af12ed8e66fb00a9b9fc Mon Sep 17 00:00:00 2001
From: Florian Renaud <florianr@element.io>
Date: Wed, 21 Sep 2022 09:27:37 +0200
Subject: [PATCH 103/108] Add visibility setting field for lab setting

---
 vector-config/src/main/res/values/config-settings.xml | 1 +
 vector/src/main/res/xml/vector_settings_labs.xml      | 5 +++--
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/vector-config/src/main/res/values/config-settings.xml b/vector-config/src/main/res/values/config-settings.xml
index 3342b1da14..c69452e3d0 100755
--- a/vector-config/src/main/res/values/config-settings.xml
+++ b/vector-config/src/main/res/values/config-settings.xml
@@ -37,6 +37,7 @@
     <bool name="settings_ignored_users_visible">true</bool>
 
     <!-- Level 1: Labs -->
+    <bool name="settings_labs_deferred_dm_visible">true</bool>
     <bool name="settings_labs_deferred_dm_default">true</bool>
     <bool name="settings_labs_thread_messages_default">false</bool>
     <bool name="settings_labs_new_app_layout_default">true</bool>
diff --git a/vector/src/main/res/xml/vector_settings_labs.xml b/vector/src/main/res/xml/vector_settings_labs.xml
index 8baeaad3c6..9fac6d722a 100644
--- a/vector/src/main/res/xml/vector_settings_labs.xml
+++ b/vector/src/main/res/xml/vector_settings_labs.xml
@@ -47,8 +47,8 @@
 
     <im.vector.app.core.preference.VectorSwitchPreference
         android:defaultValue="false"
-        android:persistent="false"
         android:key="SETTINGS_LABS_MSC3061_SHARE_KEYS_HISTORY"
+        android:persistent="false"
         android:summary="@string/labs_enable_msc3061_share_history_desc"
         android:title="@string/labs_enable_msc3061_share_history" />
 
@@ -93,6 +93,7 @@
         android:defaultValue="@bool/settings_labs_deferred_dm_default"
         android:key="SETTINGS_LABS_DEFERRED_DM_KEY"
         android:summary="@string/labs_enable_deferred_dm_summary"
-        android:title="@string/labs_enable_deferred_dm_title" />
+        android:title="@string/labs_enable_deferred_dm_title"
+        app:isPreferenceVisible="@bool/settings_labs_deferred_dm_visible" />
 
 </androidx.preference.PreferenceScreen>

From fa8b56b1ad0790f73a09661203a2f78b21a8d72d Mon Sep 17 00:00:00 2001
From: Florian Renaud <florianr@element.io>
Date: Wed, 21 Sep 2022 09:35:26 +0200
Subject: [PATCH 104/108] Restore tracking for deferred DMs

---
 .../app/features/home/room/detail/TimelineViewModel.kt | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

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 a6513ffc4f..02dd2604e1 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
@@ -39,6 +39,7 @@ import im.vector.app.core.utils.BehaviorDataSource
 import im.vector.app.features.analytics.AnalyticsTracker
 import im.vector.app.features.analytics.DecryptionFailureTracker
 import im.vector.app.features.analytics.extensions.toAnalyticsJoinedRoom
+import im.vector.app.features.analytics.plan.CreatedRoom
 import im.vector.app.features.analytics.plan.JoinedRoom
 import im.vector.app.features.call.conference.ConferenceEvent
 import im.vector.app.features.call.conference.JitsiActiveConferenceHolder
@@ -78,6 +79,7 @@ import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 import org.matrix.android.sdk.api.MatrixPatterns
+import org.matrix.android.sdk.api.extensions.orFalse
 import org.matrix.android.sdk.api.extensions.tryOrNull
 import org.matrix.android.sdk.api.query.QueryStringValue
 import org.matrix.android.sdk.api.raw.RawService
@@ -1247,8 +1249,12 @@ class TimelineViewModel @AssistedInject constructor(
                             LocalRoomCreationState.FAILURE -> {
                                 _viewEvents.post(RoomDetailViewEvents.HideWaitingView)
                             }
-                            LocalRoomCreationState.CREATED ->
-                                _viewEvents.post(RoomDetailViewEvents.OpenRoom(room.localRoomSummary()?.replacementRoomId!!, true))
+                            LocalRoomCreationState.CREATED -> {
+                                room.localRoomSummary()?.let {
+                                    analyticsTracker.capture(CreatedRoom(isDM = it.roomSummary?.isDirect.orFalse()))
+                                    _viewEvents.post(RoomDetailViewEvents.OpenRoom(it.replacementRoomId!!, true))
+                                }
+                            }
                         }
                     }
                     .launchIn(viewModelScope)

From c252f6eb70f89559e057b61b8a79a5bb86640a6f Mon Sep 17 00:00:00 2001
From: Florian Renaud <florianr@element.io>
Date: Wed, 21 Sep 2022 09:50:05 +0200
Subject: [PATCH 105/108] Update lab setting wording following design review

---
 library/ui-strings/src/main/res/values/strings.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml
index 99ff55a93d..0364cc4565 100644
--- a/library/ui-strings/src/main/res/values/strings.xml
+++ b/library/ui-strings/src/main/res/values/strings.xml
@@ -443,7 +443,7 @@
     <string name="labs_enable_new_app_layout_summary">A simplified Element with optional tabs</string>
 
     <string name="labs_enable_deferred_dm_title">Enable deferred DMs</string>
-    <string name="labs_enable_deferred_dm_summary">Direct rooms will be created after sending a first message.</string>
+    <string name="labs_enable_deferred_dm_summary">Create DM only on first message</string>
 
     <!-- Home fragment -->
     <string name="invitations_header">Invites</string>

From 602b378b65652334641fe81182cd577143275a15 Mon Sep 17 00:00:00 2001
From: NIkita Fedrunov <fedrunov@element.io>
Date: Wed, 21 Sep 2022 10:43:08 +0200
Subject: [PATCH 106/108] cancel flow when order is changed

---
 .../home/room/list/home/HomeRoomListViewModel.kt         | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt
index 4bc5d8ba95..18ab57dce9 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt
@@ -36,6 +36,8 @@ import im.vector.app.core.resources.StringProvider
 import im.vector.app.features.displayname.getBestName
 import im.vector.app.features.home.room.list.home.header.HomeRoomFilter
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.cancel
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.asSharedFlow
@@ -70,6 +72,7 @@ import org.matrix.android.sdk.api.session.room.state.isPublic
 import org.matrix.android.sdk.api.util.Optional
 import org.matrix.android.sdk.api.util.toMatrixItem
 import org.matrix.android.sdk.flow.flow
+import java.util.concurrent.CancellationException
 
 class HomeRoomListViewModel @AssistedInject constructor(
         @Assisted initialState: HomeRoomListViewState,
@@ -99,6 +102,8 @@ class HomeRoomListViewModel @AssistedInject constructor(
     private val _emptyStateFlow = MutableSharedFlow<Optional<StateView.State.Empty>>(replay = 1)
     val emptyStateFlow = _emptyStateFlow.asSharedFlow()
 
+    private var roomsFlowJob: Job? = null
+
     private var filteredPagedRoomSummariesLive: UpdatableLivePageResult? = null
 
     init {
@@ -256,7 +261,9 @@ class HomeRoomListViewModel @AssistedInject constructor(
                 .also { roomsFlow = it }
                 .launchIn(viewModelScope)
 
-        liveResults.livePagedList
+        roomsFlowJob?.cancel(CancellationException())
+
+        roomsFlowJob = liveResults.livePagedList
                 .asFlow()
                 .onEach {
                     setState { copy(roomsPagedList = it) }

From d8060a7922ca65a94a56f631009713c57d418f38 Mon Sep 17 00:00:00 2001
From: NIkita Fedrunov <fedrunov@element.io>
Date: Wed, 21 Sep 2022 11:28:21 +0200
Subject: [PATCH 107/108] review fixes

---
 .../home/room/list/home/HomeFilteredRoomsController.kt    | 8 ++++++++
 .../features/home/room/list/home/HomeRoomListFragment.kt  | 2 +-
 .../features/home/room/list/home/HomeRoomListViewModel.kt | 6 ------
 3 files changed, 9 insertions(+), 7 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeFilteredRoomsController.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeFilteredRoomsController.kt
index ebf322dc23..2b4a514750 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeFilteredRoomsController.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeFilteredRoomsController.kt
@@ -16,6 +16,7 @@
 
 package im.vector.app.features.home.room.list.home
 
+import androidx.paging.PagedList
 import com.airbnb.epoxy.EpoxyModel
 import com.airbnb.epoxy.paging.PagedListEpoxyController
 import im.vector.app.core.platform.StateView
@@ -75,6 +76,13 @@ class HomeFilteredRoomsController @Inject constructor(
         this.emptyStateData = state
     }
 
+    fun submitPagedList(newList: PagedList<RoomSummary>) {
+        submitList(newList)
+        if (newList.isEmpty()) {
+            requestForcedModelBuild()
+        }
+    }
+
     override fun buildItemModel(currentPosition: Int, item: RoomSummary?): EpoxyModel<*> {
         return if (item == null) {
             val host = this
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt
index 4d61057b59..9b8a686f37 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt
@@ -154,7 +154,7 @@ class HomeRoomListFragment :
 
         roomListViewModel.onEach(HomeRoomListViewState::roomsPagedList) { roomsList ->
             roomsList?.let {
-                roomsController.submitList(it)
+                roomsController.submitPagedList(it)
                 if (it.isEmpty()) {
                     roomsController.requestForcedModelBuild()
                 }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt
index 18ab57dce9..ad2656cec1 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt
@@ -19,7 +19,6 @@ package im.vector.app.features.home.room.list.home
 import android.widget.ImageView
 import androidx.lifecycle.asFlow
 import androidx.paging.PagedList
-import arrow.core.Option
 import arrow.core.toOption
 import com.airbnb.mvrx.MavericksViewModelFactory
 import dagger.assisted.Assisted
@@ -37,7 +36,6 @@ import im.vector.app.features.displayname.getBestName
 import im.vector.app.features.home.room.list.home.header.HomeRoomFilter
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.Job
-import kotlinx.coroutines.cancel
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.asSharedFlow
@@ -46,7 +44,6 @@ import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flow
-import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
@@ -90,7 +87,6 @@ class HomeRoomListViewModel @AssistedInject constructor(
 
     companion object : MavericksViewModelFactory<HomeRoomListViewModel, HomeRoomListViewState> by hiltMavericksViewModelFactory()
 
-    private var roomsFlow: Flow<Option<RoomSummary>>? = null
     private val pagedListConfig = PagedList.Config.Builder()
             .setPageSize(10)
             .setInitialLoadSizeHint(20)
@@ -258,7 +254,6 @@ class HomeRoomListViewModel @AssistedInject constructor(
                     )
                     emitEmptyState()
                 }
-                .also { roomsFlow = it }
                 .launchIn(viewModelScope)
 
         roomsFlowJob?.cancel(CancellationException())
@@ -268,7 +263,6 @@ class HomeRoomListViewModel @AssistedInject constructor(
                 .onEach {
                     setState { copy(roomsPagedList = it) }
                 }
-                .flowOn(Dispatchers.Default)
                 .launchIn(viewModelScope)
     }
 

From 793138bf1bf73782eea70ecafe88ffa4e868dfa6 Mon Sep 17 00:00:00 2001
From: Onuray Sahin <onurays@element.io>
Date: Wed, 21 Sep 2022 16:44:47 +0300
Subject: [PATCH 108/108] Revert changes of string keys.

---
 library/ui-strings/src/main/res/values-ar/strings.xml    | 2 +-
 library/ui-strings/src/main/res/values-bg/strings.xml    | 2 +-
 .../ui-strings/src/main/res/values-bn-rBD/strings.xml    | 2 +-
 .../ui-strings/src/main/res/values-bn-rIN/strings.xml    | 2 +-
 library/ui-strings/src/main/res/values-ca/strings.xml    | 6 +++---
 library/ui-strings/src/main/res/values-cs/strings.xml    | 6 +++---
 library/ui-strings/src/main/res/values-de/strings.xml    | 6 +++---
 library/ui-strings/src/main/res/values-el/strings.xml    | 2 +-
 library/ui-strings/src/main/res/values-eo/strings.xml    | 2 +-
 .../ui-strings/src/main/res/values-es-rMX/strings.xml    | 2 +-
 library/ui-strings/src/main/res/values-es/strings.xml    | 2 +-
 library/ui-strings/src/main/res/values-et/strings.xml    | 6 +++---
 library/ui-strings/src/main/res/values-eu/strings.xml    | 2 +-
 library/ui-strings/src/main/res/values-fa/strings.xml    | 6 +++---
 library/ui-strings/src/main/res/values-fi/strings.xml    | 2 +-
 .../ui-strings/src/main/res/values-fr-rCA/strings.xml    | 2 +-
 library/ui-strings/src/main/res/values-fr/strings.xml    | 6 +++---
 library/ui-strings/src/main/res/values-gl/strings.xml    | 2 +-
 library/ui-strings/src/main/res/values-hr/strings.xml    | 2 +-
 library/ui-strings/src/main/res/values-hu/strings.xml    | 6 +++---
 library/ui-strings/src/main/res/values-in/strings.xml    | 6 +++---
 library/ui-strings/src/main/res/values-is/strings.xml    | 2 +-
 library/ui-strings/src/main/res/values-it/strings.xml    | 6 +++---
 library/ui-strings/src/main/res/values-iw/strings.xml    | 2 +-
 library/ui-strings/src/main/res/values-ja/strings.xml    | 2 +-
 library/ui-strings/src/main/res/values-kab/strings.xml   | 2 +-
 library/ui-strings/src/main/res/values-ko/strings.xml    | 2 +-
 library/ui-strings/src/main/res/values-lo/strings.xml    | 2 +-
 library/ui-strings/src/main/res/values-lv/strings.xml    | 2 +-
 .../ui-strings/src/main/res/values-nb-rNO/strings.xml    | 2 +-
 library/ui-strings/src/main/res/values-nl/strings.xml    | 6 +++---
 library/ui-strings/src/main/res/values-nn/strings.xml    | 2 +-
 library/ui-strings/src/main/res/values-pl/strings.xml    | 6 +++---
 .../ui-strings/src/main/res/values-pt-rBR/strings.xml    | 6 +++---
 library/ui-strings/src/main/res/values-pt/strings.xml    | 2 +-
 library/ui-strings/src/main/res/values-ru/strings.xml    | 6 +++---
 library/ui-strings/src/main/res/values-sk/strings.xml    | 6 +++---
 library/ui-strings/src/main/res/values-sq/strings.xml    | 2 +-
 library/ui-strings/src/main/res/values-sv/strings.xml    | 2 +-
 library/ui-strings/src/main/res/values-te/strings.xml    | 2 +-
 library/ui-strings/src/main/res/values-tr/strings.xml    | 2 +-
 library/ui-strings/src/main/res/values-uk/strings.xml    | 6 +++---
 library/ui-strings/src/main/res/values-vi/strings.xml    | 2 +-
 .../ui-strings/src/main/res/values-zh-rCN/strings.xml    | 6 +++---
 .../ui-strings/src/main/res/values-zh-rTW/strings.xml    | 6 +++---
 library/ui-strings/src/main/res/values/strings.xml       | 9 ++++++---
 .../devices/v2/details/SessionDetailsController.kt       | 2 +-
 vector/src/main/res/layout/dialog_device_verify.xml      | 2 +-
 vector/src/main/res/layout/fragment_other_sessions.xml   | 4 ++--
 vector/src/main/res/layout/fragment_settings_devices.xml | 4 ++--
 .../main/res/xml/vector_settings_security_privacy.xml    | 2 +-
 51 files changed, 92 insertions(+), 89 deletions(-)

diff --git a/library/ui-strings/src/main/res/values-ar/strings.xml b/library/ui-strings/src/main/res/values-ar/strings.xml
index 70b9a33ab5..073f961cb6 100644
--- a/library/ui-strings/src/main/res/values-ar/strings.xml
+++ b/library/ui-strings/src/main/res/values-ar/strings.xml
@@ -320,7 +320,7 @@
     <string name="settings_theme">السمة</string>
     <string name="encryption_information_decryption_error">خطأ في فكّ التعمية</string>
     <string name="encryption_information_device_name">اسم الجهاز</string>
-    <string name="device_manager_session_details_session_id">معرّف الجهاز</string>
+    <string name="encryption_information_device_id">معرّف الجهاز</string>
     <string name="encryption_information_device_key">مفتاح الجهاز</string>
     <string name="encryption_export_room_keys">صدّر مفاتيح الغرفة</string>
     <string name="encryption_export_room_keys_summary">صدّر المفاتيح إلى ملف محلي</string>
diff --git a/library/ui-strings/src/main/res/values-bg/strings.xml b/library/ui-strings/src/main/res/values-bg/strings.xml
index d3e9e599bc..b29823040f 100644
--- a/library/ui-strings/src/main/res/values-bg/strings.xml
+++ b/library/ui-strings/src/main/res/values-bg/strings.xml
@@ -396,7 +396,7 @@
     <string name="settings_theme">Тема</string>
     <string name="encryption_information_decryption_error">Грешка при разшифроване</string>
     <string name="encryption_information_device_name">Публично име</string>
-    <string name="device_manager_session_details_session_id">Сесийно ID</string>
+    <string name="encryption_information_device_id">Сесийно ID</string>
     <string name="encryption_information_device_key">Ключ на устройство</string>
     <string name="encryption_export_e2e_room_keys">Експортирай E2E ключове за стая</string>
     <string name="encryption_export_room_keys">Експортиране на ключове за стая</string>
diff --git a/library/ui-strings/src/main/res/values-bn-rBD/strings.xml b/library/ui-strings/src/main/res/values-bn-rBD/strings.xml
index 7897da934e..2f068f1bf8 100644
--- a/library/ui-strings/src/main/res/values-bn-rBD/strings.xml
+++ b/library/ui-strings/src/main/res/values-bn-rBD/strings.xml
@@ -789,7 +789,7 @@
     <string name="encryption_export_room_keys">রুমের কুঞ্জিগুলি এক্সপোর্ট করুন</string>
     <string name="encryption_export_e2e_room_keys">শেষ থেকে শেষ রুমের কুঞ্জিগুলি এক্সপোর্ট করুন</string>
     <string name="encryption_information_device_key">সেশানের কুঞ্জি</string>
-    <string name="device_manager_session_details_session_id">আইডি</string>
+    <string name="encryption_information_device_id">আইডি</string>
     <string name="encryption_information_device_name">সর্বজনীন নাম</string>
     <string name="encryption_information_decryption_error">ডিক্রিপশন সমস্যা</string>
     <string name="settings_theme">থিম</string>
diff --git a/library/ui-strings/src/main/res/values-bn-rIN/strings.xml b/library/ui-strings/src/main/res/values-bn-rIN/strings.xml
index 56bde36977..828bc3bd34 100644
--- a/library/ui-strings/src/main/res/values-bn-rIN/strings.xml
+++ b/library/ui-strings/src/main/res/values-bn-rIN/strings.xml
@@ -693,7 +693,7 @@
     <string name="encryption_information_decryption_error">ডিক্রিপশন সমস্যা</string>
 
     <string name="encryption_information_device_name">সর্বজনীন নাম</string>
-    <string name="device_manager_session_details_session_id">আইডি</string>
+    <string name="encryption_information_device_id">আইডি</string>
     <string name="encryption_information_device_key">সেশানের কুঞ্জি</string>
 
     <string name="encryption_export_e2e_room_keys">শেষ থেকে শেষ রুমের কুঞ্জিগুলি এক্সপোর্ট করুন</string>
diff --git a/library/ui-strings/src/main/res/values-ca/strings.xml b/library/ui-strings/src/main/res/values-ca/strings.xml
index 3abd45e2d3..13a5b6c119 100644
--- a/library/ui-strings/src/main/res/values-ca/strings.xml
+++ b/library/ui-strings/src/main/res/values-ca/strings.xml
@@ -448,7 +448,7 @@
     <string name="settings_theme">Tema</string>
     <string name="encryption_information_decryption_error">Error al desxifrar</string>
     <string name="encryption_information_device_name">Nom públic</string>
-    <string name="device_manager_session_details_session_id">ID de sessió</string>
+    <string name="encryption_information_device_id">ID de sessió</string>
     <string name="encryption_information_device_key">Clau de sessió</string>
     <string name="encryption_export_e2e_room_keys">Exporta les claus de la sala E2E</string>
     <string name="encryption_export_room_keys">Exporta les claus de la sala</string>
@@ -2602,8 +2602,8 @@
     <string name="all_chats">Tots els xats</string>
     <string name="home_layout_preferences">Preferències de disseny</string>
     <string name="explore_rooms">Explora sales</string>
-    <string name="device_manager_sessions_other_description">Per estar més segur, verifica les teves sessions i tanca qualsevol sessió que no reconeguis o ja no utilitzis.</string>
-    <string name="device_manager_sessions_other_title">Altres sessions</string>
+    <string name="settings_sessions_other_description">Per estar més segur, verifica les teves sessions i tanca qualsevol sessió que no reconeguis o ja no utilitzis.</string>
+    <string name="settings_sessions_other_title">Altres sessions</string>
     <string name="settings_sessions_list">Sessions</string>
     <string name="a11y_open_spaces">Obre la llista d\'espais</string>
     <string name="a11y_create_message">Crea un nou xat o sala</string>
diff --git a/library/ui-strings/src/main/res/values-cs/strings.xml b/library/ui-strings/src/main/res/values-cs/strings.xml
index 8316eab2c3..b7bfeac444 100644
--- a/library/ui-strings/src/main/res/values-cs/strings.xml
+++ b/library/ui-strings/src/main/res/values-cs/strings.xml
@@ -635,7 +635,7 @@
     <string name="settings_theme">Motiv vzhledu</string>
     <string name="encryption_information_decryption_error">Chyba dešifrování</string>
     <string name="encryption_information_device_name">Veřejné jméno</string>
-    <string name="device_manager_session_details_session_id">ID relace</string>
+    <string name="encryption_information_device_id">ID relace</string>
     <string name="encryption_information_device_key">Klíč relace</string>
     <string name="encryption_export_e2e_room_keys">Export E2E klíčů místností</string>
     <string name="encryption_export_room_keys">Export klíčů místností</string>
@@ -2651,8 +2651,8 @@
     <string name="a11y_open_settings">Otevřít nastavení</string>
     <string name="all_chats">Všechny konverzace</string>
     <string name="device_manager_settings_active_sessions_show_all">Zobrazit všechny relace (V2, WIP)</string>
-    <string name="device_manager_sessions_other_description">V zájmu co nejlepšího zabezpečení ověřujte své relace a odhlašujte se ze všech relací, které již nepoznáváte nebo nepoužíváte.</string>
-    <string name="device_manager_sessions_other_title">Ostatní relace</string>
+    <string name="settings_sessions_other_description">V zájmu co nejlepšího zabezpečení ověřujte své relace a odhlašujte se ze všech relací, které již nepoznáváte nebo nepoužíváte.</string>
+    <string name="settings_sessions_other_title">Ostatní relace</string>
     <string name="settings_sessions_list">Relace</string>
     <string name="a11y_open_spaces">Seznam otevřených prostorů</string>
     <string name="a11y_create_message">Vytvořit novou konverzaci nebo místnost</string>
diff --git a/library/ui-strings/src/main/res/values-de/strings.xml b/library/ui-strings/src/main/res/values-de/strings.xml
index 827311bde6..3753cedff2 100644
--- a/library/ui-strings/src/main/res/values-de/strings.xml
+++ b/library/ui-strings/src/main/res/values-de/strings.xml
@@ -418,7 +418,7 @@
     <string name="room_settings_unset_main_address">Als Hauptadresse aufheben</string>
     <string name="encryption_information_decryption_error">Entschlüsselungsfehler</string>
     <string name="encryption_information_device_name">Öffentlicher Name</string>
-    <string name="device_manager_session_details_session_id">Sitzungs-ID</string>
+    <string name="encryption_information_device_id">Sitzungs-ID</string>
     <string name="encryption_information_device_key">Sitzungsschlüssel</string>
     <string name="encryption_export_e2e_room_keys">Ende-zu-Ende-Raumschlüssel exportieren</string>
     <string name="encryption_export_room_keys">Raumschlüssel exportieren</string>
@@ -2587,8 +2587,8 @@
     <string name="room_list_filter_people">Personen</string>
     <string name="send_your_first_msg_to_invite">Schreibe deine erste Nachricht, um %s zur Konversation einzuladen</string>
     <string name="device_manager_settings_active_sessions_show_all">Alle Sitzungen anzeigen (V2, in Arbeit)</string>
-    <string name="device_manager_sessions_other_description">Für bestmögliche Sicherheit verifiziere deine Sitzungen und melde dich von allen ab, die du nicht erkennst oder nutzt.</string>
-    <string name="device_manager_sessions_other_title">Andere Sitzungen</string>
+    <string name="settings_sessions_other_description">Für bestmögliche Sicherheit verifiziere deine Sitzungen und melde dich von allen ab, die du nicht erkennst oder nutzt.</string>
+    <string name="settings_sessions_other_title">Andere Sitzungen</string>
     <string name="settings_sessions_list">Sitzungen</string>
     <string name="a11y_open_spaces">Space-Liste öffnen</string>
     <string name="a11y_create_message">Beginne ein Gespräch oder erstelle einen Raum</string>
diff --git a/library/ui-strings/src/main/res/values-el/strings.xml b/library/ui-strings/src/main/res/values-el/strings.xml
index f4973f4b95..092a01bff4 100644
--- a/library/ui-strings/src/main/res/values-el/strings.xml
+++ b/library/ui-strings/src/main/res/values-el/strings.xml
@@ -172,7 +172,7 @@
     <string name="settings_theme">Θέμα</string>
     <string name="encryption_information_decryption_error">Σφάλμα αποκρυπτογράφησης</string>
     <string name="encryption_information_device_name">Όνομα συσκευής</string>
-    <string name="device_manager_session_details_session_id">Αναγνωριστικό συσκευής</string>
+    <string name="encryption_information_device_id">Αναγνωριστικό συσκευής</string>
     <string name="encryption_export_export">Εξαγωγή</string>
     <string name="encryption_import_import">Εισαγωγή</string>
     <string name="select_room_directory">Επιλέξτε ένα ευρετήριο δωματίων</string>
diff --git a/library/ui-strings/src/main/res/values-eo/strings.xml b/library/ui-strings/src/main/res/values-eo/strings.xml
index f536ca00f9..7e1925f708 100644
--- a/library/ui-strings/src/main/res/values-eo/strings.xml
+++ b/library/ui-strings/src/main/res/values-eo/strings.xml
@@ -1084,7 +1084,7 @@
     <string name="encryption_export_room_keys">Elporti ŝlosilojn de ĉambroj</string>
     <string name="encryption_export_e2e_room_keys">Elporti tutvoje ĉifrajn ŝlosilojn de ĉambroj</string>
     <string name="encryption_information_device_key">Ŝlosilo de salutaĵo</string>
-    <string name="device_manager_session_details_session_id">Identigilo de salutaĵo</string>
+    <string name="encryption_information_device_id">Identigilo de salutaĵo</string>
     <string name="encryption_information_device_name">Publika nomo</string>
     <string name="encryption_information_decryption_error">Eraris malĉifrado</string>
     <string name="settings_theme">Haŭto</string>
diff --git a/library/ui-strings/src/main/res/values-es-rMX/strings.xml b/library/ui-strings/src/main/res/values-es-rMX/strings.xml
index c82f9aff61..0b38fa6a19 100644
--- a/library/ui-strings/src/main/res/values-es-rMX/strings.xml
+++ b/library/ui-strings/src/main/res/values-es-rMX/strings.xml
@@ -249,7 +249,7 @@
     <string name="room_settings_unset_main_address">Desescojer como Dirección Principal</string>
     <string name="encryption_information_decryption_error">Error en descifrar</string>
     <string name="encryption_information_device_name">Nombre del dispositivo</string>
-    <string name="device_manager_session_details_session_id">Identificación del dispositivo</string>
+    <string name="encryption_information_device_id">Identificación del dispositivo</string>
     <string name="encryption_information_device_key">Clave del dispositivo</string>
     <string name="encryption_export_e2e_room_keys">Exportar claves de cifrado de extremo-a-extremo de salas</string>
     <string name="encryption_export_room_keys">Exportar claves de salas</string>
diff --git a/library/ui-strings/src/main/res/values-es/strings.xml b/library/ui-strings/src/main/res/values-es/strings.xml
index fcdd3f90a0..4eec90fbd6 100644
--- a/library/ui-strings/src/main/res/values-es/strings.xml
+++ b/library/ui-strings/src/main/res/values-es/strings.xml
@@ -415,7 +415,7 @@
     <string name="room_settings_unset_main_address">Dejar de Establecer como dirección principal</string>
     <string name="encryption_information_decryption_error">Error de descifrado</string>
     <string name="encryption_information_device_name">Nombre público</string>
-    <string name="device_manager_session_details_session_id">ID de sesión</string>
+    <string name="encryption_information_device_id">ID de sesión</string>
     <string name="encryption_information_device_key">Clave de sesión</string>
     <string name="encryption_export_e2e_room_keys">Exportar claves de salas con cifrado Extremo-a-Extremo</string>
     <string name="encryption_export_room_keys">Exportar claves de sala</string>
diff --git a/library/ui-strings/src/main/res/values-et/strings.xml b/library/ui-strings/src/main/res/values-et/strings.xml
index d9754b5dcb..55fb9dfef0 100644
--- a/library/ui-strings/src/main/res/values-et/strings.xml
+++ b/library/ui-strings/src/main/res/values-et/strings.xml
@@ -612,7 +612,7 @@
     <string name="room_settings_labs_warning_message">Need on alles katsejärgus olevad funktsionaalsused. Ole kasutamisel ettevaatlik.</string>
     <string name="encryption_information_decryption_error">Dekrüptimise viga</string>
     <string name="encryption_information_device_name">Avalik nimi</string>
-    <string name="device_manager_session_details_session_id">Sessiooni tunnus</string>
+    <string name="encryption_information_device_id">Sessiooni tunnus</string>
     <string name="encryption_information_device_key">Sessiooni võti</string>
     <string name="encryption_export_e2e_room_keys">Ekspordi jututubade läbiva krüptimise võtmed</string>
     <string name="encryption_export_room_keys">Ekspordi jututoa võtmed</string>
@@ -2592,8 +2592,8 @@
     <string name="a11y_open_settings">Ava seadistused</string>
     <string name="all_chats">Kõik vestlused</string>
     <string name="device_manager_settings_active_sessions_show_all">Näita kõiki sessioone (V2, WIP)</string>
-    <string name="device_manager_sessions_other_description">Parima turvalisuse nimel verifitseeri kõik oma sessioonid ning logi välja neist, mida sa enam ei kasuta.</string>
-    <string name="device_manager_sessions_other_title">Muud sessioonid</string>
+    <string name="settings_sessions_other_description">Parima turvalisuse nimel verifitseeri kõik oma sessioonid ning logi välja neist, mida sa enam ei kasuta.</string>
+    <string name="settings_sessions_other_title">Muud sessioonid</string>
     <string name="settings_sessions_list">Sessionid</string>
     <string name="a11y_open_spaces">Ava kogukondade loend</string>
     <string name="a11y_create_message">Alusta uut vestlust või loo uus jututuba</string>
diff --git a/library/ui-strings/src/main/res/values-eu/strings.xml b/library/ui-strings/src/main/res/values-eu/strings.xml
index f1f834ee04..7b27d1cc1d 100644
--- a/library/ui-strings/src/main/res/values-eu/strings.xml
+++ b/library/ui-strings/src/main/res/values-eu/strings.xml
@@ -406,7 +406,7 @@ Kontuan izan ekintza honek aplikazioa berrabiaraziko duela eta denbora bat behar
     <string name="encryption_information_decryption_error">Deszifratze errorea</string>
 
     <string name="encryption_information_device_name">Izen publikoa</string>
-    <string name="device_manager_session_details_session_id">IDa</string>
+    <string name="encryption_information_device_id">IDa</string>
     <string name="encryption_information_device_key">Saioaren gakoa</string>
 
     <string name="encryption_export_e2e_room_keys">Esportatu E2E geletako gakoak</string>
diff --git a/library/ui-strings/src/main/res/values-fa/strings.xml b/library/ui-strings/src/main/res/values-fa/strings.xml
index b7a818f58a..e104225389 100644
--- a/library/ui-strings/src/main/res/values-fa/strings.xml
+++ b/library/ui-strings/src/main/res/values-fa/strings.xml
@@ -678,7 +678,7 @@
     <string name="room_settings_labs_warning_message">این‌ها ویژگی‌های آزمایشی‌ای هستند که ممکن است به روش‌های نامنتظره‌ای حراب شوندا. با احتیاط استفاده کنید.</string>
     <string name="room_settings_set_main_address">تنظیم به عنوان نشانی اصلی</string>
     <string name="encryption_information_device_name">نام عمومی</string>
-    <string name="device_manager_session_details_session_id">شناسهٔ نشست</string>
+    <string name="encryption_information_device_id">شناسهٔ نشست</string>
     <string name="encryption_information_device_key">کلید نشست</string>
     <string name="encryption_export_e2e_room_keys">برون‌ریزی کلید‌های اتاق‌های سرتاسری</string>
     <string name="encryption_export_room_keys">برون‌ریزی کلید‌های اتاق‌ها</string>
@@ -2601,8 +2601,8 @@
     <string name="a11y_open_settings">گشودن تنظیمات</string>
     <string name="all_chats">تمامی گپ‌ها</string>
     <string name="device_manager_settings_active_sessions_show_all">نمایش تمامی نشست‌ها (ن۲، دح‌ت)</string>
-    <string name="device_manager_sessions_other_description">برای امنیت بیش‌تر، نشست‌هایتان را تأیید و از هر نشستی که تشخیصش نمی‌دهید یا دیگر استفاده نمی‌کنید خارج شوید.</string>
-    <string name="device_manager_sessions_other_title">دیگر نشست‌ها</string>
+    <string name="settings_sessions_other_description">برای امنیت بیش‌تر، نشست‌هایتان را تأیید و از هر نشستی که تشخیصش نمی‌دهید یا دیگر استفاده نمی‌کنید خارج شوید.</string>
+    <string name="settings_sessions_other_title">دیگر نشست‌ها</string>
     <string name="settings_sessions_list">نشست‌ها</string>
     <string name="a11y_open_spaces">گشودن سیاههٔ فضاها</string>
     <string name="a11y_create_message">ایجاد اتاق یا گفت‌وگویی جدید</string>
diff --git a/library/ui-strings/src/main/res/values-fi/strings.xml b/library/ui-strings/src/main/res/values-fi/strings.xml
index a576e7f0dc..fde2502ae0 100644
--- a/library/ui-strings/src/main/res/values-fi/strings.xml
+++ b/library/ui-strings/src/main/res/values-fi/strings.xml
@@ -366,7 +366,7 @@
     <string name="room_settings_unset_main_address">Kumoa pääosoitteeksi asettaminen</string>
     <string name="encryption_information_decryption_error">Salauksenpurkuvirhe</string>
     <string name="encryption_information_device_name">Julkinen nimi</string>
-    <string name="device_manager_session_details_session_id">Istunnon tunnus</string>
+    <string name="encryption_information_device_id">Istunnon tunnus</string>
     <string name="encryption_information_device_key">Istunnon avain</string>
     <string name="encryption_export_e2e_room_keys">Vie salatun huoneen avaimet</string>
     <string name="encryption_export_room_keys">Vie huoneen avaimet</string>
diff --git a/library/ui-strings/src/main/res/values-fr-rCA/strings.xml b/library/ui-strings/src/main/res/values-fr-rCA/strings.xml
index 94db2935a7..29a618f415 100644
--- a/library/ui-strings/src/main/res/values-fr-rCA/strings.xml
+++ b/library/ui-strings/src/main/res/values-fr-rCA/strings.xml
@@ -778,7 +778,7 @@
     <string name="encryption_export_room_keys">Exporter les clés des salons</string>
     <string name="encryption_export_e2e_room_keys">Exporter les clés E2E des salons</string>
     <string name="encryption_information_device_key">Clé de la session</string>
-    <string name="device_manager_session_details_session_id">Identifiant de session</string>
+    <string name="encryption_information_device_id">Identifiant de session</string>
     <string name="encryption_information_device_name">Nom public</string>
     <string name="encryption_information_decryption_error">Erreur de déchiffrement</string>
     <string name="settings_theme">Thème</string>
diff --git a/library/ui-strings/src/main/res/values-fr/strings.xml b/library/ui-strings/src/main/res/values-fr/strings.xml
index f560ddbb7b..55b5f88134 100644
--- a/library/ui-strings/src/main/res/values-fr/strings.xml
+++ b/library/ui-strings/src/main/res/values-fr/strings.xml
@@ -346,7 +346,7 @@
     <string name="room_settings_unset_main_address">Désactiver comme adresse principale</string>
     <string name="encryption_information_decryption_error">Erreur de déchiffrement</string>
     <string name="encryption_information_device_name">Nom public</string>
-    <string name="device_manager_session_details_session_id">Identifiant de session</string>
+    <string name="encryption_information_device_id">Identifiant de session</string>
     <string name="encryption_information_device_key">Clé de la session</string>
     <string name="encryption_export_e2e_room_keys">Exporter les clés E2E des salons</string>
     <string name="encryption_export_room_keys">Exporter les clés des salons</string>
@@ -2601,8 +2601,8 @@
     <string name="a11y_open_settings">Ouvrir les paramètres</string>
     <string name="all_chats">Toutes les conversations</string>
     <string name="device_manager_settings_active_sessions_show_all">Afficher toutes les sessions (V2, en cours)</string>
-    <string name="device_manager_sessions_other_description">Pour une meilleure sécurité, vérifiez vos sessions et déconnectez toutes les sessions que vous ne connaissez pas ou que vous n’utilisez plus.</string>
-    <string name="device_manager_sessions_other_title">Autres sessions</string>
+    <string name="settings_sessions_other_description">Pour une meilleure sécurité, vérifiez vos sessions et déconnectez toutes les sessions que vous ne connaissez pas ou que vous n’utilisez plus.</string>
+    <string name="settings_sessions_other_title">Autres sessions</string>
     <string name="settings_sessions_list">Sessions</string>
     <string name="a11y_open_spaces">Ouvrir la liste des espaces</string>
     <string name="a11y_create_message">Créer une nouvelle conversation ou salon</string>
diff --git a/library/ui-strings/src/main/res/values-gl/strings.xml b/library/ui-strings/src/main/res/values-gl/strings.xml
index c1e4e40a81..e6d26a63e5 100644
--- a/library/ui-strings/src/main/res/values-gl/strings.xml
+++ b/library/ui-strings/src/main/res/values-gl/strings.xml
@@ -380,7 +380,7 @@
     <string name="settings_theme">Tema</string>
     <string name="encryption_information_decryption_error">Fallo ao descifrar</string>
     <string name="encryption_information_device_name">Nome do dispositivo</string>
-    <string name="device_manager_session_details_session_id">ID de sesión</string>
+    <string name="encryption_information_device_id">ID de sesión</string>
     <string name="encryption_information_device_key">Chave do dispositivo</string>
     <string name="encryption_export_e2e_room_keys">Exportar chaves E2E da sala</string>
     <string name="encryption_export_room_keys">Exportar chaves da sala</string>
diff --git a/library/ui-strings/src/main/res/values-hr/strings.xml b/library/ui-strings/src/main/res/values-hr/strings.xml
index 6d52e5cd96..dc5930b933 100644
--- a/library/ui-strings/src/main/res/values-hr/strings.xml
+++ b/library/ui-strings/src/main/res/values-hr/strings.xml
@@ -572,7 +572,7 @@
     <string name="settings_theme">Tema</string>
     <string name="encryption_information_decryption_error">Greška u dešifriranju</string>
     <string name="encryption_information_device_name">Javni naziv</string>
-    <string name="device_manager_session_details_session_id">Identitet</string>
+    <string name="encryption_information_device_id">Identitet</string>
     <string name="encryption_information_device_key">Ključ sesije</string>
     <string name="encryption_export_e2e_room_keys">Izvezi sobne ključeve za E2E</string>
     <string name="encryption_export_room_keys">Izvezi sobne ključeve</string>
diff --git a/library/ui-strings/src/main/res/values-hu/strings.xml b/library/ui-strings/src/main/res/values-hu/strings.xml
index 32ecfaa45c..af8bf26b2e 100644
--- a/library/ui-strings/src/main/res/values-hu/strings.xml
+++ b/library/ui-strings/src/main/res/values-hu/strings.xml
@@ -351,7 +351,7 @@
     <string name="room_settings_unset_main_address">Kiszedés fő címek közül</string>
     <string name="encryption_information_decryption_error">Visszafejtés hiba</string>
     <string name="encryption_information_device_name">Nyilvános név</string>
-    <string name="device_manager_session_details_session_id">Munkamenet-azonosító</string>
+    <string name="encryption_information_device_id">Munkamenet-azonosító</string>
     <string name="encryption_information_device_key">Munkamenet kulcs</string>
     <string name="encryption_export_e2e_room_keys">E2E szoba kulcsok exportálása</string>
     <string name="encryption_export_room_keys">Szoba kulcsok exportálása</string>
@@ -2615,8 +2615,8 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze
     <string name="a11y_device_manager_device_type_web">Web</string>
     <string name="a11y_device_manager_device_type_mobile">Mobil</string>
     <string name="device_manager_settings_active_sessions_show_all">Minden munkamenet megjelenítése (V2, WIP)</string>
-    <string name="device_manager_sessions_other_description">A legjobb biztonság érdekében ellenőrizd a munkameneteket, és jelentkezz ki minden olyan munkamenetből, melyet már nem ismersz fel vagy nem használsz.</string>
-    <string name="device_manager_sessions_other_title">Más munkamenetek</string>
+    <string name="settings_sessions_other_description">A legjobb biztonság érdekében ellenőrizd a munkameneteket, és jelentkezz ki minden olyan munkamenetből, melyet már nem ismersz fel vagy nem használsz.</string>
+    <string name="settings_sessions_other_title">Más munkamenetek</string>
     <string name="settings_sessions_list">Munkamenetek</string>
     <string name="a11y_open_spaces">Nyitott területek listája</string>
     <string name="a11y_create_message">Új beszélgetés vagy szoba létrehozása</string>
diff --git a/library/ui-strings/src/main/res/values-in/strings.xml b/library/ui-strings/src/main/res/values-in/strings.xml
index 8f910fab6b..d1e68b4529 100644
--- a/library/ui-strings/src/main/res/values-in/strings.xml
+++ b/library/ui-strings/src/main/res/values-in/strings.xml
@@ -301,7 +301,7 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.</string>
     <string name="settings_theme">Tema</string>
     <string name="encryption_information_decryption_error">Kesalahan dekripsi</string>
     <string name="encryption_information_device_name">Nama perangkat</string>
-    <string name="device_manager_session_details_session_id">ID Sesi</string>
+    <string name="encryption_information_device_id">ID Sesi</string>
     <string name="encryption_information_device_key">Kunci perangkat</string>
     <string name="encryption_export_e2e_room_keys">Ekspor kunci ruangan terenkripsi</string>
     <string name="encryption_export_room_keys">Ekspor ruangan kunci</string>
@@ -2553,8 +2553,8 @@ Di masa mendatang proses verifikasi ini akan dimutakhirkan.</string>
     <string name="auth_reset_password_error_unverified">Email belum diverifikasi, periksa kotak masuk Anda</string>
     <string name="all_chats">Semua Obrolan</string>
     <string name="device_manager_settings_active_sessions_show_all">Tampilkan Semua Sesi (V2, Dalam Pengembangan)</string>
-    <string name="device_manager_sessions_other_description">Untuk keamanan terbaik, verifikasi sesi Anda dan keluarkan sesi apa pun yang Anda tidak kenal atau Anda tidak gunakan lagi.</string>
-    <string name="device_manager_sessions_other_title">Sesi lainnya</string>
+    <string name="settings_sessions_other_description">Untuk keamanan terbaik, verifikasi sesi Anda dan keluarkan sesi apa pun yang Anda tidak kenal atau Anda tidak gunakan lagi.</string>
+    <string name="settings_sessions_other_title">Sesi lainnya</string>
     <string name="settings_sessions_list">Sesi</string>
     <string name="a11y_open_spaces">Buka daftar space</string>
     <string name="a11y_create_message">Buat percakapan atau ruangan baru</string>
diff --git a/library/ui-strings/src/main/res/values-is/strings.xml b/library/ui-strings/src/main/res/values-is/strings.xml
index d25d66bfba..7818761145 100644
--- a/library/ui-strings/src/main/res/values-is/strings.xml
+++ b/library/ui-strings/src/main/res/values-is/strings.xml
@@ -193,7 +193,7 @@
     <string name="settings_theme">Þema</string>
     <string name="encryption_information_decryption_error">Afkóðunarvilla</string>
     <string name="encryption_information_device_name">Heiti tækis</string>
-    <string name="device_manager_session_details_session_id">Auðkenni setu</string>
+    <string name="encryption_information_device_id">Auðkenni setu</string>
     <string name="encryption_information_device_key">Dulritunarlykill setu</string>
     <string name="encryption_export_export">Flytja út</string>
     <string name="passphrase_enter_passphrase">Settu inn lykilsetningu</string>
diff --git a/library/ui-strings/src/main/res/values-it/strings.xml b/library/ui-strings/src/main/res/values-it/strings.xml
index 01514cef90..ecb29d1586 100644
--- a/library/ui-strings/src/main/res/values-it/strings.xml
+++ b/library/ui-strings/src/main/res/values-it/strings.xml
@@ -430,7 +430,7 @@
     <string name="settings_theme">Tema</string>
     <string name="encryption_information_decryption_error">Errore di decriptazione</string>
     <string name="encryption_information_device_name">Nome pubblico</string>
-    <string name="device_manager_session_details_session_id">ID sessione</string>
+    <string name="encryption_information_device_id">ID sessione</string>
     <string name="encryption_information_device_key">Chiave sessione</string>
     <string name="encryption_export_e2e_room_keys">Esporta le chiavi di crittografia E2E delle stanze</string>
     <string name="encryption_export_room_keys">Esporta le chiavi delle stanze</string>
@@ -2592,8 +2592,8 @@
     <string name="a11y_open_settings">Apri le impostazioni</string>
     <string name="all_chats">Tutte le chat</string>
     <string name="device_manager_settings_active_sessions_show_all">Mostra tutte le sessioni (V2, WIP)</string>
-    <string name="device_manager_sessions_other_description">Per una maggiore sicurezza, verifica le tue sessioni e disconnetti quelle che non riconosci o che non usi più.</string>
-    <string name="device_manager_sessions_other_title">Altre sessioni</string>
+    <string name="settings_sessions_other_description">Per una maggiore sicurezza, verifica le tue sessioni e disconnetti quelle che non riconosci o che non usi più.</string>
+    <string name="settings_sessions_other_title">Altre sessioni</string>
     <string name="settings_sessions_list">Sessioni</string>
     <string name="a11y_open_spaces">Apri elenco spazi</string>
     <string name="a11y_create_message">Crea una nuova conversazione o stanza</string>
diff --git a/library/ui-strings/src/main/res/values-iw/strings.xml b/library/ui-strings/src/main/res/values-iw/strings.xml
index ff19310c8e..6d9533852b 100644
--- a/library/ui-strings/src/main/res/values-iw/strings.xml
+++ b/library/ui-strings/src/main/res/values-iw/strings.xml
@@ -542,7 +542,7 @@
     <string name="encryption_export_room_keys">יצא מפתחות חדר</string>
     <string name="encryption_export_e2e_room_keys">ייצא מפתחות חדר E2E</string>
     <string name="encryption_information_device_key">מזהה מפתח</string>
-    <string name="device_manager_session_details_session_id">מזהה מושב</string>
+    <string name="encryption_information_device_id">מזהה מושב</string>
     <string name="encryption_information_device_name">שם ציבורי</string>
     <string name="encryption_information_decryption_error">שגיאת פענוח</string>
     <string name="settings_theme">ערכת נושא</string>
diff --git a/library/ui-strings/src/main/res/values-ja/strings.xml b/library/ui-strings/src/main/res/values-ja/strings.xml
index 3e817e398c..b781e4d7f0 100644
--- a/library/ui-strings/src/main/res/values-ja/strings.xml
+++ b/library/ui-strings/src/main/res/values-ja/strings.xml
@@ -197,7 +197,7 @@
     <string name="room_settings_labs_warning_message">これらは予期しない不具合が生じるかもしれない実験的機能です。慎重に使用してください。</string>
     <string name="room_settings_set_main_address">メインアドレスとして設定</string>
     <string name="room_settings_unset_main_address">メインアドレスとしての設定を解除</string>
-    <string name="device_manager_session_details_session_id">セッションID</string>
+    <string name="encryption_information_device_id">セッションID</string>
     <string name="font_size">文字の大きさ</string>
     <string name="tiny">とても小さい</string>
     <string name="small">小さい</string>
diff --git a/library/ui-strings/src/main/res/values-kab/strings.xml b/library/ui-strings/src/main/res/values-kab/strings.xml
index 353fb99f53..a79b72efde 100644
--- a/library/ui-strings/src/main/res/values-kab/strings.xml
+++ b/library/ui-strings/src/main/res/values-kab/strings.xml
@@ -291,7 +291,7 @@
     <string name="room_settings_category_advanced_title">Talqayt</string>
     <string name="room_settings_labs_pref_title">Tinarimin</string>
     <string name="settings_theme">Asentel</string>
-    <string name="device_manager_session_details_session_id">Asulay n tqimit</string>
+    <string name="encryption_information_device_id">Asulay n tqimit</string>
     <string name="encryption_information_device_key">Tasarut n tɣimit</string>
     <string name="encryption_export_e2e_room_keys">Sifeḍ tisura n texxamt E2E</string>
     <string name="encryption_export_room_keys">Sifeḍ tisura n texxamt</string>
diff --git a/library/ui-strings/src/main/res/values-ko/strings.xml b/library/ui-strings/src/main/res/values-ko/strings.xml
index 37e8849fa8..ba0cbe5abd 100644
--- a/library/ui-strings/src/main/res/values-ko/strings.xml
+++ b/library/ui-strings/src/main/res/values-ko/strings.xml
@@ -431,7 +431,7 @@
     <string name="settings_theme">테마</string>
     <string name="encryption_information_decryption_error">암호 복호화 오류</string>
     <string name="encryption_information_device_name">공개 이름</string>
-    <string name="device_manager_session_details_session_id">ID</string>
+    <string name="encryption_information_device_id">ID</string>
     <string name="encryption_information_device_key">기기 키</string>
     <string name="encryption_export_e2e_room_keys">종단간 암호화 방 키 내보내기</string>
     <string name="encryption_export_room_keys">방 키 내보내기</string>
diff --git a/library/ui-strings/src/main/res/values-lo/strings.xml b/library/ui-strings/src/main/res/values-lo/strings.xml
index a92adb0225..1a9a2820b8 100644
--- a/library/ui-strings/src/main/res/values-lo/strings.xml
+++ b/library/ui-strings/src/main/res/values-lo/strings.xml
@@ -909,7 +909,7 @@
     <string name="encryption_export_room_keys">ສົ່ງອອກກະແຈຫ້ອງ</string>
     <string name="encryption_export_e2e_room_keys">ສົ່ງອອກກະແຈຫ້ອງ E2E</string>
     <string name="encryption_information_device_key">ລະຫັດລະບົບ</string>
-    <string name="device_manager_session_details_session_id">ID ລະບົບ</string>
+    <string name="encryption_information_device_id">ID ລະບົບ</string>
     <string name="encryption_information_device_name">ຊື່ສາທາລະນະ</string>
     <string name="encryption_information_decryption_error">ການຖອດລະຫັດຜິດພາດ</string>
     <string name="settings_theme">ຫົວຂໍ້</string>
diff --git a/library/ui-strings/src/main/res/values-lv/strings.xml b/library/ui-strings/src/main/res/values-lv/strings.xml
index 1787653fae..f1fa1502c1 100644
--- a/library/ui-strings/src/main/res/values-lv/strings.xml
+++ b/library/ui-strings/src/main/res/values-lv/strings.xml
@@ -469,7 +469,7 @@
     <string name="settings_theme">Tēma</string>
     <string name="encryption_information_decryption_error">Atšifrēšanas kļūda</string>
     <string name="encryption_information_device_name">Ierīces nosaukums</string>
-    <string name="device_manager_session_details_session_id">Sesijas ID</string>
+    <string name="encryption_information_device_id">Sesijas ID</string>
     <string name="encryption_information_device_key">Sesijas atslēga</string>
     <string name="encryption_export_e2e_room_keys">Eksportēt istabas šifrēšanas atslēgas</string>
     <string name="encryption_export_room_keys">Eksportēt istabas atslēgas</string>
diff --git a/library/ui-strings/src/main/res/values-nb-rNO/strings.xml b/library/ui-strings/src/main/res/values-nb-rNO/strings.xml
index 7af718d920..031b380c7e 100644
--- a/library/ui-strings/src/main/res/values-nb-rNO/strings.xml
+++ b/library/ui-strings/src/main/res/values-nb-rNO/strings.xml
@@ -119,7 +119,7 @@
     <string name="room_settings_banned_users_title">Bannlyste brukere</string>
     <string name="room_settings_category_advanced_title">Avansert</string>
     <string name="settings_theme">Tema</string>
-    <string name="device_manager_session_details_session_id">Økt-ID</string>
+    <string name="encryption_information_device_id">Økt-ID</string>
     <string name="encryption_information_device_key">Øktnøkkel</string>
     <string name="encryption_export_export">Eksporter</string>
     <string name="encryption_import_import">Importer</string>
diff --git a/library/ui-strings/src/main/res/values-nl/strings.xml b/library/ui-strings/src/main/res/values-nl/strings.xml
index af2aa291fc..b1d239963e 100644
--- a/library/ui-strings/src/main/res/values-nl/strings.xml
+++ b/library/ui-strings/src/main/res/values-nl/strings.xml
@@ -275,7 +275,7 @@
     <string name="room_settings_unset_main_address">Niet instellen als hoofdadres</string>
     <string name="encryption_information_decryption_error">Ontsleutelingsfout</string>
     <string name="encryption_information_device_name">Publieke naam</string>
-    <string name="device_manager_session_details_session_id">Sessie ID</string>
+    <string name="encryption_information_device_id">Sessie ID</string>
     <string name="encryption_information_device_key">Sessiesleutel</string>
     <string name="encryption_export_e2e_room_keys">E2E-gesprekssleutels exporteren</string>
     <string name="encryption_export_room_keys">Gesprekssleutels exporteren</string>
@@ -2600,8 +2600,8 @@
     <string name="location_share_loading_map_error">Kan kaart niet laden
 \nDeze server is mogelijk niet geconfigureerd om kaarten weer te geven.</string>
     <string name="a11y_open_settings">Open instellingen</string>
-    <string name="device_manager_sessions_other_description">Voor de beste beveiliging verifieert u uw sessies en meldt u zich af bij elke sessie die u niet meer herkent of gebruikt.</string>
-    <string name="device_manager_sessions_other_title">Andere sessies</string>
+    <string name="settings_sessions_other_description">Voor de beste beveiliging verifieert u uw sessies en meldt u zich af bij elke sessie die u niet meer herkent of gebruikt.</string>
+    <string name="settings_sessions_other_title">Andere sessies</string>
     <string name="settings_sessions_list">Sessies</string>
     <string name="a11y_open_spaces">Lijst met publieke spaces</string>
     <string name="a11y_create_message">Maak een nieuw gesprek of een nieuwe kamer</string>
diff --git a/library/ui-strings/src/main/res/values-nn/strings.xml b/library/ui-strings/src/main/res/values-nn/strings.xml
index 45c8679736..a56ba0ac30 100644
--- a/library/ui-strings/src/main/res/values-nn/strings.xml
+++ b/library/ui-strings/src/main/res/values-nn/strings.xml
@@ -310,7 +310,7 @@
     <string name="settings_theme">Preg</string>
     <string name="encryption_information_decryption_error">Noko gjekk gale med dekrypteringa</string>
     <string name="encryption_information_device_name">Offentleg namn</string>
-    <string name="device_manager_session_details_session_id">Økt-ID</string>
+    <string name="encryption_information_device_id">Økt-ID</string>
     <string name="encryption_information_device_key">Sesjonsnøkkel</string>
     <string name="encryption_export_e2e_room_keys">Eksporter E2E-romnøkklar</string>
     <string name="encryption_export_room_keys">Eksporter romnøkklar</string>
diff --git a/library/ui-strings/src/main/res/values-pl/strings.xml b/library/ui-strings/src/main/res/values-pl/strings.xml
index e6b3a0c35c..a657709543 100644
--- a/library/ui-strings/src/main/res/values-pl/strings.xml
+++ b/library/ui-strings/src/main/res/values-pl/strings.xml
@@ -231,7 +231,7 @@
     <string name="room_settings_set_main_address">Ustaw jako główny adres</string>
     <string name="settings_theme">Motyw</string>
     <string name="encryption_information_device_name">Nazwa publiczna</string>
-    <string name="device_manager_session_details_session_id">ID sesji</string>
+    <string name="encryption_information_device_id">ID sesji</string>
     <string name="encryption_export_export">Eksportuj</string>
     <string name="passphrase_enter_passphrase">Wprowadź hasło</string>
     <string name="passphrase_confirm_passphrase">Potwierdź hasło</string>
@@ -2697,8 +2697,8 @@
     <string name="location_share_loading_map_error">Nie można wczytać mapy.
 \nTen serwer macierzysty może nie być skonfigurowany do wyświetlania map.</string>
     <string name="a11y_open_settings">Otwórz ustawienia</string>
-    <string name="device_manager_sessions_other_description">Aby zapewnić najlepsze bezpieczeństwo, zweryfikuj swoje sesje i wyloguj się z każdej sesji, której już nie rozpoznajesz lub której już nie używasz.</string>
-    <string name="device_manager_sessions_other_title">Inne sesje</string>
+    <string name="settings_sessions_other_description">Aby zapewnić najlepsze bezpieczeństwo, zweryfikuj swoje sesje i wyloguj się z każdej sesji, której już nie rozpoznajesz lub której już nie używasz.</string>
+    <string name="settings_sessions_other_title">Inne sesje</string>
     <string name="settings_sessions_list">Sesje</string>
     <string name="a11y_open_spaces">Lista otwartych przestrzeni</string>
     <string name="a11y_create_message">Utwórz nową rozmowę lub pokój</string>
diff --git a/library/ui-strings/src/main/res/values-pt-rBR/strings.xml b/library/ui-strings/src/main/res/values-pt-rBR/strings.xml
index b45de27933..08c41db365 100644
--- a/library/ui-strings/src/main/res/values-pt-rBR/strings.xml
+++ b/library/ui-strings/src/main/res/values-pt-rBR/strings.xml
@@ -418,7 +418,7 @@
     <string name="room_settings_unset_main_address">Des-definir como endereço principal</string>
     <string name="encryption_information_decryption_error">Erro de decriptação</string>
     <string name="encryption_information_device_name">Nome público</string>
-    <string name="device_manager_session_details_session_id">ID de sessão</string>
+    <string name="encryption_information_device_id">ID de sessão</string>
     <string name="encryption_information_device_key">Chave de sessão</string>
     <string name="encryption_export_e2e_room_keys">Exportar chaves de sala E2E</string>
     <string name="encryption_export_room_keys">Exportar chaves de sala</string>
@@ -2601,8 +2601,8 @@
     <string name="a11y_open_settings">Abrir configurações</string>
     <string name="all_chats">Todos os Chats</string>
     <string name="device_manager_settings_active_sessions_show_all">Mostrar Todas Sessões (V2, WIP)</string>
-    <string name="device_manager_sessions_other_description">Para a melhor segurança, verifique suas sessões e faça signout de qualquer sessão que você não reconhece ou usa mais.</string>
-    <string name="device_manager_sessions_other_title">Outras sessões</string>
+    <string name="settings_sessions_other_description">Para a melhor segurança, verifique suas sessões e faça signout de qualquer sessão que você não reconhece ou usa mais.</string>
+    <string name="settings_sessions_other_title">Outras sessões</string>
     <string name="settings_sessions_list">Sessões</string>
     <string name="a11y_open_spaces">Abrir lista de espaços</string>
     <string name="a11y_create_message">Criar uma nova conversa ou sala</string>
diff --git a/library/ui-strings/src/main/res/values-pt/strings.xml b/library/ui-strings/src/main/res/values-pt/strings.xml
index 87b6297b2b..4daaef83b0 100644
--- a/library/ui-strings/src/main/res/values-pt/strings.xml
+++ b/library/ui-strings/src/main/res/values-pt/strings.xml
@@ -246,7 +246,7 @@ Note que esta acção irá reiniciar a aplicação e poderá levar algum tempo.<
 
     <string name="encryption_information_decryption_error">Erro de decifragem</string>
     <string name="encryption_information_device_name">Nome do dispositivo</string>
-    <string name="device_manager_session_details_session_id">ID do dispositivo</string>
+    <string name="encryption_information_device_id">ID do dispositivo</string>
     <string name="encryption_information_device_key">Chave do dispositivo</string>
     <string name="encryption_export_e2e_room_keys">Exportar chaves E2E da sala</string>
     <string name="encryption_export_room_keys">Exportar chaves de sala</string>
diff --git a/library/ui-strings/src/main/res/values-ru/strings.xml b/library/ui-strings/src/main/res/values-ru/strings.xml
index f503fec55c..8d223bae5e 100644
--- a/library/ui-strings/src/main/res/values-ru/strings.xml
+++ b/library/ui-strings/src/main/res/values-ru/strings.xml
@@ -432,7 +432,7 @@
     <string name="room_settings_unset_main_address">Сбросить основной адрес</string>
     <string name="encryption_information_decryption_error">Ошибка дешифровки</string>
     <string name="encryption_information_device_name">Публичное имя</string>
-    <string name="device_manager_session_details_session_id">ID сессии</string>
+    <string name="encryption_information_device_id">ID сессии</string>
     <string name="encryption_information_device_key">Ключ сессии</string>
     <string name="encryption_export_e2e_room_keys">Экспорт E2E ключей комнаты</string>
     <string name="encryption_export_room_keys">Экспорт ключей комнаты</string>
@@ -2660,8 +2660,8 @@
     <string name="location_share_loading_map_error">Не удалось загрузить карту
 \nВозможно, этот домашний сервер не настроен для отображения карт.</string>
     <string name="all_chats">Все беседы</string>
-    <string name="device_manager_sessions_other_description">Для лучшей безопасности заверьте свои сессии и выйдите из тех, которые более не признаёте или не используете.</string>
-    <string name="device_manager_sessions_other_title">Другие сессии</string>
+    <string name="settings_sessions_other_description">Для лучшей безопасности заверьте свои сессии и выйдите из тех, которые более не признаёте или не используете.</string>
+    <string name="settings_sessions_other_title">Другие сессии</string>
     <string name="settings_sessions_list">Сессии</string>
     <string name="a11y_create_message">Создать беседу или комнату</string>
     <string name="device_manager_settings_active_sessions_show_all">Показать все сессии (V2, в разработке)</string>
diff --git a/library/ui-strings/src/main/res/values-sk/strings.xml b/library/ui-strings/src/main/res/values-sk/strings.xml
index 3b2392a610..2cc2d0280e 100644
--- a/library/ui-strings/src/main/res/values-sk/strings.xml
+++ b/library/ui-strings/src/main/res/values-sk/strings.xml
@@ -388,7 +388,7 @@
     <string name="settings_theme">Vzhľad</string>
     <string name="encryption_information_decryption_error">Chyba dešifrovania</string>
     <string name="encryption_information_device_name">Verejné meno</string>
-    <string name="device_manager_session_details_session_id">ID relácie</string>
+    <string name="encryption_information_device_id">ID relácie</string>
     <string name="encryption_information_device_key">Kľúč relácie</string>
     <string name="encryption_export_e2e_room_keys">Exportovať šifrovacie kľúče miestnosti</string>
     <string name="encryption_export_room_keys">Exportovať kľúče miestnosti</string>
@@ -2651,8 +2651,8 @@
     <string name="a11y_open_settings">Otvoriť nastavenia</string>
     <string name="all_chats">Všetky konverzácie</string>
     <string name="device_manager_settings_active_sessions_show_all">Zobraziť všetky relácie (V2, WIP)</string>
-    <string name="device_manager_sessions_other_description">V záujme čo najlepšieho zabezpečenia overte svoje relácie a odhláste sa z každej relácie, ktorú už nepoznáte alebo nepoužívate.</string>
-    <string name="device_manager_sessions_other_title">Iné relácie</string>
+    <string name="settings_sessions_other_description">V záujme čo najlepšieho zabezpečenia overte svoje relácie a odhláste sa z každej relácie, ktorú už nepoznáte alebo nepoužívate.</string>
+    <string name="settings_sessions_other_title">Iné relácie</string>
     <string name="settings_sessions_list">Relácie</string>
     <string name="a11y_open_spaces">Otvoriť zoznam priestorov</string>
     <string name="a11y_create_message">Vytvoriť novú konverzáciu alebo miestnosť</string>
diff --git a/library/ui-strings/src/main/res/values-sq/strings.xml b/library/ui-strings/src/main/res/values-sq/strings.xml
index a6af0a4921..8fdf4ee310 100644
--- a/library/ui-strings/src/main/res/values-sq/strings.xml
+++ b/library/ui-strings/src/main/res/values-sq/strings.xml
@@ -431,7 +431,7 @@
     <string name="settings_theme">Temë</string>
     <string name="encryption_information_decryption_error">Gabim shfshehtëzimi</string>
     <string name="encryption_information_device_name">Emër publik</string>
-    <string name="device_manager_session_details_session_id">ID Sesioni</string>
+    <string name="encryption_information_device_id">ID Sesioni</string>
     <string name="encryption_information_device_key">Kyç sesioni</string>
     <string name="encryption_export_e2e_room_keys">Eksporto kyçe dhome E2E</string>
     <string name="encryption_export_room_keys">Eksporto kyçe dhome</string>
diff --git a/library/ui-strings/src/main/res/values-sv/strings.xml b/library/ui-strings/src/main/res/values-sv/strings.xml
index 30b63c213c..025713272c 100644
--- a/library/ui-strings/src/main/res/values-sv/strings.xml
+++ b/library/ui-strings/src/main/res/values-sv/strings.xml
@@ -918,7 +918,7 @@
     <string name="settings_secure_backup_enter_to_setup">Sätt upp på den här enheten</string>
     <string name="reset_secure_backup_title">Generera en ny säkerhetskopia eller sätt en ny lösenfras för din existerande säkerhetskopia.</string>
     <string name="room_settings_labs_warning_message">Detta är experimentella funktioner som kan gå sönder på oväntade sätt. Använd varsamt.</string>
-    <string name="device_manager_session_details_session_id">Sessions-ID</string>
+    <string name="encryption_information_device_id">Sessions-ID</string>
     <string name="encryption_information_device_key">Sessionsnyckel</string>
     <string name="encryption_export_e2e_room_keys">Exportera krypteringsnycklar</string>
     <string name="encryption_export_room_keys">Exportera rumsnycklar</string>
diff --git a/library/ui-strings/src/main/res/values-te/strings.xml b/library/ui-strings/src/main/res/values-te/strings.xml
index 0154d54c2e..5ed2462ce8 100644
--- a/library/ui-strings/src/main/res/values-te/strings.xml
+++ b/library/ui-strings/src/main/res/values-te/strings.xml
@@ -260,7 +260,7 @@
     <string name="room_settings_set_main_address">ప్రధాన చిరునామాగా సెట్ చేయండి</string>
 
     <string name="encryption_information_device_name">పరికరం పేరు</string>
-    <string name="device_manager_session_details_session_id">పరికరం ID</string>
+    <string name="encryption_information_device_id">పరికరం ID</string>
     <string name="encryption_information_device_key">పరికరం కీ</string>
 
     <string name="encryption_export_e2e_room_keys">E2E గది కీలను ఎగుమతి చేయండి</string>
diff --git a/library/ui-strings/src/main/res/values-tr/strings.xml b/library/ui-strings/src/main/res/values-tr/strings.xml
index 1f0e5be153..c097bfce6a 100644
--- a/library/ui-strings/src/main/res/values-tr/strings.xml
+++ b/library/ui-strings/src/main/res/values-tr/strings.xml
@@ -376,7 +376,7 @@
     <string name="settings_theme">Tema</string>
     <string name="encryption_information_decryption_error">Çözme hatası</string>
     <string name="encryption_information_device_name">Görünür Ad</string>
-    <string name="device_manager_session_details_session_id">Oturum kimliği</string>
+    <string name="encryption_information_device_id">Oturum kimliği</string>
     <string name="encryption_information_device_key">Oturum anahtarı</string>
     <string name="encryption_export_e2e_room_keys">E2E Oda anahtarlarını dışa aktar</string>
     <string name="encryption_export_room_keys">Oda anahtarlarını dışa aktar</string>
diff --git a/library/ui-strings/src/main/res/values-uk/strings.xml b/library/ui-strings/src/main/res/values-uk/strings.xml
index 1162fc2a90..1c809fff3e 100644
--- a/library/ui-strings/src/main/res/values-uk/strings.xml
+++ b/library/ui-strings/src/main/res/values-uk/strings.xml
@@ -354,7 +354,7 @@
     <string name="room_settings_unset_main_address">Зробити не основною адресою</string>
     <string name="encryption_information_decryption_error">Помилка розшифрування</string>
     <string name="encryption_information_device_name">Загальнодоступна назва</string>
-    <string name="device_manager_session_details_session_id">ID сеансу</string>
+    <string name="encryption_information_device_id">ID сеансу</string>
     <string name="encryption_information_device_key">Ключ сеансу</string>
     <string name="encryption_export_e2e_room_keys">Експортувати E2E ключі кімнати</string>
     <string name="encryption_export_room_keys">Експортувати ключі кімнати</string>
@@ -2701,8 +2701,8 @@
     <string name="a11y_open_settings">Відкрити налаштування</string>
     <string name="all_chats">Усі бесіди</string>
     <string name="device_manager_settings_active_sessions_show_all">Показати всі сеанси (V2, WIP)</string>
-    <string name="device_manager_sessions_other_description">Для найкращої безпеки перевірте свої сеанси та вийдіть з усіх сеансів, які ви більше не розпізнаєте або не використовуєте.</string>
-    <string name="device_manager_sessions_other_title">Інші сеанси</string>
+    <string name="settings_sessions_other_description">Для найкращої безпеки перевірте свої сеанси та вийдіть з усіх сеансів, які ви більше не розпізнаєте або не використовуєте.</string>
+    <string name="settings_sessions_other_title">Інші сеанси</string>
     <string name="settings_sessions_list">Сеанси</string>
     <string name="a11y_open_spaces">Відкрити список кімнат</string>
     <string name="a11y_create_message">Створити нову розмову або кімнату</string>
diff --git a/library/ui-strings/src/main/res/values-vi/strings.xml b/library/ui-strings/src/main/res/values-vi/strings.xml
index c6dc97f782..2803128843 100644
--- a/library/ui-strings/src/main/res/values-vi/strings.xml
+++ b/library/ui-strings/src/main/res/values-vi/strings.xml
@@ -594,7 +594,7 @@
     <string name="deactivate_account_title">Hủy tài khoản</string>
     <string name="dialog_user_consent_submit">Xem lại ngay</string>
     <string name="encryption_information_device_key">Chìa khóa phiên</string>
-    <string name="device_manager_session_details_session_id">Mã phiên</string>
+    <string name="encryption_information_device_id">Mã phiên</string>
     <string name="encryption_information_device_name">Tên công khai</string>
     <string name="encryption_information_decryption_error">Lỗi giải mã</string>
     <string name="room_settings_labs_warning_message">Những chức năng này mang tính thí nghiệm có thể còn nhiều lỗi. Lưu ý khi dùng.</string>
diff --git a/library/ui-strings/src/main/res/values-zh-rCN/strings.xml b/library/ui-strings/src/main/res/values-zh-rCN/strings.xml
index e05949ff7d..ee0e95d648 100644
--- a/library/ui-strings/src/main/res/values-zh-rCN/strings.xml
+++ b/library/ui-strings/src/main/res/values-zh-rCN/strings.xml
@@ -242,7 +242,7 @@
     <string name="settings_password_updated">你的密码已更新</string>
     <string name="encryption_information_decryption_error">解密错误</string>
     <string name="encryption_information_device_name">公开名称</string>
-    <string name="device_manager_session_details_session_id">会话 ID</string>
+    <string name="encryption_information_device_id">会话 ID</string>
     <string name="encryption_information_device_key">会话密钥</string>
     <string name="encryption_import_import">导入</string>
     <string name="encryption_information_verified">已验证</string>
@@ -2551,8 +2551,8 @@
     <string name="a11y_open_settings">打开设置</string>
     <string name="all_chats">全部聊天</string>
     <string name="device_manager_settings_active_sessions_show_all">显示全部会话(V2, WIP)</string>
-    <string name="device_manager_sessions_other_description">为获得最佳安全性,请验证你的会话,并从任何你不认识或不再使用的会话登出。</string>
-    <string name="device_manager_sessions_other_title">其他会话</string>
+    <string name="settings_sessions_other_description">为获得最佳安全性,请验证你的会话,并从任何你不认识或不再使用的会话登出。</string>
+    <string name="settings_sessions_other_title">其他会话</string>
     <string name="settings_sessions_list">会话</string>
     <string name="a11y_open_spaces">打开空间列表</string>
     <string name="a11y_create_message">创建新对话或房间</string>
diff --git a/library/ui-strings/src/main/res/values-zh-rTW/strings.xml b/library/ui-strings/src/main/res/values-zh-rTW/strings.xml
index a2503f66f9..0f5208bcde 100644
--- a/library/ui-strings/src/main/res/values-zh-rTW/strings.xml
+++ b/library/ui-strings/src/main/res/values-zh-rTW/strings.xml
@@ -469,7 +469,7 @@
     <string name="settings_theme">主題</string>
     <string name="encryption_information_decryption_error">解密錯誤</string>
     <string name="encryption_information_device_name">公開名稱</string>
-    <string name="device_manager_session_details_session_id">工作階段 ID</string>
+    <string name="encryption_information_device_id">工作階段 ID</string>
     <string name="encryption_information_device_key">工作階段金鑰</string>
     <string name="encryption_export_e2e_room_keys">匯出聊天室的端到端加密金鑰</string>
     <string name="encryption_export_room_keys">匯出聊天室的加密金鑰</string>
@@ -2551,8 +2551,8 @@
     <string name="a11y_open_settings">開啟設定</string>
     <string name="all_chats">所有聊天</string>
     <string name="device_manager_settings_active_sessions_show_all">顯示所有工作階段 (V2, WIP)</string>
-    <string name="device_manager_sessions_other_description">為了取得最佳安全性,請驗證您的工作階段並登出任何您無法識別或不再使用的工作階段。</string>
-    <string name="device_manager_sessions_other_title">其他工作階段</string>
+    <string name="settings_sessions_other_description">為了取得最佳安全性,請驗證您的工作階段並登出任何您無法識別或不再使用的工作階段。</string>
+    <string name="settings_sessions_other_title">其他工作階段</string>
     <string name="settings_sessions_list">工作階段</string>
     <string name="a11y_open_spaces">開啟空間清單</string>
     <string name="a11y_create_message">建立新的對話或聊天室</string>
diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml
index 0364cc4565..1c6150bf9c 100644
--- a/library/ui-strings/src/main/res/values/strings.xml
+++ b/library/ui-strings/src/main/res/values/strings.xml
@@ -2364,8 +2364,10 @@
     <string name="settings_active_sessions_manage">Manage Sessions</string>
     <string name="settings_active_sessions_signout_device">Sign out of this session</string>
     <string name="settings_sessions_list">Sessions</string>
-    <string name="device_manager_sessions_other_title">Other sessions</string>
-    <string name="device_manager_sessions_other_description">For best security, verify your sessions and sign out from any session that you don’t recognize or use anymore.</string>
+    <!-- TODO rename to device_manager_sessions_other_title -->
+    <string name="settings_sessions_other_title">Other sessions</string>
+    <!-- TODO rename to device_manager_sessions_other_description -->
+    <string name="settings_sessions_other_description">For best security, verify your sessions and sign out from any session that you don’t recognize or use anymore.</string>
 
     <string name="settings_server_name">Server name</string>
     <string name="settings_server_version">Server version</string>
@@ -3296,7 +3298,8 @@
     <string name="device_manager_session_details_title">Session details</string>
     <string name="device_manager_session_details_description">Application, device, and activity information.</string>
     <string name="device_manager_session_details_session_name">Session name</string>
-    <string name="device_manager_session_details_session_id">Session ID</string>
+    <!-- TODO rename to device_manager_session_details_session_id -->
+    <string name="encryption_information_device_id">Session ID</string>
     <string name="device_manager_session_details_session_last_activity">Last activity</string>
     <string name="device_manager_session_details_device_ip_address">IP address</string>
 
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsController.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsController.kt
index 1fb5be4d78..3d3b6dfdcb 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsController.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/details/SessionDetailsController.kt
@@ -95,7 +95,7 @@ class SessionDetailsController @Inject constructor(
         }
         sessionId?.let {
             val hasDivider = sessionLastSeenTs != null
-            buildContentItem(R.string.device_manager_session_details_session_id, it, hasDivider)
+            buildContentItem(R.string.encryption_information_device_id, it, hasDivider)
         }
         sessionLastSeenTs?.let {
             val formattedDate = dateFormatter.format(it, DateFormatKind.MESSAGE_DETAIL)
diff --git a/vector/src/main/res/layout/dialog_device_verify.xml b/vector/src/main/res/layout/dialog_device_verify.xml
index 475ffc69af..82432a892a 100644
--- a/vector/src/main/res/layout/dialog_device_verify.xml
+++ b/vector/src/main/res/layout/dialog_device_verify.xml
@@ -39,7 +39,7 @@
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_marginTop="6dp"
-            android:text="@string/device_manager_session_details_session_id"
+            android:text="@string/encryption_information_device_id"
             android:textStyle="bold" />
 
         <TextView
diff --git a/vector/src/main/res/layout/fragment_other_sessions.xml b/vector/src/main/res/layout/fragment_other_sessions.xml
index 037f85ad28..7fbc92529a 100644
--- a/vector/src/main/res/layout/fragment_other_sessions.xml
+++ b/vector/src/main/res/layout/fragment_other_sessions.xml
@@ -18,7 +18,7 @@
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             app:navigationIcon="@drawable/ic_back_24dp"
-            app:title="@string/device_manager_sessions_other_title">
+            app:title="@string/settings_sessions_other_title">
 
             <FrameLayout
                 android:id="@+id/otherSessionsFilterFrameLayout"
@@ -53,7 +53,7 @@
         android:id="@+id/deviceListHeaderOtherSessions"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
-        app:sessionsListHeaderDescription="@string/device_manager_sessions_other_description"
+        app:sessionsListHeaderDescription="@string/settings_sessions_other_description"
         app:sessionsListHeaderTitle=""
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
diff --git a/vector/src/main/res/layout/fragment_settings_devices.xml b/vector/src/main/res/layout/fragment_settings_devices.xml
index 8e2daa2239..b53aef33d7 100644
--- a/vector/src/main/res/layout/fragment_settings_devices.xml
+++ b/vector/src/main/res/layout/fragment_settings_devices.xml
@@ -90,8 +90,8 @@
             android:id="@+id/deviceListHeaderOtherSessions"
             android:layout_width="0dp"
             android:layout_height="wrap_content"
-            app:sessionsListHeaderDescription="@string/device_manager_sessions_other_description"
-            app:sessionsListHeaderTitle="@string/device_manager_sessions_other_title"
+            app:sessionsListHeaderDescription="@string/settings_sessions_other_description"
+            app:sessionsListHeaderTitle="@string/settings_sessions_other_title"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintTop_toBottomOf="@id/deviceListDividerCurrentSession" />
diff --git a/vector/src/main/res/xml/vector_settings_security_privacy.xml b/vector/src/main/res/xml/vector_settings_security_privacy.xml
index 1e8997e9c8..c246a40f71 100644
--- a/vector/src/main/res/xml/vector_settings_security_privacy.xml
+++ b/vector/src/main/res/xml/vector_settings_security_privacy.xml
@@ -35,7 +35,7 @@
         <im.vector.app.core.preference.VectorPreference
             android:key="SETTINGS_ENCRYPTION_INFORMATION_DEVICE_ID_PREFERENCE_KEY"
             android:persistent="false"
-            android:title="@string/device_manager_session_details_session_id"
+            android:title="@string/encryption_information_device_id"
             tools:summary="VZRHETBEER" />
 
         <im.vector.app.core.preference.VectorPreference